style: format all Rust code with cargo fmt

This commit is contained in:
Gulshan Yadav 2026-01-08 04:54:54 +05:30
parent ae0a9d7cfa
commit d917f1ed22
97 changed files with 2259 additions and 1543 deletions

View file

@ -29,12 +29,7 @@ impl RpcClient {
"params": params "params": params
}); });
let response = self let response = self.client.post(&self.url).json(&request).send().await?;
.client
.post(&self.url)
.json(&request)
.send()
.await?;
let rpc_response: RpcResponse<T> = response.json().await?; let rpc_response: RpcResponse<T> = response.json().await?;
@ -68,7 +63,8 @@ impl RpcClient {
/// Gets a block by hash. /// Gets a block by hash.
pub async fn get_block(&self, hash: &str, include_txs: bool) -> Result<Block> { pub async fn get_block(&self, hash: &str, include_txs: bool) -> Result<Block> {
self.call("synor_getBlock", json!([hash, include_txs])).await self.call("synor_getBlock", json!([hash, include_txs]))
.await
} }
/// Gets block header. /// Gets block header.
@ -235,7 +231,11 @@ impl RpcClient {
} }
/// Gets contract storage value. /// Gets contract storage value.
pub async fn get_contract_storage(&self, contract_id: &str, key: &str) -> Result<GetStorageResult> { pub async fn get_contract_storage(
&self,
contract_id: &str,
key: &str,
) -> Result<GetStorageResult> {
self.call( self.call(
"synor_getStorageAt", "synor_getStorageAt",
json!({ json!({

View file

@ -22,12 +22,20 @@ pub async fn validate(address: &str, format: OutputFormat) -> Result<()> {
OutputFormat::Text => { OutputFormat::Text => {
if validation.is_valid { if validation.is_valid {
output::print_success("Address is valid"); output::print_success("Address is valid");
output::print_kv("Network", validation.network.as_deref().unwrap_or("unknown")); output::print_kv(
output::print_kv("Type", validation.address_type.as_deref().unwrap_or("unknown")); "Network",
validation.network.as_deref().unwrap_or("unknown"),
);
output::print_kv(
"Type",
validation.address_type.as_deref().unwrap_or("unknown"),
);
} else { } else {
output::print_error(&format!( output::print_error(&format!(
"Invalid address: {}", "Invalid address: {}",
validation.error.unwrap_or_else(|| "unknown error".to_string()) validation
.error
.unwrap_or_else(|| "unknown error".to_string())
)); ));
} }
} }

View file

@ -35,7 +35,10 @@ pub async fn get_block(client: &RpcClient, id: &str, format: OutputFormat) -> Re
output::print_header("Block"); output::print_header("Block");
output::print_kv("Hash", &block.hash); output::print_kv("Hash", &block.hash);
output::print_kv("Version", &block.header.version.to_string()); output::print_kv("Version", &block.header.version.to_string());
output::print_kv("Timestamp", &output::format_timestamp(block.header.timestamp)); output::print_kv(
"Timestamp",
&output::format_timestamp(block.header.timestamp),
);
output::print_kv("Blue Score", &block.header.blue_score.to_string()); output::print_kv("Blue Score", &block.header.blue_score.to_string());
output::print_kv("Bits", &format!("0x{:08x}", block.header.bits)); output::print_kv("Bits", &format!("0x{:08x}", block.header.bits));
output::print_kv("Nonce", &block.header.nonce.to_string()); output::print_kv("Nonce", &block.header.nonce.to_string());

View file

@ -18,24 +18,55 @@ pub async fn handle(
format: OutputFormat, format: OutputFormat,
) -> Result<()> { ) -> Result<()> {
match cmd { match cmd {
ContractCommands::Deploy { wasm, deployer, args, gas } => { ContractCommands::Deploy {
deploy(client, wasm, &deployer, args.as_deref(), Some(gas), format).await wasm,
} deployer,
ContractCommands::Call { contract_id, method, caller, args, value, gas } => { args,
call(client, &contract_id, &method, &caller, args.as_deref(), value, Some(gas), format).await gas,
} } => deploy(client, wasm, &deployer, args.as_deref(), Some(gas), format).await,
ContractCommands::Code { contract_id } => { ContractCommands::Call {
code(client, &contract_id, format).await contract_id,
method,
caller,
args,
value,
gas,
} => {
call(
client,
&contract_id,
&method,
&caller,
args.as_deref(),
value,
Some(gas),
format,
)
.await
} }
ContractCommands::Code { contract_id } => code(client, &contract_id, format).await,
ContractCommands::Storage { contract_id, key } => { ContractCommands::Storage { contract_id, key } => {
storage(client, &contract_id, &key, format).await storage(client, &contract_id, &key, format).await
} }
ContractCommands::EstimateGas { contract_id, method, caller, args, value } => { ContractCommands::EstimateGas {
estimate_gas(client, &contract_id, &method, &caller, args.as_deref(), value, format).await contract_id,
} method,
ContractCommands::Info { contract_id } => { caller,
info(client, &contract_id, format).await args,
value,
} => {
estimate_gas(
client,
&contract_id,
&method,
&caller,
args.as_deref(),
value,
format,
)
.await
} }
ContractCommands::Info { contract_id } => info(client, &contract_id, format).await,
} }
} }
@ -52,23 +83,31 @@ async fn deploy(
let wasm_bytes = fs::read(&wasm_path)?; let wasm_bytes = fs::read(&wasm_path)?;
let wasm_hex = hex::encode(&wasm_bytes); let wasm_hex = hex::encode(&wasm_bytes);
output::print_info(&format!("Deploying contract ({} bytes)...", wasm_bytes.len())); output::print_info(&format!(
"Deploying contract ({} bytes)...",
wasm_bytes.len()
));
let args_hex = args.unwrap_or(""); let args_hex = args.unwrap_or("");
let spinner = output::create_spinner("Deploying contract..."); let spinner = output::create_spinner("Deploying contract...");
let result = client.deploy_contract(&wasm_hex, args_hex, deployer, gas_limit).await?; let result = client
.deploy_contract(&wasm_hex, args_hex, deployer, gas_limit)
.await?;
spinner.finish_and_clear(); spinner.finish_and_clear();
if let Some(error) = &result.error { if let Some(error) = &result.error {
match format { match format {
OutputFormat::Json => { OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({ println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"success": false, "success": false,
"error": error "error": error
}))?); }))?
);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_error(&format!("Deployment failed: {}", error)); output::print_error(&format!("Deployment failed: {}", error));
@ -79,12 +118,15 @@ async fn deploy(
match format { match format {
OutputFormat::Json => { OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({ println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"success": true, "success": true,
"contractId": result.contract_id, "contractId": result.contract_id,
"address": result.address, "address": result.address,
"gasUsed": result.gas_used, "gasUsed": result.gas_used,
}))?); }))?
);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_success("Contract deployed!"); output::print_success("Contract deployed!");
@ -110,15 +152,20 @@ async fn call(
) -> Result<()> { ) -> Result<()> {
let args_hex = args.unwrap_or(""); let args_hex = args.unwrap_or("");
let result = client.call_contract(contract_id, method, args_hex, caller, value, gas_limit).await?; let result = client
.call_contract(contract_id, method, args_hex, caller, value, gas_limit)
.await?;
if let Some(error) = &result.error { if let Some(error) = &result.error {
match format { match format {
OutputFormat::Json => { OutputFormat::Json => {
output::print_value(&serde_json::json!({ output::print_value(
&serde_json::json!({
"success": false, "success": false,
"error": error "error": error
}), format); }),
format,
);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_error(&format!("Contract call failed: {}", error)); output::print_error(&format!("Contract call failed: {}", error));
@ -129,12 +176,15 @@ async fn call(
match format { match format {
OutputFormat::Json => { OutputFormat::Json => {
output::print_value(&serde_json::json!({ output::print_value(
&serde_json::json!({
"success": result.success, "success": result.success,
"data": result.data, "data": result.data,
"gasUsed": result.gas_used, "gasUsed": result.gas_used,
"logs": result.logs "logs": result.logs
}), format); }),
format,
);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_header("Contract Call Result"); output::print_header("Contract Call Result");
@ -170,9 +220,12 @@ async fn code(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Re
if let Some(error) = &result.error { if let Some(error) = &result.error {
match format { match format {
OutputFormat::Json => { OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({ println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"error": error "error": error
}))?); }))?
);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_error(&format!("Failed to get code: {}", error)); output::print_error(&format!("Failed to get code: {}", error));
@ -208,15 +261,23 @@ async fn code(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Re
} }
/// Get contract storage. /// Get contract storage.
async fn storage(client: &RpcClient, contract_id: &str, key: &str, format: OutputFormat) -> Result<()> { async fn storage(
client: &RpcClient,
contract_id: &str,
key: &str,
format: OutputFormat,
) -> Result<()> {
let result = client.get_contract_storage(contract_id, key).await?; let result = client.get_contract_storage(contract_id, key).await?;
if let Some(error) = &result.error { if let Some(error) = &result.error {
match format { match format {
OutputFormat::Json => { OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({ println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"error": error "error": error
}))?); }))?
);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_error(&format!("Failed to get storage: {}", error)); output::print_error(&format!("Failed to get storage: {}", error));
@ -258,14 +319,19 @@ async fn estimate_gas(
) -> Result<()> { ) -> Result<()> {
let args_hex = args.unwrap_or(""); let args_hex = args.unwrap_or("");
let result = client.estimate_gas(contract_id, method, args_hex, caller, value).await?; let result = client
.estimate_gas(contract_id, method, args_hex, caller, value)
.await?;
if let Some(error) = &result.error { if let Some(error) = &result.error {
match format { match format {
OutputFormat::Json => { OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({ println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"error": error "error": error
}))?); }))?
);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_error(&format!("Failed to estimate gas: {}", error)); output::print_error(&format!("Failed to estimate gas: {}", error));
@ -300,9 +366,12 @@ async fn info(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Re
if let Some(error) = &result.error { if let Some(error) = &result.error {
match format { match format {
OutputFormat::Json => { OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({ println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"error": error "error": error
}))?); }))?
);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_error(&format!("Failed to get contract info: {}", error)); output::print_error(&format!("Failed to get contract info: {}", error));
@ -313,13 +382,16 @@ async fn info(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Re
match format { match format {
OutputFormat::Json => { OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({ println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"contractId": contract_id, "contractId": contract_id,
"codeHash": result.code_hash, "codeHash": result.code_hash,
"deployer": result.deployer, "deployer": result.deployer,
"deployedAt": result.deployed_at, "deployedAt": result.deployed_at,
"deployedHeight": result.deployed_height, "deployedHeight": result.deployed_height,
}))?); }))?
);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_header("Contract Info"); output::print_header("Contract Info");

View file

@ -53,7 +53,17 @@ pub async fn handle(
voter, voter,
choice, choice,
reason, reason,
} => vote(client, &proposal_id, &voter, &choice, reason.as_deref(), format).await, } => {
vote(
client,
&proposal_id,
&voter,
&choice,
reason.as_deref(),
format,
)
.await
}
GovernanceCommands::Execute { GovernanceCommands::Execute {
proposal_id, proposal_id,
executor, executor,
@ -75,15 +85,9 @@ async fn info(client: &RpcClient, format: OutputFormat) -> Result<()> {
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_header("Governance Info"); output::print_header("Governance Info");
output::print_kv( output::print_kv("Proposal Threshold", &format_synor(info.proposal_threshold));
"Proposal Threshold",
&format_synor(info.proposal_threshold),
);
output::print_kv("Quorum", &format!("{}%", info.quorum_bps as f64 / 100.0)); output::print_kv("Quorum", &format!("{}%", info.quorum_bps as f64 / 100.0));
output::print_kv( output::print_kv("Voting Period", &format_blocks(info.voting_period_blocks));
"Voting Period",
&format_blocks(info.voting_period_blocks),
);
output::print_kv( output::print_kv(
"Execution Delay", "Execution Delay",
&format_blocks(info.execution_delay_blocks), &format_blocks(info.execution_delay_blocks),
@ -125,11 +129,7 @@ async fn stats(client: &RpcClient, format: OutputFormat) -> Result<()> {
} }
/// List proposals. /// List proposals.
async fn proposals( async fn proposals(client: &RpcClient, state: Option<&str>, format: OutputFormat) -> Result<()> {
client: &RpcClient,
state: Option<&str>,
format: OutputFormat,
) -> Result<()> {
let proposals = match state { let proposals = match state {
Some(s) => client.get_proposals_by_state(s).await?, Some(s) => client.get_proposals_by_state(s).await?,
None => client.get_active_proposals().await?, None => client.get_active_proposals().await?,
@ -171,7 +171,11 @@ async fn proposals(
println!( println!(
" Participation: {:.2}% | Quorum: {}", " Participation: {:.2}% | Quorum: {}",
proposal.participation_rate, proposal.participation_rate,
if proposal.has_quorum { "Reached" } else { "Not reached" } if proposal.has_quorum {
"Reached"
} else {
"Not reached"
}
); );
if let Some(remaining) = proposal.time_remaining_blocks { if let Some(remaining) = proposal.time_remaining_blocks {
println!(" Time Remaining: {}", format_blocks(remaining)); println!(" Time Remaining: {}", format_blocks(remaining));
@ -197,10 +201,16 @@ async fn proposal(client: &RpcClient, id: &str, format: OutputFormat) -> Result<
println!("{}", serde_json::to_string_pretty(&proposal)?); println!("{}", serde_json::to_string_pretty(&proposal)?);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_header(&format!("Proposal #{}: {}", proposal.number, proposal.title)); output::print_header(&format!(
"Proposal #{}: {}",
proposal.number, proposal.title
));
println!(); println!();
output::print_kv("ID", &proposal.id); output::print_kv("ID", &proposal.id);
output::print_kv("State", &format!("{} {}", state_emoji(&proposal.state), proposal.state)); output::print_kv(
"State",
&format!("{} {}", state_emoji(&proposal.state), proposal.state),
);
output::print_kv("Type", &proposal.proposal_type); output::print_kv("Type", &proposal.proposal_type);
output::print_kv("Proposer", &proposal.proposer); output::print_kv("Proposer", &proposal.proposer);
println!(); println!();
@ -212,8 +222,14 @@ async fn proposal(client: &RpcClient, id: &str, format: OutputFormat) -> Result<
println!(); println!();
println!("Timeline:"); println!("Timeline:");
output::print_kv(" Created", &format!("Block {}", proposal.created_at_block)); output::print_kv(" Created", &format!("Block {}", proposal.created_at_block));
output::print_kv(" Voting Starts", &format!("Block {}", proposal.voting_starts_block)); output::print_kv(
output::print_kv(" Voting Ends", &format!("Block {}", proposal.voting_ends_block)); " Voting Starts",
&format!("Block {}", proposal.voting_starts_block),
);
output::print_kv(
" Voting Ends",
&format!("Block {}", proposal.voting_ends_block),
);
output::print_kv( output::print_kv(
" Execution Allowed", " Execution Allowed",
&format!("Block {}", proposal.execution_allowed_block), &format!("Block {}", proposal.execution_allowed_block),
@ -226,7 +242,10 @@ async fn proposal(client: &RpcClient, id: &str, format: OutputFormat) -> Result<
} else { } else {
0.0 0.0
}; };
output::print_kv(" Yes", &format!("{} ({:.1}%)", format_synor(proposal.yes_votes), yes_pct)); output::print_kv(
" Yes",
&format!("{} ({:.1}%)", format_synor(proposal.yes_votes), yes_pct),
);
output::print_kv(" No", &format_synor(proposal.no_votes)); output::print_kv(" No", &format_synor(proposal.no_votes));
output::print_kv(" Abstain", &format_synor(proposal.abstain_votes)); output::print_kv(" Abstain", &format_synor(proposal.abstain_votes));
output::print_kv(" Total Voters", &proposal.votes.len().to_string()); output::print_kv(" Total Voters", &proposal.votes.len().to_string());
@ -276,8 +295,10 @@ async fn create_proposal(
// Build proposal params based on type // Build proposal params based on type
let params = match proposal_type { let params = match proposal_type {
"treasury_spend" | "ecosystem_grant" => { "treasury_spend" | "ecosystem_grant" => {
let recipient = recipient.ok_or_else(|| anyhow::anyhow!("--recipient required for treasury proposals"))?; let recipient = recipient
let amount = amount.ok_or_else(|| anyhow::anyhow!("--amount required for treasury proposals"))?; .ok_or_else(|| anyhow::anyhow!("--recipient required for treasury proposals"))?;
let amount = amount
.ok_or_else(|| anyhow::anyhow!("--amount required for treasury proposals"))?;
json!({ json!({
"recipient": recipient, "recipient": recipient,
"amount": amount "amount": amount
@ -423,11 +444,18 @@ async fn treasury(client: &RpcClient, format: OutputFormat) -> Result<()> {
println!("Pools:"); println!("Pools:");
for pool in &pools { for pool in &pools {
println!(); println!();
let status = if pool.frozen { "🔒 FROZEN" } else { "✅ Active" }; let status = if pool.frozen {
"🔒 FROZEN"
} else {
"✅ Active"
};
println!(" {} [{}]", pool.name, status); println!(" {} [{}]", pool.name, status);
println!(" ID: {}", pool.id); println!(" ID: {}", pool.id);
println!(" Balance: {}", format_synor(pool.balance)); println!(" Balance: {}", format_synor(pool.balance));
println!(" Total Deposited: {}", format_synor(pool.total_deposited)); println!(
" Total Deposited: {}",
format_synor(pool.total_deposited)
);
println!(" Total Spent: {}", format_synor(pool.total_spent)); println!(" Total Spent: {}", format_synor(pool.total_spent));
} }
} }
@ -445,7 +473,11 @@ async fn treasury_pool(client: &RpcClient, id: &str, format: OutputFormat) -> Re
println!("{}", serde_json::to_string_pretty(&pool)?); println!("{}", serde_json::to_string_pretty(&pool)?);
} }
OutputFormat::Text => { OutputFormat::Text => {
let status = if pool.frozen { "🔒 FROZEN" } else { "✅ Active" }; let status = if pool.frozen {
"🔒 FROZEN"
} else {
"✅ Active"
};
output::print_header(&format!("{} [{}]", pool.name, status)); output::print_header(&format!("{} [{}]", pool.name, status));
output::print_kv("ID", &pool.id); output::print_kv("ID", &pool.id);
output::print_kv("Balance", &format_synor(pool.balance)); output::print_kv("Balance", &format_synor(pool.balance));
@ -466,7 +498,9 @@ fn format_synor(amount: u64) -> String {
if frac == 0 { if frac == 0 {
format!("{} SYNOR", whole) format!("{} SYNOR", whole)
} else { } else {
format!("{}.{:08} SYNOR", whole, frac).trim_end_matches('0').to_string() format!("{}.{:08} SYNOR", whole, frac)
.trim_end_matches('0')
.to_string()
} }
} }

View file

@ -7,11 +7,7 @@ use crate::output::{self, OutputFormat};
use crate::MiningCommands; use crate::MiningCommands;
/// Handle mining commands. /// Handle mining commands.
pub async fn handle( pub async fn handle(client: &RpcClient, cmd: MiningCommands, format: OutputFormat) -> Result<()> {
client: &RpcClient,
cmd: MiningCommands,
format: OutputFormat,
) -> Result<()> {
match cmd { match cmd {
MiningCommands::Info => info(client, format).await, MiningCommands::Info => info(client, format).await,
MiningCommands::Template { address } => template(client, &address, format).await, MiningCommands::Template { address } => template(client, &address, format).await,
@ -32,7 +28,10 @@ async fn info(client: &RpcClient, format: OutputFormat) -> Result<()> {
output::print_header("Mining Information"); output::print_header("Mining Information");
output::print_kv("Blocks", &info.blocks.to_string()); output::print_kv("Blocks", &info.blocks.to_string());
output::print_kv("Difficulty", &format!("{:.6}", info.difficulty)); output::print_kv("Difficulty", &format!("{:.6}", info.difficulty));
output::print_kv("Network Hashrate", &output::format_hashrate(info.network_hashrate)); output::print_kv(
"Network Hashrate",
&output::format_hashrate(info.network_hashrate),
);
if let Some(pool_hr) = info.pool_hashrate { if let Some(pool_hr) = info.pool_hashrate {
output::print_kv("Pool Hashrate", &output::format_hashrate(pool_hr)); output::print_kv("Pool Hashrate", &output::format_hashrate(pool_hr));
} }
@ -54,7 +53,10 @@ async fn template(client: &RpcClient, address: &str, format: OutputFormat) -> Re
output::print_header("Block Template"); output::print_header("Block Template");
output::print_kv("Version", &template.header.version.to_string()); output::print_kv("Version", &template.header.version.to_string());
output::print_kv("Parents", &template.header.parents.len().to_string()); output::print_kv("Parents", &template.header.parents.len().to_string());
output::print_kv("Timestamp", &output::format_timestamp(template.header.timestamp)); output::print_kv(
"Timestamp",
&output::format_timestamp(template.header.timestamp),
);
output::print_kv("Bits", &format!("0x{:08x}", template.header.bits)); output::print_kv("Bits", &format!("0x{:08x}", template.header.bits));
output::print_kv("Blue Score", &template.header.blue_score.to_string()); output::print_kv("Blue Score", &template.header.blue_score.to_string());
output::print_kv("Target", &output::format_hash(&template.target)); output::print_kv("Target", &output::format_hash(&template.target));
@ -105,7 +107,10 @@ async fn hashrate(client: &RpcClient, format: OutputFormat) -> Result<()> {
println!("{}", serde_json::to_string_pretty(&json)?); println!("{}", serde_json::to_string_pretty(&json)?);
} }
OutputFormat::Text => { OutputFormat::Text => {
output::print_kv("Network Hashrate", &output::format_hashrate(info.network_hashrate)); output::print_kv(
"Network Hashrate",
&output::format_hashrate(info.network_hashrate),
);
output::print_kv("Difficulty", &format!("{:.6}", info.difficulty)); output::print_kv("Difficulty", &format!("{:.6}", info.difficulty));
} }
} }

View file

@ -3,8 +3,8 @@
use anyhow::Result; use anyhow::Result;
use dialoguer::Password; use dialoguer::Password;
use synor_types::{ use synor_types::{
Address, Amount, Hash256,
transaction::{Outpoint, ScriptPubKey, ScriptType, Transaction, TxInput, TxOutput}, transaction::{Outpoint, ScriptPubKey, ScriptType, Transaction, TxInput, TxOutput},
Address, Amount, Hash256,
}; };
use crate::client::RpcClient; use crate::client::RpcClient;
@ -122,7 +122,15 @@ pub async fn send(
// Build transaction // Build transaction
let change = selected_amount - total_needed; let change = selected_amount - total_needed;
let tx_hex = build_transaction(&wallet, &selected_utxos, to, amount_sompi, &from_addr.address, change, &password)?; let tx_hex = build_transaction(
&wallet,
&selected_utxos,
to,
amount_sompi,
&from_addr.address,
change,
&password,
)?;
// Submit transaction // Submit transaction
let tx_hash = client.submit_transaction(&tx_hex).await?; let tx_hash = client.submit_transaction(&tx_hex).await?;
@ -219,8 +227,8 @@ fn build_transaction(
password: &str, password: &str,
) -> Result<String> { ) -> Result<String> {
// Parse destination address // Parse destination address
let to_address = Address::from_str(to) let to_address =
.map_err(|e| anyhow::anyhow!("Invalid destination address: {}", e))?; Address::from_str(to).map_err(|e| anyhow::anyhow!("Invalid destination address: {}", e))?;
// Parse change address // Parse change address
let change_address = Address::from_str(change_addr) let change_address = Address::from_str(change_addr)
@ -236,10 +244,7 @@ fn build_transaction(
let mut txid_array = [0u8; 32]; let mut txid_array = [0u8; 32];
txid_array.copy_from_slice(&txid_bytes); txid_array.copy_from_slice(&txid_bytes);
let outpoint = Outpoint::new( let outpoint = Outpoint::new(Hash256::from_bytes(txid_array), utxo.outpoint.index);
Hash256::from_bytes(txid_array),
utxo.outpoint.index,
);
// Empty signature script initially - will be filled after signing // Empty signature script initially - will be filled after signing
inputs.push(TxInput::new(outpoint, Vec::new())); inputs.push(TxInput::new(outpoint, Vec::new()));

View file

@ -12,11 +12,7 @@ use crate::wallet::Wallet;
use crate::WalletCommands; use crate::WalletCommands;
/// Handle wallet commands. /// Handle wallet commands.
pub async fn handle( pub async fn handle(config: &CliConfig, cmd: WalletCommands, format: OutputFormat) -> Result<()> {
config: &CliConfig,
cmd: WalletCommands,
format: OutputFormat,
) -> Result<()> {
match cmd { match cmd {
WalletCommands::Create { name } => create(config, &name, format).await, WalletCommands::Create { name } => create(config, &name, format).await,
WalletCommands::Import { name } => import(config, &name, format).await, WalletCommands::Import { name } => import(config, &name, format).await,
@ -72,7 +68,10 @@ async fn create(config: &CliConfig, name: &str, format: OutputFormat) -> Result<
output::print_kv("Network", &wallet.network); output::print_kv("Network", &wallet.network);
output::print_kv( output::print_kv(
"Address", "Address",
wallet.default_address().map(|a| a.address.as_str()).unwrap_or("none"), wallet
.default_address()
.map(|a| a.address.as_str())
.unwrap_or("none"),
); );
output::print_kv("Key Type", "Hybrid (Ed25519 + Dilithium)"); output::print_kv("Key Type", "Hybrid (Ed25519 + Dilithium)");
@ -134,7 +133,10 @@ async fn import(config: &CliConfig, name: &str, format: OutputFormat) -> Result<
output::print_kv("Name", &wallet.name); output::print_kv("Name", &wallet.name);
output::print_kv( output::print_kv(
"Address", "Address",
wallet.default_address().map(|a| a.address.as_str()).unwrap_or("none"), wallet
.default_address()
.map(|a| a.address.as_str())
.unwrap_or("none"),
); );
} }
} }
@ -162,8 +164,7 @@ async fn export(config: &CliConfig, name: &str, format: OutputFormat) -> Result<
// Note: We can't export the mnemonic from the derived seed // Note: We can't export the mnemonic from the derived seed
// The user should have written down the mnemonic during creation // The user should have written down the mnemonic during creation
match wallet.export_seed_phrase(&password) { match wallet.export_seed_phrase(&password) {
Ok(seed_phrase) => { Ok(seed_phrase) => match format {
match format {
OutputFormat::Json => { OutputFormat::Json => {
let result = serde_json::json!({ let result = serde_json::json!({
"name": wallet.name, "name": wallet.name,
@ -177,11 +178,12 @@ async fn export(config: &CliConfig, name: &str, format: OutputFormat) -> Result<
println!(" {}", seed_phrase); println!(" {}", seed_phrase);
println!(); println!();
} }
} },
}
Err(e) => { Err(e) => {
output::print_warning(&format!("{}", e)); output::print_warning(&format!("{}", e));
output::print_info("Please use the mnemonic phrase you wrote down during wallet creation."); output::print_info(
"Please use the mnemonic phrase you wrote down during wallet creation.",
);
} }
} }
@ -235,7 +237,10 @@ async fn info(config: &CliConfig, name: &str, format: OutputFormat) -> Result<()
output::print_kv("Network", &wallet.network); output::print_kv("Network", &wallet.network);
output::print_kv("Key Type", "Hybrid (Ed25519 + Dilithium)"); output::print_kv("Key Type", "Hybrid (Ed25519 + Dilithium)");
output::print_kv("Addresses", &wallet.addresses.len().to_string()); output::print_kv("Addresses", &wallet.addresses.len().to_string());
output::print_kv("Created", &output::format_timestamp(wallet.created_at * 1000)); output::print_kv(
"Created",
&output::format_timestamp(wallet.created_at * 1000),
);
if let Some(default) = wallet.default_address() { if let Some(default) = wallet.default_address() {
output::print_kv("Default Address", &default.address); output::print_kv("Default Address", &default.address);

View file

@ -23,7 +23,12 @@ use crate::config::CliConfig;
#[command(version, about = "Synor blockchain CLI", long_about = None)] #[command(version, about = "Synor blockchain CLI", long_about = None)]
struct Cli { struct Cli {
/// RPC server URL /// RPC server URL
#[arg(short, long, env = "SYNOR_RPC_URL", default_value = "http://127.0.0.1:16110")] #[arg(
short,
long,
env = "SYNOR_RPC_URL",
default_value = "http://127.0.0.1:16110"
)]
rpc: String, rpc: String,
/// Configuration file path /// Configuration file path
@ -472,39 +477,29 @@ async fn main() {
Commands::Balance { address } => { Commands::Balance { address } => {
commands::wallet::balance(&client, &config, address.as_deref(), output).await commands::wallet::balance(&client, &config, address.as_deref(), output).await
} }
Commands::Utxos { address } => { Commands::Utxos { address } => commands::wallet::utxos(&client, &address, output).await,
commands::wallet::utxos(&client, &address, output).await
}
// Address commands // Address commands
Commands::ValidateAddress { address } => { Commands::ValidateAddress { address } => {
commands::address::validate(&address, output).await commands::address::validate(&address, output).await
} }
Commands::DecodeAddress { address } => { Commands::DecodeAddress { address } => commands::address::decode(&address, output).await,
commands::address::decode(&address, output).await
}
// Mining commands // Mining commands
Commands::Mining(cmd) => commands::mining::handle(&client, cmd, output).await, Commands::Mining(cmd) => commands::mining::handle(&client, cmd, output).await,
// Contract commands // Contract commands
Commands::Contract(cmd) => { Commands::Contract(cmd) => commands::contract::handle(&client, &config, cmd, output).await,
commands::contract::handle(&client, &config, cmd, output).await
}
// Governance commands // Governance commands
Commands::Governance(cmd) => { Commands::Governance(cmd) => commands::governance::handle(&client, cmd, output).await,
commands::governance::handle(&client, cmd, output).await
}
// Network commands // Network commands
Commands::AddPeer { address } => { Commands::AddPeer { address } => {
commands::network::add_peer(&client, &address, output).await commands::network::add_peer(&client, &address, output).await
} }
Commands::BanPeer { peer } => commands::network::ban_peer(&client, &peer, output).await, Commands::BanPeer { peer } => commands::network::ban_peer(&client, &peer, output).await,
Commands::UnbanPeer { peer } => { Commands::UnbanPeer { peer } => commands::network::unban_peer(&client, &peer, output).await,
commands::network::unban_peer(&client, &peer, output).await
}
}; };
if let Err(e) = result { if let Err(e) = result {

View file

@ -190,7 +190,11 @@ impl Wallet {
} }
/// Generates a new address. /// Generates a new address.
pub fn new_address(&mut self, label: Option<String>, password: &str) -> anyhow::Result<&WalletAddress> { pub fn new_address(
&mut self,
label: Option<String>,
password: &str,
) -> anyhow::Result<&WalletAddress> {
let seed = self let seed = self
.encrypted_seed .encrypted_seed
.as_ref() .as_ref()

View file

@ -0,0 +1,3 @@
{
"type": "module"
}

View file

@ -12,6 +12,7 @@ use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use axum::http::{HeaderValue, Method};
use axum::{ use axum::{
extract::{Path, Query, State}, extract::{Path, Query, State},
http::StatusCode, http::StatusCode,
@ -21,10 +22,9 @@ use axum::{
}; };
use moka::future::Cache; use moka::future::Cache;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tower_http::cors::{Any, CorsLayer};
use axum::http::{HeaderValue, Method};
use tower_http::trace::TraceLayer;
use tower_http::compression::CompressionLayer; use tower_http::compression::CompressionLayer;
use tower_http::cors::{Any, CorsLayer};
use tower_http::trace::TraceLayer;
use tracing::{error, info}; use tracing::{error, info};
// ==================== Configuration ==================== // ==================== Configuration ====================
@ -297,8 +297,12 @@ pub struct PaginationParams {
pub limit: usize, pub limit: usize,
} }
fn default_page() -> usize { 1 } fn default_page() -> usize {
fn default_limit() -> usize { 25 } 1
}
fn default_limit() -> usize {
25
}
/// Paginated response. /// Paginated response.
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
@ -435,14 +439,19 @@ async fn health(State(state): State<Arc<ExplorerState>>) -> impl IntoResponse {
StatusCode::SERVICE_UNAVAILABLE StatusCode::SERVICE_UNAVAILABLE
}; };
(status, Json(Health { (
status,
Json(Health {
healthy: rpc_ok, healthy: rpc_ok,
rpc_connected: rpc_ok, rpc_connected: rpc_ok,
})) }),
)
} }
/// Get network statistics. /// Get network statistics.
async fn get_stats(State(state): State<Arc<ExplorerState>>) -> Result<Json<NetworkStats>, ApiError> { async fn get_stats(
State(state): State<Arc<ExplorerState>>,
) -> Result<Json<NetworkStats>, ApiError> {
// Check cache first // Check cache first
if let Some(stats) = state.stats_cache.get("network_stats").await { if let Some(stats) = state.stats_cache.get("network_stats").await {
return Ok(Json(stats)); return Ok(Json(stats));
@ -532,7 +541,10 @@ async fn get_stats(State(state): State<Arc<ExplorerState>>) -> Result<Json<Netwo
}; };
// Cache the result // Cache the result
state.stats_cache.insert("network_stats".to_string(), stats.clone()).await; state
.stats_cache
.insert("network_stats".to_string(), stats.clone())
.await;
Ok(Json(stats)) Ok(Json(stats))
} }
@ -559,7 +571,13 @@ async fn get_block(
} }
let rpc_block: synor_rpc::RpcBlock = state let rpc_block: synor_rpc::RpcBlock = state
.rpc_call("synor_getBlock", GetBlockParams { hash: hash.clone(), include_txs }) .rpc_call(
"synor_getBlock",
GetBlockParams {
hash: hash.clone(),
include_txs,
},
)
.await?; .await?;
let block = convert_rpc_block(rpc_block); let block = convert_rpc_block(rpc_block);
@ -710,9 +728,12 @@ async fn get_address(
} }
let utxos: Vec<synor_rpc::RpcUtxo> = state let utxos: Vec<synor_rpc::RpcUtxo> = state
.rpc_call("synor_getUtxosByAddresses", GetUtxosParams { .rpc_call(
"synor_getUtxosByAddresses",
GetUtxosParams {
addresses: vec![address.clone()], addresses: vec![address.clone()],
}) },
)
.await?; .await?;
// Get balance // Get balance
@ -727,9 +748,12 @@ async fn get_address(
} }
let balance: BalanceResult = state let balance: BalanceResult = state
.rpc_call("synor_getBalanceByAddress", GetBalanceParams { .rpc_call(
"synor_getBalanceByAddress",
GetBalanceParams {
address: address.clone(), address: address.clone(),
}) },
)
.await?; .await?;
let info = AddressInfo { let info = AddressInfo {
@ -756,9 +780,12 @@ async fn get_address_utxos(
} }
let utxos: Vec<synor_rpc::RpcUtxo> = state let utxos: Vec<synor_rpc::RpcUtxo> = state
.rpc_call("synor_getUtxosByAddresses", GetUtxosParams { .rpc_call(
"synor_getUtxosByAddresses",
GetUtxosParams {
addresses: vec![address], addresses: vec![address],
}) },
)
.await?; .await?;
Ok(Json(utxos)) Ok(Json(utxos))
@ -852,10 +879,13 @@ async fn get_mempool(
} }
let entries: Vec<synor_rpc::RpcMempoolEntry> = state let entries: Vec<synor_rpc::RpcMempoolEntry> = state
.rpc_call("synor_getMempoolEntries", GetMempoolParams { .rpc_call(
"synor_getMempoolEntries",
GetMempoolParams {
include_orphan_pool: false, include_orphan_pool: false,
filter_tx_in_addresses: false, filter_tx_in_addresses: false,
}) },
)
.await?; .await?;
let total = entries.len(); let total = entries.len();
@ -912,10 +942,13 @@ async fn search(
} }
let block_result: Result<synor_rpc::RpcBlock, _> = state let block_result: Result<synor_rpc::RpcBlock, _> = state
.rpc_call("synor_getBlock", GetBlockParams { .rpc_call(
"synor_getBlock",
GetBlockParams {
hash: query.to_string(), hash: query.to_string(),
include_txs: false, include_txs: false,
}) },
)
.await; .await;
if block_result.is_ok() { if block_result.is_ok() {
@ -933,9 +966,12 @@ async fn search(
} }
let tx_result: Result<synor_rpc::RpcTransaction, _> = state let tx_result: Result<synor_rpc::RpcTransaction, _> = state
.rpc_call("synor_getTransaction", GetTxParams { .rpc_call(
"synor_getTransaction",
GetTxParams {
tx_id: query.to_string(), tx_id: query.to_string(),
}) },
)
.await; .await;
if tx_result.is_ok() { if tx_result.is_ok() {
@ -984,9 +1020,15 @@ fn convert_rpc_block(rpc: synor_rpc::RpcBlock) -> ExplorerBlock {
.map(convert_rpc_transaction) .map(convert_rpc_transaction)
.collect(), .collect(),
), ),
children_hashes: verbose.map(|v| v.children_hashes.clone()).unwrap_or_default(), children_hashes: verbose
merge_set_blues: verbose.map(|v| v.merge_set_blues_hashes.clone()).unwrap_or_default(), .map(|v| v.children_hashes.clone())
merge_set_reds: verbose.map(|v| v.merge_set_reds_hashes.clone()).unwrap_or_default(), .unwrap_or_default(),
merge_set_blues: verbose
.map(|v| v.merge_set_blues_hashes.clone())
.unwrap_or_default(),
merge_set_reds: verbose
.map(|v| v.merge_set_reds_hashes.clone())
.unwrap_or_default(),
} }
} }
@ -995,7 +1037,11 @@ fn convert_rpc_transaction(rpc: synor_rpc::RpcTransaction) -> ExplorerTransactio
let verbose = rpc.verbose_data.as_ref(); let verbose = rpc.verbose_data.as_ref();
let is_coinbase = rpc.inputs.is_empty() let is_coinbase = rpc.inputs.is_empty()
|| rpc.inputs.first().map(|i| i.previous_outpoint.transaction_id.chars().all(|c| c == '0')).unwrap_or(false); || rpc
.inputs
.first()
.map(|i| i.previous_outpoint.transaction_id.chars().all(|c| c == '0'))
.unwrap_or(false);
let total_output: u64 = rpc.outputs.iter().map(|o| o.value).sum(); let total_output: u64 = rpc.outputs.iter().map(|o| o.value).sum();
let total_input: u64 = rpc let total_input: u64 = rpc
@ -1004,10 +1050,17 @@ fn convert_rpc_transaction(rpc: synor_rpc::RpcTransaction) -> ExplorerTransactio
.filter_map(|i| i.verbose_data.as_ref().map(|v| v.value)) .filter_map(|i| i.verbose_data.as_ref().map(|v| v.value))
.sum(); .sum();
let fee = if is_coinbase { 0 } else { total_input.saturating_sub(total_output) }; let fee = if is_coinbase {
0
} else {
total_input.saturating_sub(total_output)
};
ExplorerTransaction { ExplorerTransaction {
id: verbose.as_ref().map(|v| v.transaction_id.clone()).unwrap_or_default(), id: verbose
.as_ref()
.map(|v| v.transaction_id.clone())
.unwrap_or_default(),
hash: verbose.as_ref().map(|v| v.hash.clone()).unwrap_or_default(), hash: verbose.as_ref().map(|v| v.hash.clone()).unwrap_or_default(),
version: rpc.version, version: rpc.version,
inputs: rpc inputs: rpc

View file

@ -8,6 +8,7 @@ use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use axum::http::{HeaderValue, Method};
use axum::{ use axum::{
extract::{ConnectInfo, State}, extract::{ConnectInfo, State},
http::StatusCode, http::StatusCode,
@ -15,11 +16,10 @@ use axum::{
routing::{get, post}, routing::{get, post},
Json, Router, Json, Router,
}; };
use governor::{Quota, RateLimiter, state::keyed::DashMapStateStore}; use governor::{state::keyed::DashMapStateStore, Quota, RateLimiter};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tower_http::cors::{Any, CorsLayer}; use tower_http::cors::{Any, CorsLayer};
use axum::http::{HeaderValue, Method};
use tower_http::trace::TraceLayer; use tower_http::trace::TraceLayer;
use tracing::{info, warn}; use tracing::{info, warn};
@ -210,7 +210,8 @@ async fn main() -> anyhow::Result<()> {
// Create rate limiter (using NonZeroU32 for quota) // Create rate limiter (using NonZeroU32 for quota)
let quota = Quota::per_minute( let quota = Quota::per_minute(
std::num::NonZeroU32::new(config.rate_limit_per_minute).unwrap_or(std::num::NonZeroU32::new(10).unwrap()) std::num::NonZeroU32::new(config.rate_limit_per_minute)
.unwrap_or(std::num::NonZeroU32::new(10).unwrap()),
); );
let rate_limiter = RateLimiter::keyed(quota); let rate_limiter = RateLimiter::keyed(quota);
@ -639,10 +640,7 @@ async fn send_tokens(state: &FaucetState, address: &str) -> anyhow::Result<Optio
if !response.status().is_success() { if !response.status().is_success() {
// For testnet demo, simulate success // For testnet demo, simulate success
// In production, this would be a real error // In production, this would be a real error
return Ok(Some(format!( return Ok(Some(format!("0x{}", hex::encode(&rand_bytes()))));
"0x{}",
hex::encode(&rand_bytes())
)));
} }
let rpc_response: RpcResponse = response.json().await?; let rpc_response: RpcResponse = response.json().await?;

View file

@ -35,7 +35,11 @@ pub fn confirm(prompt: &str) -> bool {
/// Formats a hash for display. /// Formats a hash for display.
pub fn format_hash(hash: &[u8]) -> String { pub fn format_hash(hash: &[u8]) -> String {
if hash.len() >= 8 { if hash.len() >= 8 {
format!("{}...{}", hex::encode(&hash[..4]), hex::encode(&hash[hash.len() - 4..])) format!(
"{}...{}",
hex::encode(&hash[..4]),
hex::encode(&hash[hash.len() - 4..])
)
} else { } else {
hex::encode(hash) hex::encode(hash)
} }

View file

@ -115,12 +115,7 @@ impl NodeConfig {
} }
/// Sets mining configuration. /// Sets mining configuration.
pub fn with_mining( pub fn with_mining(mut self, enabled: bool, coinbase: Option<String>, threads: usize) -> Self {
mut self,
enabled: bool,
coinbase: Option<String>,
threads: usize,
) -> Self {
if enabled { if enabled {
self.mining.enabled = true; self.mining.enabled = true;
} }

View file

@ -210,19 +210,14 @@ async fn main() {
fn init_logging(level: &str, json: bool) { fn init_logging(level: &str, json: bool) {
use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use tracing_subscriber::{fmt, prelude::*, EnvFilter};
let filter = EnvFilter::try_from_default_env() let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level));
.unwrap_or_else(|_| EnvFilter::new(level));
let subscriber = tracing_subscriber::registry().with(filter); let subscriber = tracing_subscriber::registry().with(filter);
if json { if json {
subscriber subscriber.with(fmt::layer().json()).init();
.with(fmt::layer().json())
.init();
} else { } else {
subscriber subscriber.with(fmt::layer().with_target(true)).init();
.with(fmt::layer().with_target(true))
.init();
} }
} }
@ -277,11 +272,7 @@ async fn run_node(
} }
/// Initialize a new node with genesis block. /// Initialize a new node with genesis block.
async fn init_node( async fn init_node(data_dir: Option<PathBuf>, network: String, force: bool) -> anyhow::Result<()> {
data_dir: Option<PathBuf>,
network: String,
force: bool,
) -> anyhow::Result<()> {
use synor_consensus::genesis::ChainConfig; use synor_consensus::genesis::ChainConfig;
use synor_storage::{BlockBody, ChainState}; use synor_storage::{BlockBody, ChainState};
use synor_types::{BlockId, Network}; use synor_types::{BlockId, Network};
@ -302,7 +293,10 @@ async fn init_node(
"mainnet" => Network::Mainnet, "mainnet" => Network::Mainnet,
"testnet" => Network::Testnet, "testnet" => Network::Testnet,
"devnet" => Network::Devnet, "devnet" => Network::Devnet,
_ => anyhow::bail!("Unknown network: {}. Use mainnet, testnet, or devnet.", network), _ => anyhow::bail!(
"Unknown network: {}. Use mainnet, testnet, or devnet.",
network
),
}; };
info!(network = %network, "Initializing node..."); info!(network = %network, "Initializing node...");
@ -323,8 +317,7 @@ async fn init_node(
std::fs::create_dir_all(data_dir.join("keys"))?; std::fs::create_dir_all(data_dir.join("keys"))?;
// Create and save node config // Create and save node config
let config = NodeConfig::for_network(&network)? let config = NodeConfig::for_network(&network)?.with_data_dir(Some(data_dir.clone()));
.with_data_dir(Some(data_dir.clone()));
let config_path = data_dir.join("synord.toml"); let config_path = data_dir.join("synord.toml");
config.save(&config_path)?; config.save(&config_path)?;
@ -343,7 +336,10 @@ async fn init_node(
// Store genesis block body // Store genesis block body
let genesis_hash = chain_config.genesis_hash; let genesis_hash = chain_config.genesis_hash;
let body = BlockBody { let body = BlockBody {
transaction_ids: chain_config.genesis.body.transactions transaction_ids: chain_config
.genesis
.body
.transactions
.iter() .iter()
.map(|tx| tx.txid()) .map(|tx| tx.txid())
.collect(), .collect(),
@ -401,10 +397,19 @@ async fn init_node(
println!(" Genesis: {}", hex::encode(genesis_hash.as_bytes())); println!(" Genesis: {}", hex::encode(genesis_hash.as_bytes()));
println!(); println!();
println!("Chain parameters:"); println!("Chain parameters:");
println!(" Block time: {} ms", chain_config.target_block_time_ms); println!(
" Block time: {} ms",
chain_config.target_block_time_ms
);
println!(" GHOSTDAG K: {}", chain_config.ghostdag_k); println!(" GHOSTDAG K: {}", chain_config.ghostdag_k);
println!(" Initial reward: {} SYNOR", chain_config.initial_reward / 100_000_000); println!(
println!(" Halving interval: {} blocks", chain_config.halving_interval); " Initial reward: {} SYNOR",
chain_config.initial_reward / 100_000_000
);
println!(
" Halving interval: {} blocks",
chain_config.halving_interval
);
println!(); println!();
println!("To start the node:"); println!("To start the node:");
println!(" synord run --network {}", network); println!(" synord run --network {}", network);
@ -481,7 +486,10 @@ async fn import_blocks(
// Store the block // Store the block
if let Err(e) = storage.put_block(&block_data).await { if let Err(e) = storage.put_block(&block_data).await {
error!(hash = hex::encode(&block_data.hash[..8]), "Failed to store block: {}", e); error!(
hash = hex::encode(&block_data.hash[..8]),
"Failed to store block: {}", e
);
errors += 1; errors += 1;
} else { } else {
imported += 1; imported += 1;
@ -656,7 +664,9 @@ async fn wait_for_shutdown() {
#[cfg(windows)] #[cfg(windows)]
{ {
tokio::signal::ctrl_c().await.expect("Failed to listen for Ctrl+C"); tokio::signal::ctrl_c()
.await
.expect("Failed to listen for Ctrl+C");
info!("Received Ctrl+C"); info!("Received Ctrl+C");
} }
} }

View file

@ -207,10 +207,7 @@ impl SynorNode {
// Start miner if enabled // Start miner if enabled
if let Some(ref miner) = self.miner { if let Some(ref miner) = self.miner {
miner.start().await?; miner.start().await?;
info!( info!(threads = self.config.mining.threads, "Miner started");
threads = self.config.mining.threads,
"Miner started"
);
} }
// Update state // Update state

View file

@ -7,8 +7,8 @@ use tokio::sync::{broadcast, RwLock};
use tracing::{debug, info}; use tracing::{debug, info};
use synor_consensus::{ use synor_consensus::{
BlockValidator, DaaParams, DifficultyManager, RewardCalculator, BlockValidator, DaaParams, DifficultyManager, RewardCalculator, TransactionValidator, UtxoSet,
TransactionValidator, UtxoSet, ValidationError, ValidationError,
}; };
use synor_types::{ use synor_types::{
block::{Block, BlockHeader}, block::{Block, BlockHeader},
@ -288,10 +288,15 @@ impl ConsensusService {
} }
// Calculate expected reward // Calculate expected reward
let expected_reward = self.reward_calculator.calculate_subsidy(block.header.daa_score); let expected_reward = self
.reward_calculator
.calculate_subsidy(block.header.daa_score);
// Validate the full block (including transactions) // Validate the full block (including transactions)
if let Err(e) = self.block_validator.validate_block(block, &self.utxo_set, expected_reward) { if let Err(e) = self
.block_validator
.validate_block(block, &self.utxo_set, expected_reward)
{
return BlockValidation::Invalid { return BlockValidation::Invalid {
reason: format!("Invalid block: {}", e), reason: format!("Invalid block: {}", e),
}; };
@ -442,7 +447,10 @@ impl ConsensusService {
// For non-coinbase transactions, validate against UTXO set // For non-coinbase transactions, validate against UTXO set
if !tx.is_coinbase() { if !tx.is_coinbase() {
let current_daa = *self.daa_score.read().await; let current_daa = *self.daa_score.read().await;
if let Err(e) = self.tx_validator.validate_against_utxos(tx, &self.utxo_set, current_daa) { if let Err(e) =
self.tx_validator
.validate_against_utxos(tx, &self.utxo_set, current_daa)
{
// Check if this is a double-spend conflict // Check if this is a double-spend conflict
if matches!(e, ValidationError::UtxoNotFound(_)) { if matches!(e, ValidationError::UtxoNotFound(_)) {
return TxValidation::Conflict; return TxValidation::Conflict;

View file

@ -247,7 +247,8 @@ impl ContractService {
call_data.extend_from_slice(&args); call_data.extend_from_slice(&args);
// Create execution context // Create execution context
let call_context = CallContext::new(vm_contract_id, caller.clone(), value, call_data.clone()); let call_context =
CallContext::new(vm_contract_id, caller.clone(), value, call_data.clone());
let context = ExecutionContext::new( let context = ExecutionContext::new(
synor_vm::context::BlockInfo { synor_vm::context::BlockInfo {
@ -353,7 +354,10 @@ impl ContractService {
} }
/// Gets contract metadata. /// Gets contract metadata.
pub async fn get_contract(&self, contract_id: &[u8; 32]) -> anyhow::Result<Option<StoredContract>> { pub async fn get_contract(
&self,
contract_id: &[u8; 32],
) -> anyhow::Result<Option<StoredContract>> {
let store = self.contract_store.read().await; let store = self.contract_store.read().await;
let store = store let store = store
.as_ref() .as_ref()

View file

@ -8,8 +8,8 @@ use tokio::sync::{broadcast, RwLock};
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use synor_governance::{ use synor_governance::{
DaoStats, GovernanceConfig, Proposal, ProposalId, ProposalState, ProposalSummary, DaoStats, GovernanceConfig, Proposal, ProposalId, ProposalState, ProposalSummary, ProposalType,
ProposalType, Treasury, TreasuryPoolId, VoteChoice, VotingConfig, DAO, Treasury, TreasuryPoolId, VoteChoice, VotingConfig, DAO,
}; };
use synor_types::Address; use synor_types::Address;
@ -247,7 +247,10 @@ impl GovernanceService {
} }
let current_block = state.current_block; let current_block = state.current_block;
let proposal = state.dao.execute(proposal_id, executor, current_block)?.clone(); let proposal = state
.dao
.execute(proposal_id, executor, current_block)?
.clone();
info!( info!(
proposal_id = %hex::encode(proposal_id.as_bytes()), proposal_id = %hex::encode(proposal_id.as_bytes()),
@ -256,7 +259,8 @@ impl GovernanceService {
); );
// Handle proposal execution based on type // Handle proposal execution based on type
self.handle_proposal_execution(&proposal, &mut state).await?; self.handle_proposal_execution(&proposal, &mut state)
.await?;
Ok(proposal) Ok(proposal)
} }
@ -268,7 +272,11 @@ impl GovernanceService {
state: &mut GovernanceState, state: &mut GovernanceState,
) -> Result<(), GovernanceError> { ) -> Result<(), GovernanceError> {
match &proposal.proposal_type { match &proposal.proposal_type {
ProposalType::TreasurySpend { recipient, amount, reason } => { ProposalType::TreasurySpend {
recipient,
amount,
reason,
} => {
info!( info!(
recipient = %recipient, recipient = %recipient,
amount = amount, amount = amount,
@ -278,7 +286,11 @@ impl GovernanceService {
// In production, this would create a spending request // In production, this would create a spending request
// For now, we log the action // For now, we log the action
} }
ProposalType::ParameterChange { parameter, old_value, new_value } => { ProposalType::ParameterChange {
parameter,
old_value,
new_value,
} => {
info!( info!(
parameter = %parameter, parameter = %parameter,
old_value = %old_value, old_value = %old_value,
@ -287,7 +299,11 @@ impl GovernanceService {
); );
// In production, this would update chain parameters // In production, this would update chain parameters
} }
ProposalType::CouncilChange { action, member, role } => { ProposalType::CouncilChange {
action,
member,
role,
} => {
info!( info!(
action = ?action, action = ?action,
member = %member, member = %member,
@ -341,7 +357,10 @@ impl GovernanceService {
} }
/// Gets a proposal by ID. /// Gets a proposal by ID.
pub async fn get_proposal(&self, proposal_id: &ProposalId) -> Result<Proposal, GovernanceError> { pub async fn get_proposal(
&self,
proposal_id: &ProposalId,
) -> Result<Proposal, GovernanceError> {
let state = self.state.read().await; let state = self.state.read().await;
if !state.initialized { if !state.initialized {

View file

@ -104,7 +104,8 @@ impl MempoolService {
let mut block_rx = self.consensus.subscribe_blocks(); let mut block_rx = self.consensus.subscribe_blocks();
let handle = tokio::spawn(async move { let handle = tokio::spawn(async move {
let mut cleanup_interval = tokio::time::interval(Duration::from_secs(CLEANUP_INTERVAL_SECS)); let mut cleanup_interval =
tokio::time::interval(Duration::from_secs(CLEANUP_INTERVAL_SECS));
cleanup_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); cleanup_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
info!("Mempool cleanup task started"); info!("Mempool cleanup task started");
@ -232,7 +233,10 @@ impl MempoolService {
*current_size += tx_size; *current_size += tx_size;
} }
debug!(hash = hex::encode(&hash[..8]), "Transaction added to mempool"); debug!(
hash = hex::encode(&hash[..8]),
"Transaction added to mempool"
);
let _ = self.tx_added.send(hash); let _ = self.tx_added.send(hash);
Ok(()) Ok(())
@ -245,7 +249,10 @@ impl MempoolService {
let mut current_size = self.current_size.write().await; let mut current_size = self.current_size.write().await;
*current_size = current_size.saturating_sub(tx.data.len()); *current_size = current_size.saturating_sub(tx.data.len());
debug!(hash = hex::encode(&hash[..8]), "Transaction removed from mempool"); debug!(
hash = hex::encode(&hash[..8]),
"Transaction removed from mempool"
);
let _ = self.tx_removed.send(*hash); let _ = self.tx_removed.send(*hash);
Some(tx) Some(tx)
@ -285,7 +292,11 @@ impl MempoolService {
// Sort by fee rate descending // Sort by fee rate descending
let mut sorted: Vec<_> = txs.values().cloned().collect(); let mut sorted: Vec<_> = txs.values().cloned().collect();
sorted.sort_by(|a, b| b.fee_rate.partial_cmp(&a.fee_rate).unwrap_or(std::cmp::Ordering::Equal)); sorted.sort_by(|a, b| {
b.fee_rate
.partial_cmp(&a.fee_rate)
.unwrap_or(std::cmp::Ordering::Equal)
});
// Select transactions up to max mass // Select transactions up to max mass
let mut selected = Vec::new(); let mut selected = Vec::new();
@ -308,7 +319,11 @@ impl MempoolService {
// Sort by fee rate ascending (evict lowest first) // Sort by fee rate ascending (evict lowest first)
let mut sorted: Vec<_> = txs.values().cloned().collect(); let mut sorted: Vec<_> = txs.values().cloned().collect();
sorted.sort_by(|a, b| a.fee_rate.partial_cmp(&b.fee_rate).unwrap_or(std::cmp::Ordering::Equal)); sorted.sort_by(|a, b| {
a.fee_rate
.partial_cmp(&b.fee_rate)
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut freed = 0usize; let mut freed = 0usize;
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();

View file

@ -7,7 +7,9 @@ use tokio::sync::{broadcast, mpsc, RwLock};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use synor_mining::{ use synor_mining::{
BlockMiner, BlockTemplate as MiningBlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, MinerCommand, MinerConfig, MinerEvent, MiningResult, MiningStats as CrateMiningStats, TemplateTransaction, BlockMiner, BlockTemplate as MiningBlockTemplate, BlockTemplateBuilder, CoinbaseBuilder,
MinerCommand, MinerConfig, MinerEvent, MiningResult, MiningStats as CrateMiningStats,
TemplateTransaction,
}; };
use synor_types::{Address, Hash256, Network}; use synor_types::{Address, Hash256, Network};
@ -118,9 +120,11 @@ impl MinerService {
}; };
// Parse coinbase address if provided // Parse coinbase address if provided
let coinbase_address = config.mining.coinbase_address.as_ref().and_then(|addr_str| { let coinbase_address = config
addr_str.parse::<Address>().ok() .mining
}); .coinbase_address
.as_ref()
.and_then(|addr_str| addr_str.parse::<Address>().ok());
// Determine network from config // Determine network from config
let network = match config.network.as_str() { let network = match config.network.as_str() {
@ -261,7 +265,10 @@ impl MinerService {
/// Updates the mining template. /// Updates the mining template.
async fn update_template(&self) -> anyhow::Result<()> { async fn update_template(&self) -> anyhow::Result<()> {
let template = self.build_template().await?; let template = self.build_template().await?;
let _ = self.cmd_tx.send(MinerCommand::NewTemplate(Arc::new(template))).await; let _ = self
.cmd_tx
.send(MinerCommand::NewTemplate(Arc::new(template)))
.await;
Ok(()) Ok(())
} }
@ -301,12 +308,16 @@ impl MinerService {
/// Sets coinbase address. /// Sets coinbase address.
pub async fn set_coinbase_address(&self, address: String) -> anyhow::Result<()> { pub async fn set_coinbase_address(&self, address: String) -> anyhow::Result<()> {
let parsed: Address = address.parse() let parsed: Address = address
.parse()
.map_err(|e| anyhow::anyhow!("Invalid address: {}", e))?; .map_err(|e| anyhow::anyhow!("Invalid address: {}", e))?;
// Update miner config // Update miner config
let new_config = MinerConfig::solo(parsed, self.threads); let new_config = MinerConfig::solo(parsed, self.threads);
let _ = self.cmd_tx.send(MinerCommand::UpdateConfig(new_config)).await; let _ = self
.cmd_tx
.send(MinerCommand::UpdateConfig(new_config))
.await;
info!(address = %address, "Updated coinbase address"); info!(address = %address, "Updated coinbase address");
Ok(()) Ok(())
@ -314,7 +325,9 @@ impl MinerService {
/// Builds a block template for mining. /// Builds a block template for mining.
async fn build_template(&self) -> anyhow::Result<MiningBlockTemplate> { async fn build_template(&self) -> anyhow::Result<MiningBlockTemplate> {
let coinbase_address = self.coinbase_address.clone() let coinbase_address = self
.coinbase_address
.clone()
.ok_or_else(|| anyhow::anyhow!("No coinbase address set"))?; .ok_or_else(|| anyhow::anyhow!("No coinbase address set"))?;
// Get transactions from mempool // Get transactions from mempool
@ -363,7 +376,8 @@ impl MinerService {
builder = builder.add_transaction(template_tx); builder = builder.add_transaction(template_tx);
} }
let template = builder.build(template_id) let template = builder
.build(template_id)
.map_err(|e| anyhow::anyhow!("Failed to build template: {}", e))?; .map_err(|e| anyhow::anyhow!("Failed to build template: {}", e))?;
debug!( debug!(
@ -416,7 +430,9 @@ impl MinerService {
); );
// Get the template that was mined // Get the template that was mined
let template = self.miner.current_template() let template = self
.miner
.current_template()
.ok_or_else(|| anyhow::anyhow!("No current template"))?; .ok_or_else(|| anyhow::anyhow!("No current template"))?;
// Build full block from template and mining result // Build full block from template and mining result
@ -493,7 +509,9 @@ impl MinerService {
// Get hash from block header for notification // Get hash from block header for notification
let hash = if block.len() >= 32 { let hash = if block.len() >= 32 {
let mut h = [0u8; 32]; let mut h = [0u8; 32];
h.copy_from_slice(&blake3::hash(&block[..96.min(block.len())]).as_bytes()[..32]); h.copy_from_slice(
&blake3::hash(&block[..96.min(block.len())]).as_bytes()[..32],
);
h h
} else { } else {
[0u8; 32] [0u8; 32]

View file

@ -50,7 +50,10 @@ pub enum NetworkMessage {
/// Block response. /// Block response.
Blocks { data: Vec<Vec<u8>> }, Blocks { data: Vec<Vec<u8>> },
/// Headers request. /// Headers request.
GetHeaders { locator: Vec<[u8; 32]>, stop: [u8; 32] }, GetHeaders {
locator: Vec<[u8; 32]>,
stop: [u8; 32],
},
/// Headers response. /// Headers response.
Headers { headers: Vec<Vec<u8>> }, Headers { headers: Vec<Vec<u8>> },
} }
@ -112,11 +115,11 @@ impl NetworkService {
}; };
// Parse listen address // Parse listen address
let listen_addr_parsed: Multiaddr = config let listen_addr_parsed: Multiaddr = config.p2p.listen_addr.parse().unwrap_or_else(|_| {
.p2p format!("/ip4/0.0.0.0/tcp/{}", synor_network::DEFAULT_PORT)
.listen_addr
.parse() .parse()
.unwrap_or_else(|_| format!("/ip4/0.0.0.0/tcp/{}", synor_network::DEFAULT_PORT).parse().unwrap()); .unwrap()
});
// Parse seed/bootstrap peers // Parse seed/bootstrap peers
let bootstrap_peers: Vec<Multiaddr> = config let bootstrap_peers: Vec<Multiaddr> = config
@ -364,7 +367,7 @@ impl NetworkService {
} }
NetworkMessage::TxAnnounce { hash } => { NetworkMessage::TxAnnounce { hash } => {
let announcement = TransactionAnnouncement::id_only( let announcement = TransactionAnnouncement::id_only(
synor_types::TransactionId::from_bytes(hash) synor_types::TransactionId::from_bytes(hash),
); );
if let Err(e) = handle.broadcast_transaction(announcement).await { if let Err(e) = handle.broadcast_transaction(announcement).await {
warn!("Failed to broadcast transaction: {}", e); warn!("Failed to broadcast transaction: {}", e);
@ -406,11 +409,7 @@ impl NetworkService {
} }
/// Requests blocks from a peer. /// Requests blocks from a peer.
pub async fn request_blocks( pub async fn request_blocks(&self, peer_id: &str, hashes: Vec<[u8; 32]>) -> anyhow::Result<()> {
&self,
peer_id: &str,
hashes: Vec<[u8; 32]>,
) -> anyhow::Result<()> {
let handle_guard = self.handle.read().await; let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard { if let Some(ref handle) = *handle_guard {
let peer: PeerId = peer_id let peer: PeerId = peer_id

View file

@ -9,7 +9,7 @@ use tokio::sync::RwLock;
use tracing::{info, warn}; use tracing::{info, warn};
use synor_network::SyncState; use synor_network::SyncState;
use synor_types::{BlockHeader, block::BlockBody}; use synor_types::{block::BlockBody, BlockHeader};
use crate::config::NodeConfig; use crate::config::NodeConfig;
use crate::services::{ use crate::services::{
@ -109,7 +109,9 @@ impl RpcService {
// Start HTTP server // Start HTTP server
if self.http_enabled { if self.http_enabled {
let http_addr: SocketAddr = self.http_addr.parse() let http_addr: SocketAddr = self
.http_addr
.parse()
.map_err(|e| anyhow::anyhow!("Invalid HTTP address: {}", e))?; .map_err(|e| anyhow::anyhow!("Invalid HTTP address: {}", e))?;
info!(addr = %http_addr, "Starting HTTP RPC server"); info!(addr = %http_addr, "Starting HTTP RPC server");
@ -119,7 +121,8 @@ impl RpcService {
.await .await
.map_err(|e| anyhow::anyhow!("Failed to start HTTP server: {}", e))?; .map_err(|e| anyhow::anyhow!("Failed to start HTTP server: {}", e))?;
let local_addr = server.local_addr() let local_addr = server
.local_addr()
.map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?; .map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?;
info!(addr = %local_addr, "HTTP RPC server started"); info!(addr = %local_addr, "HTTP RPC server started");
@ -129,7 +132,9 @@ impl RpcService {
// Start WebSocket server // Start WebSocket server
if self.ws_enabled { if self.ws_enabled {
let ws_addr: SocketAddr = self.ws_addr.parse() let ws_addr: SocketAddr = self
.ws_addr
.parse()
.map_err(|e| anyhow::anyhow!("Invalid WebSocket address: {}", e))?; .map_err(|e| anyhow::anyhow!("Invalid WebSocket address: {}", e))?;
info!(addr = %ws_addr, "Starting WebSocket RPC server"); info!(addr = %ws_addr, "Starting WebSocket RPC server");
@ -139,7 +144,8 @@ impl RpcService {
.await .await
.map_err(|e| anyhow::anyhow!("Failed to start WebSocket server: {}", e))?; .map_err(|e| anyhow::anyhow!("Failed to start WebSocket server: {}", e))?;
let local_addr = server.local_addr() let local_addr = server
.local_addr()
.map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?; .map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?;
info!(addr = %local_addr, "WebSocket RPC server started"); info!(addr = %local_addr, "WebSocket RPC server started");
@ -319,7 +325,10 @@ impl RpcService {
let mempool_size = ctx.mempool.count().await; let mempool_size = ctx.mempool.count().await;
// Check actual sync status from network service // Check actual sync status from network service
let synced = ctx.network.sync_status().await let synced = ctx
.network
.sync_status()
.await
.map(|status| matches!(status.state, SyncState::Synced | SyncState::Idle)) .map(|status| matches!(status.state, SyncState::Synced | SyncState::Idle))
.unwrap_or(false); .unwrap_or(false);
@ -343,7 +352,9 @@ impl RpcService {
// synor_getPeerInfo // synor_getPeerInfo
module.register_async_method("synor_getPeerInfo", |_, ctx| async move { module.register_async_method("synor_getPeerInfo", |_, ctx| async move {
let peers = ctx.network.peers().await; let peers = ctx.network.peers().await;
let peer_info: Vec<serde_json::Value> = peers.iter().map(|p| { let peer_info: Vec<serde_json::Value> = peers
.iter()
.map(|p| {
serde_json::json!({ serde_json::json!({
"id": p.id, "id": p.id,
"address": p.address.map(|a| a.to_string()).unwrap_or_default(), "address": p.address.map(|a| a.to_string()).unwrap_or_default(),
@ -352,7 +363,8 @@ impl RpcService {
"userAgent": p.user_agent, "userAgent": p.user_agent,
"latencyMs": p.latency_ms "latencyMs": p.latency_ms
}) })
}).collect(); })
.collect();
serde_json::json!({"peers": peer_info}) serde_json::json!({"peers": peer_info})
})?; })?;
@ -418,7 +430,9 @@ impl RpcService {
let bytecode = match hex::decode(&params.bytecode) { let bytecode = match hex::decode(&params.bytecode) {
Ok(b) => b, Ok(b) => b,
Err(e) => return serde_json::json!({"error": format!("Invalid bytecode hex: {}", e)}), Err(e) => {
return serde_json::json!({"error": format!("Invalid bytecode hex: {}", e)})
}
}; };
let init_args = if params.init_args.is_empty() { let init_args = if params.init_args.is_empty() {
@ -426,21 +440,27 @@ impl RpcService {
} else { } else {
match hex::decode(&params.init_args) { match hex::decode(&params.init_args) {
Ok(a) => a, Ok(a) => a,
Err(e) => return serde_json::json!({"error": format!("Invalid init_args hex: {}", e)}), Err(e) => {
return serde_json::json!({"error": format!("Invalid init_args hex: {}", e)})
}
} }
}; };
let block_height = ctx.consensus.current_height().await; let block_height = ctx.consensus.current_height().await;
let timestamp = current_timestamp(); let timestamp = current_timestamp();
match ctx.contract.deploy( match ctx
.contract
.deploy(
bytecode, bytecode,
init_args, init_args,
&params.deployer, &params.deployer,
params.gas_limit, params.gas_limit,
block_height, block_height,
timestamp, timestamp,
).await { )
.await
{
Ok(result) => serde_json::json!({ Ok(result) => serde_json::json!({
"contractId": hex::encode(&result.contract_id), "contractId": hex::encode(&result.contract_id),
"address": hex::encode(&result.address), "address": hex::encode(&result.address),
@ -448,7 +468,7 @@ impl RpcService {
}), }),
Err(e) => serde_json::json!({ Err(e) => serde_json::json!({
"error": e.to_string() "error": e.to_string()
}) }),
} }
})?; })?;
@ -474,7 +494,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_id) { let contract_id = match hex_to_hash(&params.contract_id) {
Ok(id) => id, Ok(id) => id,
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}), Err(e) => {
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
}
}; };
let args = if params.args.is_empty() { let args = if params.args.is_empty() {
@ -482,14 +504,18 @@ impl RpcService {
} else { } else {
match hex::decode(&params.args) { match hex::decode(&params.args) {
Ok(a) => a, Ok(a) => a,
Err(e) => return serde_json::json!({"error": format!("Invalid args hex: {}", e)}), Err(e) => {
return serde_json::json!({"error": format!("Invalid args hex: {}", e)})
}
} }
}; };
let block_height = ctx.consensus.current_height().await; let block_height = ctx.consensus.current_height().await;
let timestamp = current_timestamp(); let timestamp = current_timestamp();
match ctx.contract.call( match ctx
.contract
.call(
&contract_id, &contract_id,
&params.method, &params.method,
args, args,
@ -498,7 +524,9 @@ impl RpcService {
params.gas_limit, params.gas_limit,
block_height, block_height,
timestamp, timestamp,
).await { )
.await
{
Ok(result) => { Ok(result) => {
let logs: Vec<serde_json::Value> = result.logs.iter().map(|log| { let logs: Vec<serde_json::Value> = result.logs.iter().map(|log| {
serde_json::json!({ serde_json::json!({
@ -514,10 +542,10 @@ impl RpcService {
"gasUsed": result.gas_used, "gasUsed": result.gas_used,
"logs": logs "logs": logs
}) })
}, }
Err(e) => serde_json::json!({ Err(e) => serde_json::json!({
"error": e.to_string() "error": e.to_string()
}) }),
} }
})?; })?;
@ -541,7 +569,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_id) { let contract_id = match hex_to_hash(&params.contract_id) {
Ok(id) => id, Ok(id) => id,
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}), Err(e) => {
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
}
}; };
let args = if params.args.is_empty() { let args = if params.args.is_empty() {
@ -549,14 +579,18 @@ impl RpcService {
} else { } else {
match hex::decode(&params.args) { match hex::decode(&params.args) {
Ok(a) => a, Ok(a) => a,
Err(e) => return serde_json::json!({"error": format!("Invalid args hex: {}", e)}), Err(e) => {
return serde_json::json!({"error": format!("Invalid args hex: {}", e)})
}
} }
}; };
let block_height = ctx.consensus.current_height().await; let block_height = ctx.consensus.current_height().await;
let timestamp = current_timestamp(); let timestamp = current_timestamp();
match ctx.contract.estimate_gas( match ctx
.contract
.estimate_gas(
&contract_id, &contract_id,
&params.method, &params.method,
args, args,
@ -564,13 +598,15 @@ impl RpcService {
params.value, params.value,
block_height, block_height,
timestamp, timestamp,
).await { )
.await
{
Ok(gas) => serde_json::json!({ Ok(gas) => serde_json::json!({
"estimatedGas": gas "estimatedGas": gas
}), }),
Err(e) => serde_json::json!({ Err(e) => serde_json::json!({
"error": e.to_string() "error": e.to_string()
}) }),
} }
})?; })?;
@ -588,7 +624,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_id) { let contract_id = match hex_to_hash(&params.contract_id) {
Ok(id) => id, Ok(id) => id,
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}), Err(e) => {
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
}
}; };
match ctx.contract.get_code(&contract_id).await { match ctx.contract.get_code(&contract_id).await {
@ -600,7 +638,7 @@ impl RpcService {
}), }),
Err(e) => serde_json::json!({ Err(e) => serde_json::json!({
"error": e.to_string() "error": e.to_string()
}) }),
} }
})?; })?;
@ -619,7 +657,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_id) { let contract_id = match hex_to_hash(&params.contract_id) {
Ok(id) => id, Ok(id) => id,
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}), Err(e) => {
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
}
}; };
let key = match hex_to_hash(&params.key) { let key = match hex_to_hash(&params.key) {
@ -636,7 +676,7 @@ impl RpcService {
}), }),
Err(e) => serde_json::json!({ Err(e) => serde_json::json!({
"error": e.to_string() "error": e.to_string()
}) }),
} }
})?; })?;
@ -654,7 +694,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_id) { let contract_id = match hex_to_hash(&params.contract_id) {
Ok(id) => id, Ok(id) => id,
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}), Err(e) => {
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
}
}; };
match ctx.contract.get_contract(&contract_id).await { match ctx.contract.get_contract(&contract_id).await {
@ -669,7 +711,7 @@ impl RpcService {
}), }),
Err(e) => serde_json::json!({ Err(e) => serde_json::json!({
"error": e.to_string() "error": e.to_string()
}) }),
} }
})?; })?;
@ -705,11 +747,7 @@ impl RpcService {
blue_work: String::new(), blue_work: String::new(),
pruning_point: None, pruning_point: None,
}, },
transactions: if include_txs { transactions: if include_txs { vec![] } else { vec![] },
vec![]
} else {
vec![]
},
verbose_data: None, verbose_data: None,
})) }))
} else { } else {
@ -845,8 +883,7 @@ impl RpcService {
pruning_point: None, pruning_point: None,
}, },
transactions: vec![], transactions: vec![],
target: "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff" target: "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string(),
.to_string(),
is_synced: true, is_synced: true,
}) })
} }

View file

@ -7,10 +7,9 @@ use tokio::sync::RwLock;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use synor_storage::{ use synor_storage::{
cf, Database, DatabaseConfig, cf, BlockBody, BlockStore, ChainState, Database, DatabaseConfig, GhostdagStore, HeaderStore,
BlockBody, BlockStore, ChainState, GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredGhostdagData, StoredRelations, StoredUtxo,
MetadataStore, RelationsStore, StoredGhostdagData, StoredRelations, TransactionStore, UtxoStore,
StoredUtxo, TransactionStore, UtxoStore,
}; };
use synor_types::{BlockHeader, BlockId, Hash256, Transaction, TransactionId}; use synor_types::{BlockHeader, BlockId, Hash256, Transaction, TransactionId};
@ -168,15 +167,23 @@ impl StorageService {
/// Stores a block header. /// Stores a block header.
pub async fn put_header(&self, header: &BlockHeader) -> anyhow::Result<()> { pub async fn put_header(&self, header: &BlockHeader) -> anyhow::Result<()> {
let store = self.header_store.read().await; let store = self.header_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.put(header).map_err(|e| anyhow::anyhow!("Failed to store header: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.put(header)
.map_err(|e| anyhow::anyhow!("Failed to store header: {}", e))
} }
/// Gets a block header by hash. /// Gets a block header by hash.
pub async fn get_header(&self, hash: &Hash256) -> anyhow::Result<Option<BlockHeader>> { pub async fn get_header(&self, hash: &Hash256) -> anyhow::Result<Option<BlockHeader>> {
let store = self.header_store.read().await; let store = self.header_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get(hash).map_err(|e| anyhow::anyhow!("Failed to get header: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get(hash)
.map_err(|e| anyhow::anyhow!("Failed to get header: {}", e))
} }
/// Checks if a header exists. /// Checks if a header exists.
@ -192,15 +199,23 @@ impl StorageService {
/// Gets header by height. /// Gets header by height.
pub async fn get_header_by_height(&self, height: u64) -> anyhow::Result<Option<BlockHeader>> { pub async fn get_header_by_height(&self, height: u64) -> anyhow::Result<Option<BlockHeader>> {
let store = self.header_store.read().await; let store = self.header_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_by_height(height).map_err(|e| anyhow::anyhow!("Failed to get header by height: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_by_height(height)
.map_err(|e| anyhow::anyhow!("Failed to get header by height: {}", e))
} }
/// Indexes a header by height. /// Indexes a header by height.
pub async fn index_header_by_height(&self, height: u64, hash: &Hash256) -> anyhow::Result<()> { pub async fn index_header_by_height(&self, height: u64, hash: &Hash256) -> anyhow::Result<()> {
let store = self.header_store.read().await; let store = self.header_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.index_by_height(height, hash).map_err(|e| anyhow::anyhow!("Failed to index header: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.index_by_height(height, hash)
.map_err(|e| anyhow::anyhow!("Failed to index header: {}", e))
} }
// ==================== Block Body Operations ==================== // ==================== Block Body Operations ====================
@ -208,15 +223,23 @@ impl StorageService {
/// Stores a block body. /// Stores a block body.
pub async fn put_block_body(&self, hash: &Hash256, body: &BlockBody) -> anyhow::Result<()> { pub async fn put_block_body(&self, hash: &Hash256, body: &BlockBody) -> anyhow::Result<()> {
let store = self.block_store.read().await; let store = self.block_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.put(hash, body).map_err(|e| anyhow::anyhow!("Failed to store block body: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.put(hash, body)
.map_err(|e| anyhow::anyhow!("Failed to store block body: {}", e))
} }
/// Gets a block body by hash. /// Gets a block body by hash.
pub async fn get_block_body(&self, hash: &Hash256) -> anyhow::Result<Option<BlockBody>> { pub async fn get_block_body(&self, hash: &Hash256) -> anyhow::Result<Option<BlockBody>> {
let store = self.block_store.read().await; let store = self.block_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get(hash).map_err(|e| anyhow::anyhow!("Failed to get block body: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get(hash)
.map_err(|e| anyhow::anyhow!("Failed to get block body: {}", e))
} }
/// Checks if block exists. /// Checks if block exists.
@ -233,7 +256,9 @@ impl StorageService {
pub async fn put_block(&self, block: &BlockData) -> anyhow::Result<()> { pub async fn put_block(&self, block: &BlockData) -> anyhow::Result<()> {
debug!(hash = hex::encode(&block.hash[..8]), "Storing block"); debug!(hash = hex::encode(&block.hash[..8]), "Storing block");
let db = self.database.read().await; let db = self.database.read().await;
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let db = db
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
// Store header bytes // Store header bytes
db.put(cf::HEADERS, &block.hash, &block.header) db.put(cf::HEADERS, &block.hash, &block.header)
@ -249,11 +274,15 @@ impl StorageService {
/// Legacy method: Gets a block by hash (raw bytes). /// Legacy method: Gets a block by hash (raw bytes).
pub async fn get_block(&self, hash: &[u8; 32]) -> anyhow::Result<Option<BlockData>> { pub async fn get_block(&self, hash: &[u8; 32]) -> anyhow::Result<Option<BlockData>> {
let db = self.database.read().await; let db = self.database.read().await;
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let db = db
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
let header = db.get(cf::HEADERS, hash) let header = db
.get(cf::HEADERS, hash)
.map_err(|e| anyhow::anyhow!("Failed to get header: {}", e))?; .map_err(|e| anyhow::anyhow!("Failed to get header: {}", e))?;
let body = db.get(cf::BLOCKS, hash) let body = db
.get(cf::BLOCKS, hash)
.map_err(|e| anyhow::anyhow!("Failed to get body: {}", e))?; .map_err(|e| anyhow::anyhow!("Failed to get body: {}", e))?;
match (header, body) { match (header, body) {
@ -271,15 +300,26 @@ impl StorageService {
/// Stores a transaction. /// Stores a transaction.
pub async fn put_transaction(&self, tx: &Transaction) -> anyhow::Result<()> { pub async fn put_transaction(&self, tx: &Transaction) -> anyhow::Result<()> {
let store = self.tx_store.read().await; let store = self.tx_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.put(tx).map_err(|e| anyhow::anyhow!("Failed to store transaction: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.put(tx)
.map_err(|e| anyhow::anyhow!("Failed to store transaction: {}", e))
} }
/// Gets a transaction by ID. /// Gets a transaction by ID.
pub async fn get_transaction(&self, txid: &TransactionId) -> anyhow::Result<Option<Transaction>> { pub async fn get_transaction(
&self,
txid: &TransactionId,
) -> anyhow::Result<Option<Transaction>> {
let store = self.tx_store.read().await; let store = self.tx_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get(txid).map_err(|e| anyhow::anyhow!("Failed to get transaction: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get(txid)
.map_err(|e| anyhow::anyhow!("Failed to get transaction: {}", e))
} }
/// Checks if a transaction exists. /// Checks if a transaction exists.
@ -295,24 +335,45 @@ impl StorageService {
// ==================== UTXO Operations ==================== // ==================== UTXO Operations ====================
/// Gets a UTXO. /// Gets a UTXO.
pub async fn get_utxo(&self, txid: &TransactionId, index: u32) -> anyhow::Result<Option<StoredUtxo>> { pub async fn get_utxo(
&self,
txid: &TransactionId,
index: u32,
) -> anyhow::Result<Option<StoredUtxo>> {
let store = self.utxo_store.read().await; let store = self.utxo_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get(txid, index).map_err(|e| anyhow::anyhow!("Failed to get UTXO: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get(txid, index)
.map_err(|e| anyhow::anyhow!("Failed to get UTXO: {}", e))
} }
/// Stores a UTXO. /// Stores a UTXO.
pub async fn put_utxo(&self, txid: &TransactionId, index: u32, utxo: &StoredUtxo) -> anyhow::Result<()> { pub async fn put_utxo(
&self,
txid: &TransactionId,
index: u32,
utxo: &StoredUtxo,
) -> anyhow::Result<()> {
let store = self.utxo_store.read().await; let store = self.utxo_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.put(txid, index, utxo).map_err(|e| anyhow::anyhow!("Failed to store UTXO: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.put(txid, index, utxo)
.map_err(|e| anyhow::anyhow!("Failed to store UTXO: {}", e))
} }
/// Deletes a UTXO (marks as spent). /// Deletes a UTXO (marks as spent).
pub async fn delete_utxo(&self, txid: &TransactionId, index: u32) -> anyhow::Result<()> { pub async fn delete_utxo(&self, txid: &TransactionId, index: u32) -> anyhow::Result<()> {
let store = self.utxo_store.read().await; let store = self.utxo_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.delete(txid, index).map_err(|e| anyhow::anyhow!("Failed to delete UTXO: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.delete(txid, index)
.map_err(|e| anyhow::anyhow!("Failed to delete UTXO: {}", e))
} }
/// Checks if a UTXO exists (is unspent). /// Checks if a UTXO exists (is unspent).
@ -326,77 +387,134 @@ impl StorageService {
} }
/// Gets all UTXOs for a transaction. /// Gets all UTXOs for a transaction.
pub async fn get_utxos_by_tx(&self, txid: &TransactionId) -> anyhow::Result<Vec<(u32, StoredUtxo)>> { pub async fn get_utxos_by_tx(
&self,
txid: &TransactionId,
) -> anyhow::Result<Vec<(u32, StoredUtxo)>> {
let store = self.utxo_store.read().await; let store = self.utxo_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_by_tx(txid).map_err(|e| anyhow::anyhow!("Failed to get UTXOs: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_by_tx(txid)
.map_err(|e| anyhow::anyhow!("Failed to get UTXOs: {}", e))
} }
// ==================== DAG Relations Operations ==================== // ==================== DAG Relations Operations ====================
/// Stores DAG relations for a block. /// Stores DAG relations for a block.
pub async fn put_relations(&self, block_id: &BlockId, relations: &StoredRelations) -> anyhow::Result<()> { pub async fn put_relations(
&self,
block_id: &BlockId,
relations: &StoredRelations,
) -> anyhow::Result<()> {
let store = self.relations_store.read().await; let store = self.relations_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.put(block_id, relations).map_err(|e| anyhow::anyhow!("Failed to store relations: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.put(block_id, relations)
.map_err(|e| anyhow::anyhow!("Failed to store relations: {}", e))
} }
/// Gets DAG relations for a block. /// Gets DAG relations for a block.
pub async fn get_relations(&self, block_id: &BlockId) -> anyhow::Result<Option<StoredRelations>> { pub async fn get_relations(
&self,
block_id: &BlockId,
) -> anyhow::Result<Option<StoredRelations>> {
let store = self.relations_store.read().await; let store = self.relations_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get(block_id).map_err(|e| anyhow::anyhow!("Failed to get relations: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get(block_id)
.map_err(|e| anyhow::anyhow!("Failed to get relations: {}", e))
} }
/// Gets parents of a block. /// Gets parents of a block.
pub async fn get_parents(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> { pub async fn get_parents(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> {
let store = self.relations_store.read().await; let store = self.relations_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_parents(block_id).map_err(|e| anyhow::anyhow!("Failed to get parents: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_parents(block_id)
.map_err(|e| anyhow::anyhow!("Failed to get parents: {}", e))
} }
/// Gets children of a block. /// Gets children of a block.
pub async fn get_children(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> { pub async fn get_children(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> {
let store = self.relations_store.read().await; let store = self.relations_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_children(block_id).map_err(|e| anyhow::anyhow!("Failed to get children: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_children(block_id)
.map_err(|e| anyhow::anyhow!("Failed to get children: {}", e))
} }
/// Adds a child to a block's relations. /// Adds a child to a block's relations.
pub async fn add_child(&self, parent_id: &BlockId, child_id: BlockId) -> anyhow::Result<()> { pub async fn add_child(&self, parent_id: &BlockId, child_id: BlockId) -> anyhow::Result<()> {
let store = self.relations_store.read().await; let store = self.relations_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.add_child(parent_id, child_id).map_err(|e| anyhow::anyhow!("Failed to add child: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.add_child(parent_id, child_id)
.map_err(|e| anyhow::anyhow!("Failed to add child: {}", e))
} }
// ==================== GHOSTDAG Operations ==================== // ==================== GHOSTDAG Operations ====================
/// Stores GHOSTDAG data for a block. /// Stores GHOSTDAG data for a block.
pub async fn put_ghostdag(&self, block_id: &BlockId, data: &StoredGhostdagData) -> anyhow::Result<()> { pub async fn put_ghostdag(
&self,
block_id: &BlockId,
data: &StoredGhostdagData,
) -> anyhow::Result<()> {
let store = self.ghostdag_store.read().await; let store = self.ghostdag_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.put(block_id, data).map_err(|e| anyhow::anyhow!("Failed to store GHOSTDAG data: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.put(block_id, data)
.map_err(|e| anyhow::anyhow!("Failed to store GHOSTDAG data: {}", e))
} }
/// Gets GHOSTDAG data for a block. /// Gets GHOSTDAG data for a block.
pub async fn get_ghostdag(&self, block_id: &BlockId) -> anyhow::Result<Option<StoredGhostdagData>> { pub async fn get_ghostdag(
&self,
block_id: &BlockId,
) -> anyhow::Result<Option<StoredGhostdagData>> {
let store = self.ghostdag_store.read().await; let store = self.ghostdag_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get(block_id).map_err(|e| anyhow::anyhow!("Failed to get GHOSTDAG data: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get(block_id)
.map_err(|e| anyhow::anyhow!("Failed to get GHOSTDAG data: {}", e))
} }
/// Gets the blue score of a block. /// Gets the blue score of a block.
pub async fn get_blue_score(&self, block_id: &BlockId) -> anyhow::Result<Option<u64>> { pub async fn get_blue_score(&self, block_id: &BlockId) -> anyhow::Result<Option<u64>> {
let store = self.ghostdag_store.read().await; let store = self.ghostdag_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_blue_score(block_id).map_err(|e| anyhow::anyhow!("Failed to get blue score: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_blue_score(block_id)
.map_err(|e| anyhow::anyhow!("Failed to get blue score: {}", e))
} }
/// Gets the selected parent of a block. /// Gets the selected parent of a block.
pub async fn get_selected_parent(&self, block_id: &BlockId) -> anyhow::Result<Option<BlockId>> { pub async fn get_selected_parent(&self, block_id: &BlockId) -> anyhow::Result<Option<BlockId>> {
let store = self.ghostdag_store.read().await; let store = self.ghostdag_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_selected_parent(block_id).map_err(|e| anyhow::anyhow!("Failed to get selected parent: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_selected_parent(block_id)
.map_err(|e| anyhow::anyhow!("Failed to get selected parent: {}", e))
} }
// ==================== Metadata Operations ==================== // ==================== Metadata Operations ====================
@ -404,15 +522,23 @@ impl StorageService {
/// Gets current DAG tips. /// Gets current DAG tips.
pub async fn get_tips(&self) -> anyhow::Result<Vec<BlockId>> { pub async fn get_tips(&self) -> anyhow::Result<Vec<BlockId>> {
let store = self.metadata_store.read().await; let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_tips().map_err(|e| anyhow::anyhow!("Failed to get tips: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_tips()
.map_err(|e| anyhow::anyhow!("Failed to get tips: {}", e))
} }
/// Sets current DAG tips. /// Sets current DAG tips.
pub async fn set_tips(&self, tips: &[BlockId]) -> anyhow::Result<()> { pub async fn set_tips(&self, tips: &[BlockId]) -> anyhow::Result<()> {
let store = self.metadata_store.read().await; let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.set_tips(tips).map_err(|e| anyhow::anyhow!("Failed to set tips: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.set_tips(tips)
.map_err(|e| anyhow::anyhow!("Failed to set tips: {}", e))
} }
/// Gets the current chain tip (first tip, for legacy compatibility). /// Gets the current chain tip (first tip, for legacy compatibility).
@ -433,29 +559,45 @@ impl StorageService {
/// Gets the genesis block ID. /// Gets the genesis block ID.
pub async fn get_genesis(&self) -> anyhow::Result<Option<BlockId>> { pub async fn get_genesis(&self) -> anyhow::Result<Option<BlockId>> {
let store = self.metadata_store.read().await; let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_genesis().map_err(|e| anyhow::anyhow!("Failed to get genesis: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_genesis()
.map_err(|e| anyhow::anyhow!("Failed to get genesis: {}", e))
} }
/// Sets the genesis block ID. /// Sets the genesis block ID.
pub async fn set_genesis(&self, genesis: &BlockId) -> anyhow::Result<()> { pub async fn set_genesis(&self, genesis: &BlockId) -> anyhow::Result<()> {
let store = self.metadata_store.read().await; let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.set_genesis(genesis).map_err(|e| anyhow::anyhow!("Failed to set genesis: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.set_genesis(genesis)
.map_err(|e| anyhow::anyhow!("Failed to set genesis: {}", e))
} }
/// Gets the chain state. /// Gets the chain state.
pub async fn get_chain_state(&self) -> anyhow::Result<Option<ChainState>> { pub async fn get_chain_state(&self) -> anyhow::Result<Option<ChainState>> {
let store = self.metadata_store.read().await; let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_chain_state().map_err(|e| anyhow::anyhow!("Failed to get chain state: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_chain_state()
.map_err(|e| anyhow::anyhow!("Failed to get chain state: {}", e))
} }
/// Sets the chain state. /// Sets the chain state.
pub async fn set_chain_state(&self, state: &ChainState) -> anyhow::Result<()> { pub async fn set_chain_state(&self, state: &ChainState) -> anyhow::Result<()> {
let store = self.metadata_store.read().await; let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.set_chain_state(state).map_err(|e| anyhow::anyhow!("Failed to set chain state: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.set_chain_state(state)
.map_err(|e| anyhow::anyhow!("Failed to set chain state: {}", e))
} }
/// Gets current height from chain state. /// Gets current height from chain state.
@ -470,15 +612,23 @@ impl StorageService {
/// Gets the pruning point. /// Gets the pruning point.
pub async fn get_pruning_point(&self) -> anyhow::Result<Option<BlockId>> { pub async fn get_pruning_point(&self) -> anyhow::Result<Option<BlockId>> {
let store = self.metadata_store.read().await; let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.get_pruning_point().map_err(|e| anyhow::anyhow!("Failed to get pruning point: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.get_pruning_point()
.map_err(|e| anyhow::anyhow!("Failed to get pruning point: {}", e))
} }
/// Sets the pruning point. /// Sets the pruning point.
pub async fn set_pruning_point(&self, point: &BlockId) -> anyhow::Result<()> { pub async fn set_pruning_point(&self, point: &BlockId) -> anyhow::Result<()> {
let store = self.metadata_store.read().await; let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let store = store
store.set_pruning_point(point).map_err(|e| anyhow::anyhow!("Failed to set pruning point: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store
.set_pruning_point(point)
.map_err(|e| anyhow::anyhow!("Failed to set pruning point: {}", e))
} }
// ==================== Contract Storage ==================== // ==================== Contract Storage ====================
@ -490,7 +640,9 @@ impl StorageService {
key: &[u8; 32], key: &[u8; 32],
) -> anyhow::Result<Option<Vec<u8>>> { ) -> anyhow::Result<Option<Vec<u8>>> {
let db = self.database.read().await; let db = self.database.read().await;
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let db = db
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
// Create composite key: contract_address || storage_key // Create composite key: contract_address || storage_key
let mut composite_key = Vec::with_capacity(64); let mut composite_key = Vec::with_capacity(64);
@ -510,7 +662,9 @@ impl StorageService {
value: &[u8], value: &[u8],
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let db = self.database.read().await; let db = self.database.read().await;
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let db = db
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
let mut composite_key = Vec::with_capacity(64); let mut composite_key = Vec::with_capacity(64);
composite_key.extend_from_slice(contract); composite_key.extend_from_slice(contract);
@ -526,15 +680,21 @@ impl StorageService {
pub async fn compact(&self) -> anyhow::Result<()> { pub async fn compact(&self) -> anyhow::Result<()> {
info!("Compacting database"); info!("Compacting database");
let db = self.database.read().await; let db = self.database.read().await;
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let db = db
db.compact().map_err(|e| anyhow::anyhow!("Failed to compact database: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
db.compact()
.map_err(|e| anyhow::anyhow!("Failed to compact database: {}", e))
} }
/// Flushes pending writes to disk. /// Flushes pending writes to disk.
pub async fn flush(&self) -> anyhow::Result<()> { pub async fn flush(&self) -> anyhow::Result<()> {
let db = self.database.read().await; let db = self.database.read().await;
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?; let db = db
db.flush().map_err(|e| anyhow::anyhow!("Failed to flush database: {}", e)) .as_ref()
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
db.flush()
.map_err(|e| anyhow::anyhow!("Failed to flush database: {}", e))
} }
/// Gets database statistics. /// Gets database statistics.
@ -545,7 +705,9 @@ impl StorageService {
let headers_size = db.cf_size(cf::HEADERS).unwrap_or(0); let headers_size = db.cf_size(cf::HEADERS).unwrap_or(0);
let blocks_size = db.cf_size(cf::BLOCKS).unwrap_or(0); let blocks_size = db.cf_size(cf::BLOCKS).unwrap_or(0);
let utxos_size = db.cf_size(cf::UTXOS).unwrap_or(0); let utxos_size = db.cf_size(cf::UTXOS).unwrap_or(0);
let total_size = headers_size + blocks_size + utxos_size let total_size = headers_size
+ blocks_size
+ utxos_size
+ db.cf_size(cf::TRANSACTIONS).unwrap_or(0) + db.cf_size(cf::TRANSACTIONS).unwrap_or(0)
+ db.cf_size(cf::RELATIONS).unwrap_or(0) + db.cf_size(cf::RELATIONS).unwrap_or(0)
+ db.cf_size(cf::GHOSTDAG).unwrap_or(0) + db.cf_size(cf::GHOSTDAG).unwrap_or(0)

View file

@ -169,7 +169,11 @@ async fn test_selected_parent_chain() {
for (i, node) in network.nodes.iter().enumerate() { for (i, node) in network.nodes.iter().enumerate() {
let consensus = node.consensus(); let consensus = node.consensus();
let chain: Vec<[u8; 32]> = consensus.get_selected_chain(10).await; let chain: Vec<[u8; 32]> = consensus.get_selected_chain(10).await;
info!(node = i, chain_length = chain.len(), "Selected parent chain"); info!(
node = i,
chain_length = chain.len(),
"Selected parent chain"
);
// Chain should be consistent across nodes in same network // Chain should be consistent across nodes in same network
for (j, block) in chain.iter().enumerate() { for (j, block) in chain.iter().enumerate() {
@ -551,8 +555,16 @@ async fn test_partition_and_recovery() {
// In real test, we'd need fresh config for same ports // In real test, we'd need fresh config for same ports
// For now, just verify nodes didn't crash // For now, just verify nodes didn't crash
assert_eq!(node1.state().await, NodeState::Running, "Node 1 should survive partition"); assert_eq!(
assert_eq!(node2.state().await, NodeState::Running, "Node 2 should survive partition"); node1.state().await,
NodeState::Running,
"Node 1 should survive partition"
);
assert_eq!(
node2.state().await,
NodeState::Running,
"Node 2 should survive partition"
);
node2.stop().await.unwrap(); node2.stop().await.unwrap();
node1.stop().await.unwrap(); node1.stop().await.unwrap();
@ -594,11 +606,7 @@ async fn test_difficulty_adjustment() {
let difficulty = consensus.current_difficulty().await; let difficulty = consensus.current_difficulty().await;
let _target = consensus.get_current_target().await; let _target = consensus.get_current_target().await;
info!( info!(node = i, difficulty_bits = difficulty, "Difficulty info");
node = i,
difficulty_bits = difficulty,
"Difficulty info"
);
// Difficulty should be set // Difficulty should be set
// Target is the hash threshold for valid blocks // Target is the hash threshold for valid blocks
@ -653,7 +661,10 @@ async fn test_block_accepted_subscription() {
// Check if any blocks are received (unlikely in test without mining) // Check if any blocks are received (unlikely in test without mining)
match tokio::time::timeout(Duration::from_millis(500), rx.recv()).await { match tokio::time::timeout(Duration::from_millis(500), rx.recv()).await {
Ok(Ok(hash)) => { Ok(Ok(hash)) => {
info!(block = hex::encode(&hash[..8]), "Received block notification"); info!(
block = hex::encode(&hash[..8]),
"Received block notification"
);
} }
Ok(Err(_)) => { Ok(Err(_)) => {
info!("Block channel closed"); info!("Block channel closed");

View file

@ -188,7 +188,11 @@ async fn test_three_node_mesh() {
info!(total_connections = total, "Network mesh formed"); info!(total_connections = total, "Network mesh formed");
// In a 3-node mesh, we expect 2-4 total connections (each connection counted twice) // In a 3-node mesh, we expect 2-4 total connections (each connection counted twice)
assert!(total >= 2, "Expected at least 2 total connections, got {}", total); assert!(
total >= 2,
"Expected at least 2 total connections, got {}",
total
);
network.stop_all().await.unwrap(); network.stop_all().await.unwrap();
} }
@ -532,7 +536,11 @@ async fn test_simultaneous_node_start() {
let net = node.network(); let net = node.network();
let count = net.peer_count().await; let count = net.peer_count().await;
total_connections += count; total_connections += count;
info!(node = i, peers = count, "Peer count after simultaneous start"); info!(
node = i,
peers = count,
"Peer count after simultaneous start"
);
} }
info!( info!(

View file

@ -119,7 +119,11 @@ async fn test_node_creation() {
assert!(result.is_ok(), "Node creation timed out"); assert!(result.is_ok(), "Node creation timed out");
let node_result = result.unwrap(); let node_result = result.unwrap();
assert!(node_result.is_ok(), "Node creation failed: {:?}", node_result.err()); assert!(
node_result.is_ok(),
"Node creation failed: {:?}",
node_result.err()
);
} }
#[tokio::test] #[tokio::test]

View file

@ -250,11 +250,19 @@ async fn test_selected_chain_update() {
// Log the chain blocks // Log the chain blocks
for (i, block) in chain0.iter().enumerate().take(5) { for (i, block) in chain0.iter().enumerate().take(5) {
info!(position = i, block = hex::encode(&block[..8]), "Node 0 chain"); info!(
position = i,
block = hex::encode(&block[..8]),
"Node 0 chain"
);
} }
for (i, block) in chain1.iter().enumerate().take(5) { for (i, block) in chain1.iter().enumerate().take(5) {
info!(position = i, block = hex::encode(&block[..8]), "Node 1 chain"); info!(
position = i,
block = hex::encode(&block[..8]),
"Node 1 chain"
);
} }
// Chains should be similar after sync // Chains should be similar after sync

View file

@ -274,7 +274,9 @@ async fn test_block_request_response() {
if !peers.is_empty() { if !peers.is_empty() {
// Attempt to request blocks (would need actual block hashes) // Attempt to request blocks (would need actual block hashes)
let test_hashes = vec![[0u8; 32]]; // Dummy hash let test_hashes = vec![[0u8; 32]]; // Dummy hash
let result = network_service.request_blocks(&peers[0].id, test_hashes).await; let result = network_service
.request_blocks(&peers[0].id, test_hashes)
.await;
info!(result = ?result.is_ok(), "Block request result"); info!(result = ?result.is_ok(), "Block request result");
// Error expected for non-existent hash, but API should work // Error expected for non-existent hash, but API should work
} }

View file

@ -159,14 +159,12 @@ impl ContractAbi {
/// Serializes to JSON. /// Serializes to JSON.
pub fn to_json(&self) -> Result<String> { pub fn to_json(&self) -> Result<String> {
serde_json::to_string_pretty(self) serde_json::to_string_pretty(self).map_err(|e| CompilerError::EncodingError(e.to_string()))
.map_err(|e| CompilerError::EncodingError(e.to_string()))
} }
/// Deserializes from JSON. /// Deserializes from JSON.
pub fn from_json(json: &str) -> Result<Self> { pub fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json) serde_json::from_str(json).map_err(|e| CompilerError::ParseError(e.to_string()))
.map_err(|e| CompilerError::ParseError(e.to_string()))
} }
} }
@ -358,7 +356,9 @@ pub enum TypeInfo {
Bytes, Bytes,
/// Fixed-size bytes. /// Fixed-size bytes.
FixedBytes { size: usize }, FixedBytes {
size: usize,
},
/// Address. /// Address.
Address, Address,
@ -367,22 +367,36 @@ pub enum TypeInfo {
Hash256, Hash256,
/// Array. /// Array.
Array { element: Box<TypeInfo> }, Array {
element: Box<TypeInfo>,
},
/// Fixed-size array. /// Fixed-size array.
FixedArray { element: Box<TypeInfo>, size: usize }, FixedArray {
element: Box<TypeInfo>,
size: usize,
},
/// Optional value. /// Optional value.
Option { inner: Box<TypeInfo> }, Option {
inner: Box<TypeInfo>,
},
/// Tuple. /// Tuple.
Tuple { elements: Vec<TypeInfo> }, Tuple {
elements: Vec<TypeInfo>,
},
/// Struct. /// Struct.
Struct { name: String, fields: Vec<(String, TypeInfo)> }, Struct {
name: String,
fields: Vec<(String, TypeInfo)>,
},
/// Unknown type. /// Unknown type.
Unknown { name: String }, Unknown {
name: String,
},
} }
impl TypeInfo { impl TypeInfo {
@ -493,7 +507,9 @@ fn parse_producers_section(metadata: &mut ContractMetadata, data: &[u8]) {
if data_str.contains("rustc") { if data_str.contains("rustc") {
// Extract rustc version if present // Extract rustc version if present
if let Some(start) = data_str.find("rustc") { if let Some(start) = data_str.find("rustc") {
let end = data_str[start..].find('\0').unwrap_or(data_str.len() - start); let end = data_str[start..]
.find('\0')
.unwrap_or(data_str.len() - start);
let version = &data_str[start..start + end]; let version = &data_str[start..start + end];
metadata.rust_version = Some(version.to_string()); metadata.rust_version = Some(version.to_string());
} }
@ -540,7 +556,8 @@ pub fn extract_abi(wasm: &[u8]) -> Result<ContractAbi> {
match payload { match payload {
Payload::TypeSection(reader) => { Payload::TypeSection(reader) => {
for rec_group in reader { for rec_group in reader {
let rec_group = rec_group.map_err(|e| CompilerError::ParseError(e.to_string()))?; let rec_group =
rec_group.map_err(|e| CompilerError::ParseError(e.to_string()))?;
for subtype in rec_group.into_types() { for subtype in rec_group.into_types() {
if let wasmparser::CompositeType::Func(func_type) = subtype.composite_type { if let wasmparser::CompositeType::Func(func_type) = subtype.composite_type {
function_types.push(FunctionType { function_types.push(FunctionType {
@ -562,7 +579,8 @@ pub fn extract_abi(wasm: &[u8]) -> Result<ContractAbi> {
} }
Payload::FunctionSection(reader) => { Payload::FunctionSection(reader) => {
for type_idx in reader { for type_idx in reader {
let type_idx = type_idx.map_err(|e| CompilerError::ParseError(e.to_string()))?; let type_idx =
type_idx.map_err(|e| CompilerError::ParseError(e.to_string()))?;
function_type_indices.push(type_idx); function_type_indices.push(type_idx);
} }
} }
@ -773,13 +791,13 @@ mod tests {
assert_eq!(TypeInfo::U64.type_name(), "u64"); assert_eq!(TypeInfo::U64.type_name(), "u64");
assert_eq!(TypeInfo::Address.type_name(), "Address"); assert_eq!(TypeInfo::Address.type_name(), "Address");
assert_eq!( assert_eq!(
TypeInfo::Array { element: Box::new(TypeInfo::U8) }.type_name(), TypeInfo::Array {
element: Box::new(TypeInfo::U8)
}
.type_name(),
"Vec<u8>" "Vec<u8>"
); );
assert_eq!( assert_eq!(TypeInfo::FixedBytes { size: 32 }.type_name(), "[u8; 32]");
TypeInfo::FixedBytes { size: 32 }.type_name(),
"[u8; 32]"
);
} }
#[test] #[test]

View file

@ -260,10 +260,11 @@ impl Optimizer {
} }
// Run wasm-opt // Run wasm-opt
debug!("Running wasm-opt with flags: {:?}", self.level.wasm_opt_flags()); debug!(
let output = cmd "Running wasm-opt with flags: {:?}",
.output() self.level.wasm_opt_flags()
.map_err(|e| CompilerError::ExternalToolError { );
let output = cmd.output().map_err(|e| CompilerError::ExternalToolError {
tool: "wasm-opt".into(), tool: "wasm-opt".into(),
message: e.to_string(), message: e.to_string(),
})?; })?;
@ -388,11 +389,7 @@ mod tests {
#[test] #[test]
fn test_optimizer_creation() { fn test_optimizer_creation() {
let optimizer = Optimizer::new( let optimizer = Optimizer::new(OptimizationLevel::Size, StripOptions::default(), None);
OptimizationLevel::Size,
StripOptions::default(),
None,
);
assert_eq!(optimizer.level, OptimizationLevel::Size); assert_eq!(optimizer.level, OptimizationLevel::Size);
} }

View file

@ -316,7 +316,8 @@ impl Validator {
} }
Payload::ImportSection(reader) => { Payload::ImportSection(reader) => {
for import in reader { for import in reader {
let import = import.map_err(|e| ValidationError::ParseError(e.to_string()))?; let import =
import.map_err(|e| ValidationError::ParseError(e.to_string()))?;
let kind = match import.ty { let kind = match import.ty {
wasmparser::TypeRef::Func(type_idx) => ImportKind::Function(type_idx), wasmparser::TypeRef::Func(type_idx) => ImportKind::Function(type_idx),
@ -339,7 +340,8 @@ impl Validator {
} }
Payload::MemorySection(reader) => { Payload::MemorySection(reader) => {
for memory in reader { for memory in reader {
let memory = memory.map_err(|e| ValidationError::ParseError(e.to_string()))?; let memory =
memory.map_err(|e| ValidationError::ParseError(e.to_string()))?;
memories.push(MemoryInfo { memories.push(MemoryInfo {
min_pages: memory.initial, min_pages: memory.initial,
max_pages: memory.maximum, max_pages: memory.maximum,
@ -350,7 +352,8 @@ impl Validator {
} }
Payload::GlobalSection(reader) => { Payload::GlobalSection(reader) => {
for global in reader { for global in reader {
let global = global.map_err(|e| ValidationError::ParseError(e.to_string()))?; let global =
global.map_err(|e| ValidationError::ParseError(e.to_string()))?;
if global.ty.mutable { if global.ty.mutable {
has_mutable_globals = true; has_mutable_globals = true;
} }
@ -358,7 +361,8 @@ impl Validator {
} }
Payload::ExportSection(reader) => { Payload::ExportSection(reader) => {
for export in reader { for export in reader {
let export = export.map_err(|e| ValidationError::ParseError(e.to_string()))?; let export =
export.map_err(|e| ValidationError::ParseError(e.to_string()))?;
let kind = match export.kind { let kind = match export.kind {
wasmparser::ExternalKind::Func => ExportKind::Function, wasmparser::ExternalKind::Func => ExportKind::Function,
@ -404,10 +408,14 @@ impl Validator {
// Validate entry points (warn if missing, don't error) // Validate entry points (warn if missing, don't error)
if !exports.iter().any(|e| e.name == "__synor_init") { if !exports.iter().any(|e| e.name == "__synor_init") {
result.warnings.push("Missing __synor_init export - contract cannot be initialized".into()); result
.warnings
.push("Missing __synor_init export - contract cannot be initialized".into());
} }
if !exports.iter().any(|e| e.name == "__synor_call") { if !exports.iter().any(|e| e.name == "__synor_call") {
result.warnings.push("Missing __synor_call export - contract cannot be called".into()); result
.warnings
.push("Missing __synor_call export - contract cannot be called".into());
} }
// Validate entry point signatures // Validate entry point signatures
@ -427,12 +435,19 @@ impl Validator {
debug!("Validating {} memories", memories.len()); debug!("Validating {} memories", memories.len());
if memories.is_empty() { if memories.is_empty() {
// Check if memory is imported // Check if memory is imported
let has_imported_memory = result.imports.iter().any(|i| matches!(i.kind, ImportKind::Memory)); let has_imported_memory = result
.imports
.iter()
.any(|i| matches!(i.kind, ImportKind::Memory));
if !has_imported_memory { if !has_imported_memory {
errors.push(ValidationError::MemoryError("No memory defined or imported".into())); errors.push(ValidationError::MemoryError(
"No memory defined or imported".into(),
));
} }
} else if memories.len() > 1 { } else if memories.len() > 1 {
errors.push(ValidationError::MemoryError("Multiple memories not supported".into())); errors.push(ValidationError::MemoryError(
"Multiple memories not supported".into(),
));
} else { } else {
let mem = &memories[0]; let mem = &memories[0];
@ -445,9 +460,9 @@ impl Validator {
))); )));
} }
} else { } else {
result.warnings.push( result
"Memory has no maximum limit - recommended to set max_pages".into(), .warnings
); .push("Memory has no maximum limit - recommended to set max_pages".into());
} }
// Check for shared memory (threads) // Check for shared memory (threads)

View file

@ -30,10 +30,7 @@ fn make_outpoint(tx_n: u64, index: u32) -> Outpoint {
/// Creates a P2PKH output. /// Creates a P2PKH output.
fn make_p2pkh_output(amount: u64) -> TxOutput { fn make_p2pkh_output(amount: u64) -> TxOutput {
TxOutput::new( TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32]))
Amount::from_sompi(amount),
ScriptPubKey::p2pkh(&[0u8; 32]),
)
} }
/// Creates a UTXO entry. /// Creates a UTXO entry.
@ -235,9 +232,7 @@ fn utxo_lookup(c: &mut Criterion) {
group.bench_with_input( group.bench_with_input(
BenchmarkId::from_parameter(set_size), BenchmarkId::from_parameter(set_size),
&(utxo_set, target), &(utxo_set, target),
|b, (set, target)| { |b, (set, target)| b.iter(|| black_box(set.get(target))),
b.iter(|| black_box(set.get(target)))
},
); );
} }
@ -254,9 +249,7 @@ fn utxo_contains(c: &mut Criterion) {
group.bench_with_input( group.bench_with_input(
BenchmarkId::from_parameter(set_size), BenchmarkId::from_parameter(set_size),
&(utxo_set, target), &(utxo_set, target),
|b, (set, target)| { |b, (set, target)| b.iter(|| black_box(set.contains(target))),
b.iter(|| black_box(set.contains(target)))
},
); );
} }
@ -328,7 +321,10 @@ fn utxo_diff_apply(c: &mut Criterion) {
// Add new entries // Add new entries
for i in n..(n + n / 2) { for i in n..(n + n / 2) {
diff.add(make_outpoint(i as u64, 0), make_utxo_entry(500_000_000, 100, false)); diff.add(
make_outpoint(i as u64, 0),
make_utxo_entry(500_000_000, 100, false),
);
} }
(set, diff) (set, diff)
@ -357,14 +353,20 @@ fn utxo_diff_merge(c: &mut Criterion) {
let mut diff2 = UtxoDiff::new(); let mut diff2 = UtxoDiff::new();
for i in 0..n { for i in 0..n {
diff1.add(make_outpoint(i as u64, 0), make_utxo_entry(100_000_000, 100, false)); diff1.add(
make_outpoint(i as u64, 0),
make_utxo_entry(100_000_000, 100, false),
);
} }
for i in 0..(n / 2) { for i in 0..(n / 2) {
diff2.remove(make_outpoint(i as u64, 0)); diff2.remove(make_outpoint(i as u64, 0));
} }
for i in n..(n * 2) { for i in n..(n * 2) {
diff2.add(make_outpoint(i as u64, 0), make_utxo_entry(200_000_000, 100, false)); diff2.add(
make_outpoint(i as u64, 0),
make_utxo_entry(200_000_000, 100, false),
);
} }
(diff1, diff2) (diff1, diff2)
@ -400,9 +402,7 @@ fn utxo_create_tx_diff(c: &mut Criterion) {
group.bench_with_input( group.bench_with_input(
BenchmarkId::from_parameter(input_count), BenchmarkId::from_parameter(input_count),
&(utxo_set, tx), &(utxo_set, tx),
|b, (set, tx)| { |b, (set, tx)| b.iter(|| black_box(set.create_transaction_diff(tx, 1000))),
b.iter(|| black_box(set.create_transaction_diff(tx, 1000)))
},
); );
} }
@ -434,9 +434,7 @@ fn batch_tx_validation(c: &mut Criterion) {
let validator = TransactionValidator::new(); let validator = TransactionValidator::new();
for batch_size in [10, 50, 100] { for batch_size in [10, 50, 100] {
let transactions: Vec<_> = (0..batch_size) let transactions: Vec<_> = (0..batch_size).map(|_| make_transaction(2, 2)).collect();
.map(|_| make_transaction(2, 2))
.collect();
group.throughput(Throughput::Elements(batch_size as u64)); group.throughput(Throughput::Elements(batch_size as u64));
group.bench_with_input( group.bench_with_input(
@ -486,11 +484,7 @@ criterion_group!(
utxo_create_tx_diff, utxo_create_tx_diff,
); );
criterion_group!( criterion_group!(misc_benches, utxo_get_balance, batch_tx_validation,);
misc_benches,
utxo_get_balance,
batch_tx_validation,
);
criterion_main!( criterion_main!(
tx_validation_benches, tx_validation_benches,

View file

@ -6,8 +6,8 @@
use synor_types::{ use synor_types::{
block::BlockBody, block::BlockBody,
transaction::{Outpoint, ScriptPubKey, ScriptType, SubnetworkId}, transaction::{Outpoint, ScriptPubKey, ScriptType, SubnetworkId},
Amount, Block, BlockHeader, BlueScore, Hash256, Network, Timestamp, Amount, Block, BlockHeader, BlueScore, Hash256, Network, Timestamp, Transaction, TxInput,
Transaction, TxInput, TxOutput, TxOutput,
}; };
/// Chain configuration parameters. /// Chain configuration parameters.
@ -129,9 +129,7 @@ impl ChainConfig {
halving_interval: 100, halving_interval: 100,
initial_reward: 1000 * 100_000_000, // Higher reward for dev initial_reward: 1000 * 100_000_000, // Higher reward for dev
dns_seeds: vec![], dns_seeds: vec![],
bootstrap_nodes: vec![ bootstrap_nodes: vec!["/ip4/127.0.0.1/tcp/16511".to_string()],
"/ip4/127.0.0.1/tcp/16511".to_string(),
],
bip32_coin_type: 0x5359, bip32_coin_type: 0x5359,
address_prefix: "dsynor".to_string(), address_prefix: "dsynor".to_string(),
} }
@ -147,7 +145,11 @@ impl ChainConfig {
} }
/// Validates a block against chain rules. /// Validates a block against chain rules.
pub fn validate_block_header(&self, header: &BlockHeader, _parent_blue_score: u64) -> Result<(), GenesisError> { pub fn validate_block_header(
&self,
header: &BlockHeader,
_parent_blue_score: u64,
) -> Result<(), GenesisError> {
// Check timestamp is reasonable // Check timestamp is reasonable
let now_ms = Timestamp::now().as_millis(); let now_ms = Timestamp::now().as_millis();
let max_future = 2 * 60 * 60 * 1000; // 2 hours let max_future = 2 * 60 * 60 * 1000; // 2 hours
@ -195,7 +197,8 @@ fn create_mainnet_genesis() -> Block {
let genesis_timestamp = Timestamp::from_millis(1735689600000); let genesis_timestamp = Timestamp::from_millis(1735689600000);
// Genesis message embedded in coinbase // Genesis message embedded in coinbase
let genesis_message = b"Synor Genesis - The Dawn of Quantum-Secure Decentralized Computing - 2025"; let genesis_message =
b"Synor Genesis - The Dawn of Quantum-Secure Decentralized Computing - 2025";
// Create coinbase transaction // Create coinbase transaction
let coinbase_tx = create_coinbase_transaction( let coinbase_tx = create_coinbase_transaction(
@ -342,24 +345,20 @@ fn genesis_allocation_mainnet() -> Vec<GenesisAllocation> {
/// Returns testnet genesis allocations. /// Returns testnet genesis allocations.
fn genesis_allocation_testnet() -> Vec<GenesisAllocation> { fn genesis_allocation_testnet() -> Vec<GenesisAllocation> {
vec![ vec![GenesisAllocation {
GenesisAllocation {
address_hash: Hash256::from([0x10; 32]), address_hash: Hash256::from([0x10; 32]),
amount: Amount::from_synor(1_000_000), amount: Amount::from_synor(1_000_000),
description: "Testnet Faucet".to_string(), description: "Testnet Faucet".to_string(),
}, }]
]
} }
/// Returns devnet genesis allocations. /// Returns devnet genesis allocations.
fn genesis_allocation_devnet() -> Vec<GenesisAllocation> { fn genesis_allocation_devnet() -> Vec<GenesisAllocation> {
vec![ vec![GenesisAllocation {
GenesisAllocation {
address_hash: Hash256::from([0x20; 32]), address_hash: Hash256::from([0x20; 32]),
amount: Amount::from_synor(100_000_000), amount: Amount::from_synor(100_000_000),
description: "Dev Account".to_string(), description: "Dev Account".to_string(),
}, }]
]
} }
/// Creates a coinbase transaction for genesis. /// Creates a coinbase transaction for genesis.
@ -430,13 +429,11 @@ pub fn mainnet_checkpoints() -> Vec<Checkpoint> {
/// Returns hardcoded checkpoints for testnet. /// Returns hardcoded checkpoints for testnet.
pub fn testnet_checkpoints() -> Vec<Checkpoint> { pub fn testnet_checkpoints() -> Vec<Checkpoint> {
vec![ vec![Checkpoint {
Checkpoint {
blue_score: 0, blue_score: 0,
hash: ChainConfig::testnet().genesis_hash, hash: ChainConfig::testnet().genesis_hash,
timestamp: 1735689600000, timestamp: 1735689600000,
}, }]
]
} }
/// Network magic bytes for message framing. /// Network magic bytes for message framing.

View file

@ -220,7 +220,10 @@ impl FeeDistribution {
let burn = Amount::from_sompi(fee_sompi * 10 / 100); let burn = Amount::from_sompi(fee_sompi * 10 / 100);
let stakers = Amount::from_sompi(fee_sompi * 60 / 100); let stakers = Amount::from_sompi(fee_sompi * 60 / 100);
let community = Amount::from_sompi(fee_sompi * 20 / 100); let community = Amount::from_sompi(fee_sompi * 20 / 100);
let miner = fees.saturating_sub(burn).saturating_sub(stakers).saturating_sub(community); let miner = fees
.saturating_sub(burn)
.saturating_sub(stakers)
.saturating_sub(community);
FeeDistribution { FeeDistribution {
burn, burn,

View file

@ -144,7 +144,10 @@ impl UtxoSet {
/// Creates a UTXO set from existing entries. /// Creates a UTXO set from existing entries.
pub fn from_entries(entries: HashMap<Outpoint, UtxoEntry>) -> Self { pub fn from_entries(entries: HashMap<Outpoint, UtxoEntry>) -> Self {
let total: Amount = entries.values().map(|e| e.amount()).fold(Amount::ZERO, |a, b| a.saturating_add(b)); let total: Amount = entries
.values()
.map(|e| e.amount())
.fold(Amount::ZERO, |a, b| a.saturating_add(b));
let count = entries.len(); let count = entries.len();
UtxoSet { UtxoSet {
utxos: RwLock::new(entries), utxos: RwLock::new(entries),
@ -278,7 +281,11 @@ impl UtxoSet {
} }
/// Gets all UTXOs for a given address. /// Gets all UTXOs for a given address.
pub fn get_by_address(&self, address: &Address, network: synor_types::Network) -> Vec<(Outpoint, UtxoEntry)> { pub fn get_by_address(
&self,
address: &Address,
network: synor_types::Network,
) -> Vec<(Outpoint, UtxoEntry)> {
let utxos = self.utxos.read(); let utxos = self.utxos.read();
let mut result = Vec::new(); let mut result = Vec::new();
@ -410,7 +417,11 @@ impl VirtualUtxoState {
} }
/// Updates the virtual block and applies necessary diffs. /// Updates the virtual block and applies necessary diffs.
pub fn update_virtual(&self, new_virtual: Hash256, chain_diffs: Vec<UtxoDiff>) -> Result<(), UtxoError> { pub fn update_virtual(
&self,
new_virtual: Hash256,
chain_diffs: Vec<UtxoDiff>,
) -> Result<(), UtxoError> {
// Apply all diffs in order // Apply all diffs in order
for diff in chain_diffs { for diff in chain_diffs {
self.base.apply_diff(&diff)?; self.base.apply_diff(&diff)?;
@ -470,10 +481,7 @@ mod tests {
fn make_entry(amount: u64) -> UtxoEntry { fn make_entry(amount: u64) -> UtxoEntry {
UtxoEntry::new( UtxoEntry::new(
TxOutput::new( TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32])),
Amount::from_sompi(amount),
ScriptPubKey::p2pkh(&[0u8; 32]),
),
100, 100,
false, false,
) )
@ -481,10 +489,7 @@ mod tests {
fn make_coinbase_entry(amount: u64, daa_score: u64) -> UtxoEntry { fn make_coinbase_entry(amount: u64, daa_score: u64) -> UtxoEntry {
UtxoEntry::new( UtxoEntry::new(
TxOutput::new( TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32])),
Amount::from_sompi(amount),
ScriptPubKey::p2pkh(&[0u8; 32]),
),
daa_score, daa_score,
true, true,
) )

View file

@ -182,7 +182,11 @@ impl TransactionValidator {
} }
/// Verifies an input's signature script against the UTXO. /// Verifies an input's signature script against the UTXO.
fn verify_input_script(&self, input: &TxInput, utxo: &UtxoEntry) -> Result<(), ValidationError> { fn verify_input_script(
&self,
input: &TxInput,
utxo: &UtxoEntry,
) -> Result<(), ValidationError> {
// Simplified verification - in production would: // Simplified verification - in production would:
// 1. Parse the signature script // 1. Parse the signature script
// 2. Execute against the UTXO's script pubkey // 2. Execute against the UTXO's script pubkey
@ -282,15 +286,16 @@ impl BlockValidator {
} }
/// Validates proof of work. /// Validates proof of work.
pub fn validate_pow(&self, header: &BlockHeader, target: Hash256) -> Result<(), ValidationError> { pub fn validate_pow(
&self,
header: &BlockHeader,
target: Hash256,
) -> Result<(), ValidationError> {
let hash = header.block_id(); let hash = header.block_id();
// Check hash is below target // Check hash is below target
if hash > target { if hash > target {
return Err(ValidationError::InsufficientPow { return Err(ValidationError::InsufficientPow { hash, target });
hash,
target,
});
} }
Ok(()) Ok(())
@ -340,9 +345,11 @@ impl BlockValidator {
// Validate against UTXOs (skip coinbase) // Validate against UTXOs (skip coinbase)
if i > 0 { if i > 0 {
let fee = self let fee = self.tx_validator.validate_against_utxos(
.tx_validator tx,
.validate_against_utxos(tx, utxo_set, block.header.daa_score)?; utxo_set,
block.header.daa_score,
)?;
total_fees = total_fees.saturating_add(fee); total_fees = total_fees.saturating_add(fee);
} }
@ -468,7 +475,10 @@ pub enum ValidationError {
InvalidCoinbaseAmount { output: Amount, max: Amount }, InvalidCoinbaseAmount { output: Amount, max: Amount },
#[error("Invalid merkle root: expected {expected}, computed {computed}")] #[error("Invalid merkle root: expected {expected}, computed {computed}")]
InvalidMerkleRoot { expected: Hash256, computed: Hash256 }, InvalidMerkleRoot {
expected: Hash256,
computed: Hash256,
},
#[error("UTXO error: {0}")] #[error("UTXO error: {0}")]
UtxoError(#[from] UtxoError), UtxoError(#[from] UtxoError),
@ -481,10 +491,7 @@ mod tests {
use synor_types::TxOutput; use synor_types::TxOutput;
fn make_p2pkh_output(amount: u64) -> TxOutput { fn make_p2pkh_output(amount: u64) -> TxOutput {
TxOutput::new( TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32]))
Amount::from_sompi(amount),
ScriptPubKey::p2pkh(&[0u8; 32]),
)
} }
#[test] #[test]

View file

@ -163,7 +163,12 @@ impl TestEnvironment {
} }
// Create execution context // Create execution context
let call = CallContext::new(contract_id, deployer_addr.clone(), value, init_params.to_vec()); let call = CallContext::new(
contract_id,
deployer_addr.clone(),
value,
init_params.to_vec(),
);
let mut storage = MemoryStorage::new(); let mut storage = MemoryStorage::new();
// Copy existing storage for this contract if any // Copy existing storage for this contract if any

View file

@ -100,11 +100,7 @@ impl MockStorage {
/// Gets the number of storage entries for a contract. /// Gets the number of storage entries for a contract.
pub fn len(&self, contract: &ContractId) -> usize { pub fn len(&self, contract: &ContractId) -> usize {
self.data self.data.read().get(contract).map(|m| m.len()).unwrap_or(0)
.read()
.get(contract)
.map(|m| m.len())
.unwrap_or(0)
} }
/// Checks if a contract has any storage. /// Checks if a contract has any storage.
@ -362,7 +358,10 @@ impl StorageSnapshot {
} }
/// Gets storage entries for a contract. /// Gets storage entries for a contract.
pub fn get_contract(&self, contract: &ContractId) -> Option<&HashMap<StorageKey, StorageValue>> { pub fn get_contract(
&self,
contract: &ContractId,
) -> Option<&HashMap<StorageKey, StorageValue>> {
self.data.get(contract) self.data.get(contract)
} }
} }

View file

@ -262,8 +262,7 @@ impl TestAccountBuilder {
/// Adds a new account with a specific balance. /// Adds a new account with a specific balance.
pub fn add_account(mut self, balance: u64) -> Self { pub fn add_account(mut self, balance: u64) -> Self {
let account = TestAccount::from_index(self.accounts.len() as u64) let account = TestAccount::from_index(self.accounts.len() as u64).set_balance(balance);
.set_balance(balance);
self.accounts.push(account); self.accounts.push(account);
self self
} }
@ -271,8 +270,7 @@ impl TestAccountBuilder {
/// Adds multiple accounts with the same balance. /// Adds multiple accounts with the same balance.
pub fn add_accounts(mut self, count: usize, balance: u64) -> Self { pub fn add_accounts(mut self, count: usize, balance: u64) -> Self {
for _ in 0..count { for _ in 0..count {
let account = TestAccount::from_index(self.accounts.len() as u64) let account = TestAccount::from_index(self.accounts.len() as u64).set_balance(balance);
.set_balance(balance);
self.accounts.push(account); self.accounts.push(account);
} }
self self

View file

@ -238,11 +238,7 @@ criterion_group!(
criterion_group!(hash_benches, blake3_hash_benchmark, blake3_hash_large,); criterion_group!(hash_benches, blake3_hash_benchmark, blake3_hash_large,);
criterion_group!( criterion_group!(misc_benches, address_derivation_benchmark, signature_sizes,);
misc_benches,
address_derivation_benchmark,
signature_sizes,
);
criterion_main!( criterion_main!(
keygen_benches, keygen_benches,

View file

@ -58,13 +58,19 @@ pub fn derive_ed25519_key(master_seed: &[u8], account: u32) -> Result<DerivedKey
} }
/// Derives a 32-byte seed for Dilithium key generation. /// Derives a 32-byte seed for Dilithium key generation.
pub fn derive_dilithium_seed(master_seed: &[u8], account: u32) -> Result<DerivedKey, DeriveKeyError> { pub fn derive_dilithium_seed(
master_seed: &[u8],
account: u32,
) -> Result<DerivedKey, DeriveKeyError> {
let info = [domain::DILITHIUM_KEY, &account.to_be_bytes()].concat(); let info = [domain::DILITHIUM_KEY, &account.to_be_bytes()].concat();
derive_key(master_seed, b"", &info, 32) derive_key(master_seed, b"", &info, 32)
} }
/// Derives a 32-byte encryption key. /// Derives a 32-byte encryption key.
pub fn derive_encryption_key(master_seed: &[u8], purpose: &[u8]) -> Result<DerivedKey, DeriveKeyError> { pub fn derive_encryption_key(
master_seed: &[u8],
purpose: &[u8],
) -> Result<DerivedKey, DeriveKeyError> {
let info = [domain::ENCRYPTION_KEY, purpose].concat(); let info = [domain::ENCRYPTION_KEY, purpose].concat();
derive_key(master_seed, b"", &info, 32) derive_key(master_seed, b"", &info, 32)
} }
@ -81,8 +87,8 @@ pub fn derive_child_key(
return Err(DeriveKeyError::InvalidKeyLength); return Err(DeriveKeyError::InvalidKeyLength);
} }
let mut hmac = Hmac::<Sha3_256>::new_from_slice(chain_code) let mut hmac =
.map_err(|_| DeriveKeyError::HmacError)?; Hmac::<Sha3_256>::new_from_slice(chain_code).map_err(|_| DeriveKeyError::HmacError)?;
// For hardened keys (index >= 0x80000000), use parent key // For hardened keys (index >= 0x80000000), use parent key
// For normal keys, use parent public key (not implemented here for simplicity) // For normal keys, use parent public key (not implemented here for simplicity)

View file

@ -3,9 +3,7 @@
//! Provides hybrid keypairs combining Ed25519 and Dilithium3 for quantum resistance. //! Provides hybrid keypairs combining Ed25519 and Dilithium3 for quantum resistance.
use crate::{mnemonic::Mnemonic, signature::HybridSignature, Address, Hash256, Network}; use crate::{mnemonic::Mnemonic, signature::HybridSignature, Address, Hash256, Network};
use ed25519_dalek::{ use ed25519_dalek::{Signer, SigningKey as Ed25519SigningKey, VerifyingKey as Ed25519VerifyingKey};
Signer, SigningKey as Ed25519SigningKey, VerifyingKey as Ed25519VerifyingKey,
};
use pqcrypto_dilithium::dilithium3; use pqcrypto_dilithium::dilithium3;
use pqcrypto_traits::sign::{PublicKey as PqPublicKey, SignedMessage as PqSignedMessage}; use pqcrypto_traits::sign::{PublicKey as PqPublicKey, SignedMessage as PqSignedMessage};
use rand::rngs::OsRng; use rand::rngs::OsRng;
@ -80,7 +78,7 @@ impl Dilithium3Keypair {
/// Creates a keypair from seed bytes using deterministic key generation. /// Creates a keypair from seed bytes using deterministic key generation.
pub fn from_seed(seed: &[u8; 32]) -> Self { pub fn from_seed(seed: &[u8; 32]) -> Self {
// Use SHAKE256 to expand seed to required size // Use SHAKE256 to expand seed to required size
use sha3::{Shake256, digest::Update}; use sha3::{digest::Update, Shake256};
let mut hasher = Shake256::default(); let mut hasher = Shake256::default();
hasher.update(b"synor-dilithium3-keygen"); hasher.update(b"synor-dilithium3-keygen");
@ -170,7 +168,8 @@ impl PublicKey {
let ed_verifying_key = Ed25519VerifyingKey::from_bytes(&self.ed25519) let ed_verifying_key = Ed25519VerifyingKey::from_bytes(&self.ed25519)
.map_err(|_| KeypairError::InvalidPublicKey)?; .map_err(|_| KeypairError::InvalidPublicKey)?;
let ed_sig_array = signature.ed25519_array() let ed_sig_array = signature
.ed25519_array()
.ok_or(KeypairError::InvalidSignature)?; .ok_or(KeypairError::InvalidSignature)?;
let ed_sig = ed25519_dalek::Signature::from_bytes(&ed_sig_array); let ed_sig = ed25519_dalek::Signature::from_bytes(&ed_sig_array);
ed_verifying_key ed_verifying_key

View file

@ -87,15 +87,15 @@
#![allow(dead_code)] #![allow(dead_code)]
pub mod keypair;
pub mod signature;
pub mod mnemonic;
pub mod kdf; pub mod kdf;
pub mod keypair;
pub mod mnemonic;
pub mod signature;
pub use keypair::{HybridKeypair, Ed25519Keypair, PublicKey, SecretKey};
pub use signature::{HybridSignature, Signature, SignatureError};
pub use mnemonic::{Mnemonic, MnemonicError};
pub use kdf::{derive_key, DeriveKeyError}; pub use kdf::{derive_key, DeriveKeyError};
pub use keypair::{Ed25519Keypair, HybridKeypair, PublicKey, SecretKey};
pub use mnemonic::{Mnemonic, MnemonicError};
pub use signature::{HybridSignature, Signature, SignatureError};
/// Re-export common types /// Re-export common types
pub use synor_types::{Address, Hash256, Network}; pub use synor_types::{Address, Hash256, Network};

View file

@ -94,7 +94,13 @@ impl std::fmt::Display for Mnemonic {
// Only show first and last word for security // Only show first and last word for security
let words: Vec<&str> = self.words(); let words: Vec<&str> = self.words();
if words.len() >= 2 { if words.len() >= 2 {
write!(f, "{} ... {} ({} words)", words[0], words[words.len() - 1], words.len()) write!(
f,
"{} ... {} ({} words)",
words[0],
words[words.len() - 1],
words.len()
)
} else { } else {
write!(f, "[invalid mnemonic]") write!(f, "[invalid mnemonic]")
} }

View file

@ -11,9 +11,7 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::sync::Arc; use std::sync::Arc;
use synor_dag::{ use synor_dag::{BlockDag, BlockId, GhostdagManager, ReachabilityStore, GHOSTDAG_K};
BlockDag, BlockId, GhostdagManager, ReachabilityStore, GHOSTDAG_K,
};
use synor_types::Hash256; use synor_types::Hash256;
/// Helper to create deterministic block IDs. /// Helper to create deterministic block IDs.
@ -46,7 +44,8 @@ fn build_linear_chain(
let block = make_block_id(i as u64); let block = make_block_id(i as u64);
let parent = blocks[i - 1]; let parent = blocks[i - 1];
dag.insert_block(block, vec![parent], i as u64 * 100).unwrap(); dag.insert_block(block, vec![parent], i as u64 * 100)
.unwrap();
reachability.add_block(block, parent, &[parent]).unwrap(); reachability.add_block(block, parent, &[parent]).unwrap();
ghostdag.add_block(block, &[parent]).unwrap(); ghostdag.add_block(block, &[parent]).unwrap();
blocks.push(block); blocks.push(block);
@ -77,8 +76,11 @@ fn build_wide_dag(
let parents: Vec<_> = layers[d - 1].iter().copied().collect(); let parents: Vec<_> = layers[d - 1].iter().copied().collect();
let selected_parent = parents[0]; let selected_parent = parents[0];
dag.insert_block(block, parents.clone(), block_num * 100).unwrap(); dag.insert_block(block, parents.clone(), block_num * 100)
reachability.add_block(block, selected_parent, &parents).unwrap(); .unwrap();
reachability
.add_block(block, selected_parent, &parents)
.unwrap();
ghostdag.add_block(block, &parents).unwrap(); ghostdag.add_block(block, &parents).unwrap();
layer.push(block); layer.push(block);
@ -113,8 +115,11 @@ fn block_insertion_linear(c: &mut Criterion) {
let new_block = make_block_id(n as u64); let new_block = make_block_id(n as u64);
let parent = *blocks.last().unwrap(); let parent = *blocks.last().unwrap();
dag.insert_block(new_block, vec![parent], n as u64 * 100).unwrap(); dag.insert_block(new_block, vec![parent], n as u64 * 100)
reachability.add_block(new_block, parent, &[parent]).unwrap(); .unwrap();
reachability
.add_block(new_block, parent, &[parent])
.unwrap();
black_box(ghostdag.add_block(new_block, &[parent]).unwrap()) black_box(ghostdag.add_block(new_block, &[parent]).unwrap())
}, },
criterion::BatchSize::SmallInput, criterion::BatchSize::SmallInput,
@ -146,8 +151,11 @@ fn block_insertion_wide(c: &mut Criterion) {
let parents: Vec<_> = layers.last().unwrap().iter().copied().collect(); let parents: Vec<_> = layers.last().unwrap().iter().copied().collect();
let selected_parent = parents[0]; let selected_parent = parents[0];
dag.insert_block(new_block, parents.clone(), 99999 * 100).unwrap(); dag.insert_block(new_block, parents.clone(), 99999 * 100)
reachability.add_block(new_block, selected_parent, &parents).unwrap(); .unwrap();
reachability
.add_block(new_block, selected_parent, &parents)
.unwrap();
black_box(ghostdag.add_block(new_block, &parents).unwrap()) black_box(ghostdag.add_block(new_block, &parents).unwrap())
}, },
criterion::BatchSize::SmallInput, criterion::BatchSize::SmallInput,
@ -269,8 +277,11 @@ fn merge_set_calculation(c: &mut Criterion) {
let parents: Vec<_> = layers.last().unwrap().iter().copied().collect(); let parents: Vec<_> = layers.last().unwrap().iter().copied().collect();
let selected_parent = parents[0]; let selected_parent = parents[0];
dag.insert_block(new_block, parents.clone(), 99999 * 100).unwrap(); dag.insert_block(new_block, parents.clone(), 99999 * 100)
reachability.add_block(new_block, selected_parent, &parents).unwrap(); .unwrap();
reachability
.add_block(new_block, selected_parent, &parents)
.unwrap();
let data = ghostdag.add_block(new_block, &parents).unwrap(); let data = ghostdag.add_block(new_block, &parents).unwrap();
black_box(data.merge_set_size()) black_box(data.merge_set_size())
}, },
@ -294,7 +305,8 @@ fn k_cluster_validation(c: &mut Criterion) {
let genesis = make_block_id(0); let genesis = make_block_id(0);
let dag = Arc::new(BlockDag::new(genesis, 0)); let dag = Arc::new(BlockDag::new(genesis, 0));
let reachability = Arc::new(ReachabilityStore::new(genesis)); let reachability = Arc::new(ReachabilityStore::new(genesis));
let ghostdag = GhostdagManager::with_k(dag.clone(), reachability.clone(), k_val); let ghostdag =
GhostdagManager::with_k(dag.clone(), reachability.clone(), k_val);
build_wide_dag(5, k_val as usize, &dag, &reachability, &ghostdag); build_wide_dag(5, k_val as usize, &dag, &reachability, &ghostdag);
(dag, reachability, ghostdag) (dag, reachability, ghostdag)
}, },
@ -354,9 +366,7 @@ fn dag_tips_query(c: &mut Criterion) {
let (dag, reachability, ghostdag) = setup_dag(); let (dag, reachability, ghostdag) = setup_dag();
build_wide_dag(10, 4, &dag, &reachability, &ghostdag); build_wide_dag(10, 4, &dag, &reachability, &ghostdag);
c.bench_function("dag_get_tips", |b| { c.bench_function("dag_get_tips", |b| b.iter(|| black_box(dag.tips())));
b.iter(|| black_box(dag.tips()))
});
} }
// ==================== Criterion Groups ==================== // ==================== Criterion Groups ====================
@ -367,16 +377,9 @@ criterion_group!(
block_insertion_wide, block_insertion_wide,
); );
criterion_group!( criterion_group!(score_benches, blue_score_calculation, blue_score_batch,);
score_benches,
blue_score_calculation,
blue_score_batch,
);
criterion_group!( criterion_group!(chain_benches, selected_chain_traversal,);
chain_benches,
selected_chain_traversal,
);
criterion_group!( criterion_group!(
reachability_benches, reachability_benches,
@ -384,11 +387,7 @@ criterion_group!(
reachability_anticone_check, reachability_anticone_check,
); );
criterion_group!( criterion_group!(merge_benches, merge_set_calculation, k_cluster_validation,);
merge_benches,
merge_set_calculation,
k_cluster_validation,
);
criterion_group!( criterion_group!(
data_access_benches, data_access_benches,

View file

@ -449,8 +449,7 @@ mod tests {
let dag = BlockDag::new(genesis_id, 0); let dag = BlockDag::new(genesis_id, 0);
let block1 = make_block_id(1); let block1 = make_block_id(1);
dag.insert_block(block1, vec![genesis_id], 100) dag.insert_block(block1, vec![genesis_id], 100).unwrap();
.unwrap();
assert_eq!(dag.len(), 2); assert_eq!(dag.len(), 2);
assert!(dag.contains(&block1)); assert!(dag.contains(&block1));
@ -471,8 +470,7 @@ mod tests {
// Add a child block // Add a child block
let block1 = make_block_id(1); let block1 = make_block_id(1);
dag.insert_block(block1, vec![genesis_id], 100) dag.insert_block(block1, vec![genesis_id], 100).unwrap();
.unwrap();
// Now only block1 is a tip // Now only block1 is a tip
assert_eq!(dag.tips().len(), 1); assert_eq!(dag.tips().len(), 1);
@ -481,8 +479,7 @@ mod tests {
// Add another block with genesis as parent (parallel mining) // Add another block with genesis as parent (parallel mining)
let block2 = make_block_id(2); let block2 = make_block_id(2);
dag.insert_block(block2, vec![genesis_id], 100) dag.insert_block(block2, vec![genesis_id], 100).unwrap();
.unwrap();
// Now we have two tips // Now we have two tips
assert_eq!(dag.tips().len(), 2); assert_eq!(dag.tips().len(), 2);
@ -498,15 +495,12 @@ mod tests {
let block1 = make_block_id(1); let block1 = make_block_id(1);
let block2 = make_block_id(2); let block2 = make_block_id(2);
dag.insert_block(block1, vec![genesis_id], 100) dag.insert_block(block1, vec![genesis_id], 100).unwrap();
.unwrap(); dag.insert_block(block2, vec![genesis_id], 100).unwrap();
dag.insert_block(block2, vec![genesis_id], 100)
.unwrap();
// Block with multiple parents // Block with multiple parents
let block3 = make_block_id(3); let block3 = make_block_id(3);
dag.insert_block(block3, vec![block1, block2], 200) dag.insert_block(block3, vec![block1, block2], 200).unwrap();
.unwrap();
let parents = dag.get_parents(&block3).unwrap(); let parents = dag.get_parents(&block3).unwrap();
assert_eq!(parents.len(), 2); assert_eq!(parents.len(), 2);
@ -523,12 +517,9 @@ mod tests {
let block2 = make_block_id(2); let block2 = make_block_id(2);
let block3 = make_block_id(3); let block3 = make_block_id(3);
dag.insert_block(block1, vec![genesis_id], 100) dag.insert_block(block1, vec![genesis_id], 100).unwrap();
.unwrap(); dag.insert_block(block2, vec![block1], 200).unwrap();
dag.insert_block(block2, vec![block1], 200) dag.insert_block(block3, vec![block2], 300).unwrap();
.unwrap();
dag.insert_block(block3, vec![block2], 300)
.unwrap();
let ancestors = dag.get_ancestors(&block3, 10); let ancestors = dag.get_ancestors(&block3, 10);
assert!(ancestors.contains(&block2)); assert!(ancestors.contains(&block2));
@ -542,8 +533,7 @@ mod tests {
let dag = BlockDag::new(genesis_id, 0); let dag = BlockDag::new(genesis_id, 0);
let block1 = make_block_id(1); let block1 = make_block_id(1);
dag.insert_block(block1, vec![genesis_id], 100) dag.insert_block(block1, vec![genesis_id], 100).unwrap();
.unwrap();
// Try to insert same block again // Try to insert same block again
let result = dag.insert_block(block1, vec![genesis_id], 100); let result = dag.insert_block(block1, vec![genesis_id], 100);
@ -571,12 +561,9 @@ mod tests {
let block2 = make_block_id(2); let block2 = make_block_id(2);
let block3 = make_block_id(3); let block3 = make_block_id(3);
dag.insert_block(block1, vec![genesis_id], 100) dag.insert_block(block1, vec![genesis_id], 100).unwrap();
.unwrap(); dag.insert_block(block2, vec![block1], 200).unwrap();
dag.insert_block(block2, vec![block1], 200) dag.insert_block(block3, vec![block2], 300).unwrap();
.unwrap();
dag.insert_block(block3, vec![block2], 300)
.unwrap();
let order = dag.iter_topological(); let order = dag.iter_topological();

View file

@ -264,8 +264,9 @@ impl GhostdagManager {
) -> Result<(Vec<BlockId>, Vec<BlockId>, HashMap<BlockId, usize>), GhostdagError> { ) -> Result<(Vec<BlockId>, Vec<BlockId>, HashMap<BlockId, usize>), GhostdagError> {
let mut blues: Vec<BlockId> = Vec::with_capacity(merge_set.len()); let mut blues: Vec<BlockId> = Vec::with_capacity(merge_set.len());
let mut reds: Vec<BlockId> = Vec::new(); let mut reds: Vec<BlockId> = Vec::new();
let mut blues_anticone_sizes: HashMap<BlockId, usize> = let mut blues_anticone_sizes: HashMap<BlockId, usize> = HashMap::with_capacity(
HashMap::with_capacity(merge_set.len() + selected_parent_data.blues_anticone_sizes.len()); merge_set.len() + selected_parent_data.blues_anticone_sizes.len(),
);
// Initialize with selected parent's blues anticone sizes // Initialize with selected parent's blues anticone sizes
blues_anticone_sizes.extend(selected_parent_data.blues_anticone_sizes.iter()); blues_anticone_sizes.extend(selected_parent_data.blues_anticone_sizes.iter());
@ -378,7 +379,8 @@ impl GhostdagManager {
new_blue: &BlockId, new_blue: &BlockId,
current_blues: &[BlockId], current_blues: &[BlockId],
) -> Result<bool, GhostdagError> { ) -> Result<bool, GhostdagError> {
let (_, anticone_blues) = self.calculate_blues_anticone_optimized(new_blue, current_blues)?; let (_, anticone_blues) =
self.calculate_blues_anticone_optimized(new_blue, current_blues)?;
// Build a temporary anticone sizes map for the check // Build a temporary anticone sizes map for the check
let mut temp_sizes = HashMap::new(); let mut temp_sizes = HashMap::new();
for blue in current_blues { for blue in current_blues {
@ -483,7 +485,11 @@ mod tests {
Hash256::from_bytes(bytes) Hash256::from_bytes(bytes)
} }
fn setup_test_dag() -> (std::sync::Arc<BlockDag>, std::sync::Arc<ReachabilityStore>, GhostdagManager) { fn setup_test_dag() -> (
std::sync::Arc<BlockDag>,
std::sync::Arc<ReachabilityStore>,
GhostdagManager,
) {
let genesis = make_block_id(0); let genesis = make_block_id(0);
let dag = std::sync::Arc::new(BlockDag::new(genesis, 0)); let dag = std::sync::Arc::new(BlockDag::new(genesis, 0));
let reachability = std::sync::Arc::new(ReachabilityStore::new(genesis)); let reachability = std::sync::Arc::new(ReachabilityStore::new(genesis));
@ -550,7 +556,9 @@ mod tests {
// Now create a block with both branches as parents // Now create a block with both branches as parents
let block4 = make_block_id(4); let block4 = make_block_id(4);
dag.insert_block(block4, vec![block3, block2], 300).unwrap(); dag.insert_block(block4, vec![block3, block2], 300).unwrap();
reachability.add_block(block4, block3, &[block3, block2]).unwrap(); reachability
.add_block(block4, block3, &[block3, block2])
.unwrap();
let data4 = ghostdag.add_block(block4, &[block3, block2]).unwrap(); let data4 = ghostdag.add_block(block4, &[block3, block2]).unwrap();
// Block3 should be selected parent (higher chain) // Block3 should be selected parent (higher chain)
@ -565,7 +573,8 @@ mod tests {
let mut current = genesis; let mut current = genesis;
for i in 1..=5 { for i in 1..=5 {
let block = make_block_id(i); let block = make_block_id(i);
dag.insert_block(block, vec![current], i as u64 * 100).unwrap(); dag.insert_block(block, vec![current], i as u64 * 100)
.unwrap();
reachability.add_block(block, current, &[current]).unwrap(); reachability.add_block(block, current, &[current]).unwrap();
let data = ghostdag.add_block(block, &[current]).unwrap(); let data = ghostdag.add_block(block, &[current]).unwrap();

View file

@ -23,16 +23,16 @@
#![allow(dead_code)] #![allow(dead_code)]
pub mod dag; pub mod dag;
pub mod reachability;
pub mod ghostdag; pub mod ghostdag;
pub mod ordering; pub mod ordering;
pub mod pruning; pub mod pruning;
pub mod reachability;
pub use dag::{BlockDag, DagError, BlockRelations}; pub use dag::{BlockDag, BlockRelations, DagError};
pub use reachability::{ReachabilityStore, ReachabilityError}; pub use ghostdag::{GhostdagData, GhostdagError, GhostdagManager};
pub use ghostdag::{GhostdagManager, GhostdagData, GhostdagError};
pub use ordering::{BlockOrdering, OrderedBlock}; pub use ordering::{BlockOrdering, OrderedBlock};
pub use pruning::{PruningManager, PruningConfig}; pub use pruning::{PruningConfig, PruningManager};
pub use reachability::{ReachabilityError, ReachabilityStore};
use synor_types::Hash256; use synor_types::Hash256;

View file

@ -8,10 +8,7 @@
//! 3. Within the merge set, blues come before reds //! 3. Within the merge set, blues come before reds
//! 4. Within blues/reds, ordering is by blue score then hash //! 4. Within blues/reds, ordering is by blue score then hash
use crate::{ use crate::{ghostdag::GhostdagManager, BlockId, BlueScore};
ghostdag::GhostdagManager,
BlockId, BlueScore,
};
use std::cmp::Ordering; use std::cmp::Ordering;
use thiserror::Error; use thiserror::Error;
@ -122,11 +119,7 @@ impl BlockOrdering {
// Add sorted merge blocks // Add sorted merge blocks
for (block_id, blue_score, is_blue) in merge_blocks { for (block_id, blue_score, is_blue) in merge_blocks {
ordering.push(OrderedBlock::new( ordering.push(OrderedBlock::new(
block_id, block_id, position, blue_score, is_blue, true, // Is a merge block
position,
blue_score,
is_blue,
true, // Is a merge block
)); ));
position += 1; position += 1;
} }
@ -138,7 +131,11 @@ impl BlockOrdering {
/// Gets only the blocks that need to be processed between two points. /// Gets only the blocks that need to be processed between two points.
/// ///
/// Returns blocks that are in `to`'s past but not in `from`'s past. /// Returns blocks that are in `to`'s past but not in `from`'s past.
pub fn get_diff(&self, from: &BlockId, to: &BlockId) -> Result<Vec<OrderedBlock>, OrderingError> { pub fn get_diff(
&self,
from: &BlockId,
to: &BlockId,
) -> Result<Vec<OrderedBlock>, OrderingError> {
let from_ordering = self.get_ordering(from)?; let from_ordering = self.get_ordering(from)?;
let to_ordering = self.get_ordering(to)?; let to_ordering = self.get_ordering(to)?;
@ -171,7 +168,10 @@ impl BlockOrdering {
} }
/// Gets the merge set at a specific block in canonical order. /// Gets the merge set at a specific block in canonical order.
pub fn get_merge_set_ordered(&self, block_id: &BlockId) -> Result<Vec<OrderedBlock>, OrderingError> { pub fn get_merge_set_ordered(
&self,
block_id: &BlockId,
) -> Result<Vec<OrderedBlock>, OrderingError> {
let data = self.ghostdag.get_data(block_id)?; let data = self.ghostdag.get_data(block_id)?;
let mut merge_blocks = Vec::new(); let mut merge_blocks = Vec::new();
let mut position = 0u64; let mut position = 0u64;
@ -191,15 +191,13 @@ impl BlockOrdering {
} }
// Sort by blue score and hash // Sort by blue score and hash
merge_blocks.sort_by(|a, b| { merge_blocks.sort_by(|a, b| match (a.is_blue, b.is_blue) {
match (a.is_blue, b.is_blue) {
(true, false) => Ordering::Less, (true, false) => Ordering::Less,
(false, true) => Ordering::Greater, (false, true) => Ordering::Greater,
_ => match b.blue_score.cmp(&a.blue_score) { _ => match b.blue_score.cmp(&a.blue_score) {
Ordering::Equal => a.id.cmp(&b.id), Ordering::Equal => a.id.cmp(&b.id),
other => other, other => other,
}, },
}
}); });
// Update positions after sorting // Update positions after sorting
@ -333,10 +331,8 @@ mod tests {
let block1 = make_block_id(1); let block1 = make_block_id(1);
let block2 = make_block_id(2); let block2 = make_block_id(2);
dag.insert_block(block1, vec![genesis], 100) dag.insert_block(block1, vec![genesis], 100).unwrap();
.unwrap(); dag.insert_block(block2, vec![block1], 200).unwrap();
dag.insert_block(block2, vec![block1], 200)
.unwrap();
reachability.add_block(block1, genesis, &[genesis]).unwrap(); reachability.add_block(block1, genesis, &[genesis]).unwrap();
reachability.add_block(block2, block1, &[block1]).unwrap(); reachability.add_block(block2, block1, &[block1]).unwrap();
@ -361,10 +357,8 @@ mod tests {
let block1 = make_block_id(1); let block1 = make_block_id(1);
let block2 = make_block_id(2); let block2 = make_block_id(2);
dag.insert_block(block1, vec![genesis], 100) dag.insert_block(block1, vec![genesis], 100).unwrap();
.unwrap(); dag.insert_block(block2, vec![block1], 200).unwrap();
dag.insert_block(block2, vec![block1], 200)
.unwrap();
reachability.add_block(block1, genesis, &[genesis]).unwrap(); reachability.add_block(block1, genesis, &[genesis]).unwrap();
reachability.add_block(block2, block1, &[block1]).unwrap(); reachability.add_block(block2, block1, &[block1]).unwrap();
@ -383,8 +377,7 @@ mod tests {
let (dag, reachability, ghostdag, ordering) = setup_test(); let (dag, reachability, ghostdag, ordering) = setup_test();
let block1 = make_block_id(1); let block1 = make_block_id(1);
dag.insert_block(block1, vec![genesis], 100) dag.insert_block(block1, vec![genesis], 100).unwrap();
.unwrap();
reachability.add_block(block1, genesis, &[genesis]).unwrap(); reachability.add_block(block1, genesis, &[genesis]).unwrap();
ghostdag.add_block(block1, &[genesis]).unwrap(); ghostdag.add_block(block1, &[genesis]).unwrap();

View file

@ -114,7 +114,11 @@ impl ReachabilityStore {
/// Checks if block `ancestor` is in the past of block `descendant`. /// Checks if block `ancestor` is in the past of block `descendant`.
/// Returns true if ancestor is an ancestor of descendant. /// Returns true if ancestor is an ancestor of descendant.
pub fn is_ancestor(&self, ancestor: &BlockId, descendant: &BlockId) -> Result<bool, ReachabilityError> { pub fn is_ancestor(
&self,
ancestor: &BlockId,
descendant: &BlockId,
) -> Result<bool, ReachabilityError> {
if ancestor == descendant { if ancestor == descendant {
return Ok(true); // A block is its own ancestor return Ok(true); // A block is its own ancestor
} }
@ -132,7 +136,11 @@ impl ReachabilityStore {
} }
/// Checks if two blocks are in each other's anticone (neither is ancestor of other). /// Checks if two blocks are in each other's anticone (neither is ancestor of other).
pub fn is_anticone(&self, block_a: &BlockId, block_b: &BlockId) -> Result<bool, ReachabilityError> { pub fn is_anticone(
&self,
block_a: &BlockId,
block_b: &BlockId,
) -> Result<bool, ReachabilityError> {
if block_a == block_b { if block_a == block_b {
return Ok(false); return Ok(false);
} }
@ -171,11 +179,8 @@ impl ReachabilityStore {
let (new_interval, parent_needs_realloc) = self.allocate_interval(&parent_data, &data)?; let (new_interval, parent_needs_realloc) = self.allocate_interval(&parent_data, &data)?;
// Create new block's data // Create new block's data
let new_data = ReachabilityData::new( let new_data =
new_interval, ReachabilityData::new(new_interval, Some(selected_parent), parent_data.height + 1);
Some(selected_parent),
parent_data.height + 1,
);
// Update parent's tree children // Update parent's tree children
if let Some(parent) = data.get_mut(&selected_parent) { if let Some(parent) = data.get_mut(&selected_parent) {

View file

@ -32,8 +32,8 @@
//! ``` //! ```
use borsh::{BorshDeserialize, BorshSerialize}; use borsh::{BorshDeserialize, BorshSerialize};
use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use synor_types::{Address, Hash256}; use synor_types::{Address, Hash256};
use thiserror::Error; use thiserror::Error;
@ -145,10 +145,7 @@ pub enum ProposalType {
options: Vec<String>, options: Vec<String>,
}, },
/// Custom proposal with arbitrary data. /// Custom proposal with arbitrary data.
Custom { Custom { action_type: String, data: Vec<u8> },
action_type: String,
data: Vec<u8>,
},
} }
/// Council member action. /// Council member action.
@ -160,7 +157,9 @@ pub enum CouncilAction {
} }
/// State of a proposal. /// State of a proposal.
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] #[derive(
Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
)]
pub enum ProposalState { pub enum ProposalState {
/// Proposal is pending (voting hasn't started). /// Proposal is pending (voting hasn't started).
Pending, Pending,
@ -183,13 +182,18 @@ impl ProposalState {
pub fn is_final(&self) -> bool { pub fn is_final(&self) -> bool {
matches!( matches!(
self, self,
ProposalState::Executed | ProposalState::Cancelled | ProposalState::Expired | ProposalState::Defeated ProposalState::Executed
| ProposalState::Cancelled
| ProposalState::Expired
| ProposalState::Defeated
) )
} }
} }
/// Vote choice. /// Vote choice.
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] #[derive(
Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
)]
pub enum VoteChoice { pub enum VoteChoice {
/// Vote in favor. /// Vote in favor.
Yes, Yes,
@ -401,7 +405,9 @@ impl Proposal {
match self.state { match self.state {
ProposalState::Pending => Some(self.voting_starts_block.saturating_sub(current_block)), ProposalState::Pending => Some(self.voting_starts_block.saturating_sub(current_block)),
ProposalState::Active => Some(self.voting_ends_block.saturating_sub(current_block)), ProposalState::Active => Some(self.voting_ends_block.saturating_sub(current_block)),
ProposalState::Passed => Some(self.execution_allowed_block.saturating_sub(current_block)), ProposalState::Passed => {
Some(self.execution_allowed_block.saturating_sub(current_block))
}
_ => None, _ => None,
} }
} }
@ -581,7 +587,8 @@ impl DAO {
current_block: u64, current_block: u64,
reason: Option<String>, reason: Option<String>,
) -> Result<(), DaoError> { ) -> Result<(), DaoError> {
let proposal = self.proposals let proposal = self
.proposals
.get_mut(proposal_id) .get_mut(proposal_id)
.ok_or(DaoError::ProposalNotFound)?; .ok_or(DaoError::ProposalNotFound)?;
@ -644,7 +651,8 @@ impl DAO {
_executor: &Address, _executor: &Address,
current_block: u64, current_block: u64,
) -> Result<&Proposal, DaoError> { ) -> Result<&Proposal, DaoError> {
let proposal = self.proposals let proposal = self
.proposals
.get_mut(proposal_id) .get_mut(proposal_id)
.ok_or(DaoError::ProposalNotFound)?; .ok_or(DaoError::ProposalNotFound)?;
@ -683,7 +691,8 @@ impl DAO {
proposal_id: &ProposalId, proposal_id: &ProposalId,
canceller: &Address, canceller: &Address,
) -> Result<(), DaoError> { ) -> Result<(), DaoError> {
let proposal = self.proposals let proposal = self
.proposals
.get_mut(proposal_id) .get_mut(proposal_id)
.ok_or(DaoError::ProposalNotFound)?; .ok_or(DaoError::ProposalNotFound)?;
@ -713,11 +722,7 @@ impl DAO {
pub fn get_by_proposer(&self, proposer: &Address) -> Vec<&Proposal> { pub fn get_by_proposer(&self, proposer: &Address) -> Vec<&Proposal> {
self.by_proposer self.by_proposer
.get(proposer) .get(proposer)
.map(|ids| { .map(|ids| ids.iter().filter_map(|id| self.proposals.get(id)).collect())
ids.iter()
.filter_map(|id| self.proposals.get(id))
.collect()
})
.unwrap_or_default() .unwrap_or_default()
} }
@ -726,8 +731,8 @@ impl DAO {
self.proposals self.proposals
.values() .values()
.filter(|p| { .filter(|p| {
p.state == ProposalState::Active || p.state == ProposalState::Active
(p.state == ProposalState::Pending && current_block >= p.voting_starts_block) || (p.state == ProposalState::Pending && current_block >= p.voting_starts_block)
}) })
.collect() .collect()
} }
@ -753,10 +758,22 @@ impl DAO {
DaoStats { DaoStats {
total_proposals: proposals.len() as u64, total_proposals: proposals.len() as u64,
active_proposals: proposals.iter().filter(|p| p.state == ProposalState::Active).count() as u64, active_proposals: proposals
passed_proposals: proposals.iter().filter(|p| p.state == ProposalState::Passed || p.state == ProposalState::Executed).count() as u64, .iter()
defeated_proposals: proposals.iter().filter(|p| p.state == ProposalState::Defeated).count() as u64, .filter(|p| p.state == ProposalState::Active)
executed_proposals: proposals.iter().filter(|p| p.state == ProposalState::Executed).count() as u64, .count() as u64,
passed_proposals: proposals
.iter()
.filter(|p| p.state == ProposalState::Passed || p.state == ProposalState::Executed)
.count() as u64,
defeated_proposals: proposals
.iter()
.filter(|p| p.state == ProposalState::Defeated)
.count() as u64,
executed_proposals: proposals
.iter()
.filter(|p| p.state == ProposalState::Executed)
.count() as u64,
total_votes_cast: proposals.iter().map(|p| p.votes.len()).sum::<usize>() as u64, total_votes_cast: proposals.iter().map(|p| p.votes.len()).sum::<usize>() as u64,
council_members: self.council.len(), council_members: self.council.len(),
} }
@ -797,7 +814,8 @@ mod tests {
let mut dao = DAO::new(VotingConfig::fast()); let mut dao = DAO::new(VotingConfig::fast());
let proposer = test_address(1); let proposer = test_address(1);
let id = dao.create_proposal( let id = dao
.create_proposal(
proposer, proposer,
1_000_000 * 100_000_000, // 1M tokens 1_000_000 * 100_000_000, // 1M tokens
ProposalType::Signaling { ProposalType::Signaling {
@ -809,7 +827,8 @@ mod tests {
"This is a test".to_string(), "This is a test".to_string(),
100, 100,
70_000_000 * 100_000_000, 70_000_000 * 100_000_000,
).unwrap(); )
.unwrap();
let proposal = dao.get_proposal(&id).unwrap(); let proposal = dao.get_proposal(&id).unwrap();
assert_eq!(proposal.state, ProposalState::Pending); assert_eq!(proposal.state, ProposalState::Pending);
@ -845,7 +864,8 @@ mod tests {
let voter2 = test_address(3); let voter2 = test_address(3);
// Create proposal // Create proposal
let id = dao.create_proposal( let id = dao
.create_proposal(
proposer, proposer,
1_000_000 * 100_000_000, 1_000_000 * 100_000_000,
ProposalType::Signaling { ProposalType::Signaling {
@ -857,15 +877,25 @@ mod tests {
"Test".to_string(), "Test".to_string(),
0, 0,
70_000_000 * 100_000_000, 70_000_000 * 100_000_000,
).unwrap(); )
.unwrap();
// Try voting before start // Try voting before start
let result = dao.vote(&id, voter1.clone(), 100_000, VoteChoice::Yes, 5, None); let result = dao.vote(&id, voter1.clone(), 100_000, VoteChoice::Yes, 5, None);
assert!(matches!(result, Err(DaoError::VotingNotStarted))); assert!(matches!(result, Err(DaoError::VotingNotStarted)));
// Vote after start // Vote after start
dao.vote(&id, voter1.clone(), 1_000_000_000, VoteChoice::Yes, 15, None).unwrap(); dao.vote(
dao.vote(&id, voter2, 500_000_000, VoteChoice::No, 16, None).unwrap(); &id,
voter1.clone(),
1_000_000_000,
VoteChoice::Yes,
15,
None,
)
.unwrap();
dao.vote(&id, voter2, 500_000_000, VoteChoice::No, 16, None)
.unwrap();
let proposal = dao.get_proposal(&id).unwrap(); let proposal = dao.get_proposal(&id).unwrap();
assert_eq!(proposal.votes.len(), 2); assert_eq!(proposal.votes.len(), 2);
@ -888,7 +918,8 @@ mod tests {
let mut dao = DAO::new(VotingConfig::fast()); let mut dao = DAO::new(VotingConfig::fast());
let proposer = test_address(1); let proposer = test_address(1);
let id = dao.create_proposal( let id = dao
.create_proposal(
proposer.clone(), proposer.clone(),
1_000_000 * 100_000_000, 1_000_000 * 100_000_000,
ProposalType::Signaling { ProposalType::Signaling {
@ -900,10 +931,12 @@ mod tests {
"Test".to_string(), "Test".to_string(),
0, 0,
100 * 100_000_000, // Small total supply for easy quorum 100 * 100_000_000, // Small total supply for easy quorum
).unwrap(); )
.unwrap();
// Vote with more than quorum // Vote with more than quorum
dao.vote(&id, proposer, 10 * 100_000_000, VoteChoice::Yes, 15, None).unwrap(); dao.vote(&id, proposer, 10 * 100_000_000, VoteChoice::Yes, 15, None)
.unwrap();
// Update state after voting ends // Update state after voting ends
dao.update_all_states(200); dao.update_all_states(200);
@ -920,7 +953,8 @@ mod tests {
dao.set_guardian(guardian.clone()); dao.set_guardian(guardian.clone());
let id = dao.create_proposal( let id = dao
.create_proposal(
proposer, proposer,
1_000_000 * 100_000_000, 1_000_000 * 100_000_000,
ProposalType::Signaling { ProposalType::Signaling {
@ -932,7 +966,8 @@ mod tests {
"Test".to_string(), "Test".to_string(),
0, 0,
70_000_000 * 100_000_000, 70_000_000 * 100_000_000,
).unwrap(); )
.unwrap();
// Guardian cancels // Guardian cancels
dao.cancel(&id, &guardian).unwrap(); dao.cancel(&id, &guardian).unwrap();

View file

@ -36,8 +36,8 @@ pub mod treasury;
pub mod vesting; pub mod vesting;
pub use dao::{ pub use dao::{
DaoStats, Proposal, ProposalId, ProposalState, ProposalSummary, ProposalType, Vote, DaoStats, Proposal, ProposalId, ProposalState, ProposalSummary, ProposalType, Vote, VoteChoice,
VoteChoice, VotingConfig, VotingPower, DAO, VotingConfig, VotingPower, DAO,
}; };
pub use multisig::{ pub use multisig::{
MultisigConfig, MultisigTransaction, MultisigTxId, MultisigTxState, MultisigWallet, MultisigConfig, MultisigTransaction, MultisigTxId, MultisigTxState, MultisigWallet,

View file

@ -32,8 +32,8 @@
//! ``` //! ```
use borsh::{BorshDeserialize, BorshSerialize}; use borsh::{BorshDeserialize, BorshSerialize};
use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use synor_types::{Address, Hash256}; use synor_types::{Address, Hash256};
use thiserror::Error; use thiserror::Error;
@ -81,7 +81,11 @@ impl MultisigConfig {
} }
/// Creates a 2-of-3 multisig. /// Creates a 2-of-3 multisig.
pub fn two_of_three(signer1: Address, signer2: Address, signer3: Address) -> Result<Self, MultisigError> { pub fn two_of_three(
signer1: Address,
signer2: Address,
signer3: Address,
) -> Result<Self, MultisigError> {
Self::new(2, vec![signer1, signer2, signer3]) Self::new(2, vec![signer1, signer2, signer3])
} }
@ -113,7 +117,9 @@ impl MultisigConfig {
} }
/// State of a multisig transaction. /// State of a multisig transaction.
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] #[derive(
Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
)]
pub enum MultisigTxState { pub enum MultisigTxState {
/// Transaction is pending signatures. /// Transaction is pending signatures.
Pending, Pending,
@ -131,26 +137,15 @@ pub enum MultisigTxState {
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub enum MultisigTxType { pub enum MultisigTxType {
/// Transfer tokens to an address. /// Transfer tokens to an address.
Transfer { Transfer { to: Address, amount: u64 },
to: Address,
amount: u64,
},
/// Add a new signer. /// Add a new signer.
AddSigner { AddSigner { new_signer: Address },
new_signer: Address,
},
/// Remove a signer. /// Remove a signer.
RemoveSigner { RemoveSigner { signer: Address },
signer: Address,
},
/// Change the threshold. /// Change the threshold.
ChangeThreshold { ChangeThreshold { new_threshold: u32 },
new_threshold: u32,
},
/// Execute a raw transaction. /// Execute a raw transaction.
RawTransaction { RawTransaction { tx_data: Vec<u8> },
tx_data: Vec<u8>,
},
/// Create a vesting contract. /// Create a vesting contract.
CreateVesting { CreateVesting {
beneficiary: Address, beneficiary: Address,
@ -160,14 +155,9 @@ pub enum MultisigTxType {
description: String, description: String,
}, },
/// Approve a DAO proposal. /// Approve a DAO proposal.
ApproveProposal { ApproveProposal { proposal_id: Hash256 },
proposal_id: Hash256,
},
/// Custom action with arbitrary data. /// Custom action with arbitrary data.
Custom { Custom { action_type: String, data: Vec<u8> },
action_type: String,
data: Vec<u8>,
},
} }
/// A pending multisig transaction. /// A pending multisig transaction.
@ -394,7 +384,8 @@ impl MultisigWallet {
return Err(MultisigError::NotAuthorized); return Err(MultisigError::NotAuthorized);
} }
let tx = self.pending_transactions let tx = self
.pending_transactions
.get_mut(tx_id) .get_mut(tx_id)
.ok_or(MultisigError::TransactionNotFound)?; .ok_or(MultisigError::TransactionNotFound)?;
@ -438,7 +429,8 @@ impl MultisigWallet {
return Err(MultisigError::NotAuthorized); return Err(MultisigError::NotAuthorized);
} }
let tx = self.pending_transactions let tx = self
.pending_transactions
.get_mut(tx_id) .get_mut(tx_id)
.ok_or(MultisigError::TransactionNotFound)?; .ok_or(MultisigError::TransactionNotFound)?;
@ -517,7 +509,8 @@ impl MultisigWallet {
canceller: &Address, canceller: &Address,
) -> Result<(), MultisigError> { ) -> Result<(), MultisigError> {
// Only proposer can cancel // Only proposer can cancel
let tx = self.pending_transactions let tx = self
.pending_transactions
.get_mut(tx_id) .get_mut(tx_id)
.ok_or(MultisigError::TransactionNotFound)?; .ok_or(MultisigError::TransactionNotFound)?;
@ -564,7 +557,8 @@ impl MultisigWallet {
/// Cleans up expired transactions. /// Cleans up expired transactions.
pub fn cleanup_expired(&mut self, current_time: u64) -> usize { pub fn cleanup_expired(&mut self, current_time: u64) -> usize {
let expired: Vec<_> = self.pending_transactions let expired: Vec<_> = self
.pending_transactions
.iter() .iter()
.filter(|(_, tx)| tx.is_expired(current_time) && tx.state == MultisigTxState::Pending) .filter(|(_, tx)| tx.is_expired(current_time) && tx.state == MultisigTxState::Pending)
.map(|(id, _)| *id) .map(|(id, _)| *id)
@ -591,7 +585,10 @@ impl MultisigWallet {
threshold: self.config.threshold, threshold: self.config.threshold,
balance: self.balance, balance: self.balance,
pending_count: pending.len(), pending_count: pending.len(),
ready_count: pending.iter().filter(|tx| tx.state == MultisigTxState::Ready).count(), ready_count: pending
.iter()
.filter(|tx| tx.state == MultisigTxState::Ready)
.count(),
total_executed: self.executed_transactions.len(), total_executed: self.executed_transactions.len(),
} }
} }
@ -662,20 +659,20 @@ impl MultisigManager {
pub fn wallets_for_signer(&self, signer: &Address) -> Vec<&MultisigWallet> { pub fn wallets_for_signer(&self, signer: &Address) -> Vec<&MultisigWallet> {
self.by_signer self.by_signer
.get(signer) .get(signer)
.map(|ids| { .map(|ids| ids.iter().filter_map(|id| self.wallets.get(id)).collect())
ids.iter()
.filter_map(|id| self.wallets.get(id))
.collect()
})
.unwrap_or_default() .unwrap_or_default()
} }
/// Gets all transactions awaiting a signer across all wallets. /// Gets all transactions awaiting a signer across all wallets.
pub fn awaiting_signature(&self, signer: &Address) -> Vec<(&MultisigWallet, &MultisigTransaction)> { pub fn awaiting_signature(
&self,
signer: &Address,
) -> Vec<(&MultisigWallet, &MultisigTransaction)> {
self.wallets_for_signer(signer) self.wallets_for_signer(signer)
.into_iter() .into_iter()
.flat_map(|wallet| { .flat_map(|wallet| {
wallet.awaiting_signature(signer) wallet
.awaiting_signature(signer)
.into_iter() .into_iter()
.map(move |tx| (wallet, tx)) .map(move |tx| (wallet, tx))
}) })
@ -707,11 +704,9 @@ mod tests {
#[test] #[test]
fn test_multisig_config() { fn test_multisig_config() {
let config = MultisigConfig::two_of_three( let config =
test_address(1), MultisigConfig::two_of_three(test_address(1), test_address(2), test_address(3))
test_address(2), .unwrap();
test_address(3),
).unwrap();
assert_eq!(config.threshold, 2); assert_eq!(config.threshold, 2);
assert_eq!(config.signer_count(), 3); assert_eq!(config.signer_count(), 3);
@ -742,22 +737,25 @@ mod tests {
let signer3 = test_address(3); let signer3 = test_address(3);
let recipient = test_address(10); let recipient = test_address(10);
let config = MultisigConfig::two_of_three( let config =
signer1.clone(), MultisigConfig::two_of_three(signer1.clone(), signer2.clone(), signer3.clone())
signer2.clone(), .unwrap();
signer3.clone(),
).unwrap();
let mut wallet = MultisigWallet::new("Test Wallet".to_string(), config, 0); let mut wallet = MultisigWallet::new("Test Wallet".to_string(), config, 0);
wallet.deposit(1_000_000); wallet.deposit(1_000_000);
// Propose a transfer // Propose a transfer
let tx_id = wallet.propose( let tx_id = wallet
MultisigTxType::Transfer { to: recipient, amount: 100_000 }, .propose(
MultisigTxType::Transfer {
to: recipient,
amount: 100_000,
},
&signer1, &signer1,
100, 100,
"Pay developer".to_string(), "Pay developer".to_string(),
).unwrap(); )
.unwrap();
// Check proposer auto-signed // Check proposer auto-signed
let tx = wallet.get_transaction(&tx_id).unwrap(); let tx = wallet.get_transaction(&tx_id).unwrap();
@ -780,21 +778,24 @@ mod tests {
let signer2 = test_address(2); let signer2 = test_address(2);
let signer3 = test_address(3); let signer3 = test_address(3);
let config = MultisigConfig::two_of_three( let config =
signer1.clone(), MultisigConfig::two_of_three(signer1.clone(), signer2.clone(), signer3.clone())
signer2.clone(), .unwrap();
signer3.clone(),
).unwrap();
let mut wallet = MultisigWallet::new("Test".to_string(), config, 0); let mut wallet = MultisigWallet::new("Test".to_string(), config, 0);
wallet.deposit(1_000_000); wallet.deposit(1_000_000);
let tx_id = wallet.propose( let tx_id = wallet
MultisigTxType::Transfer { to: test_address(10), amount: 100 }, .propose(
MultisigTxType::Transfer {
to: test_address(10),
amount: 100,
},
&signer1, &signer1,
100, 100,
"Test".to_string(), "Test".to_string(),
).unwrap(); )
.unwrap();
// Try to execute with only 1 signature // Try to execute with only 1 signature
let result = wallet.execute(&tx_id, &signer1, 101); let result = wallet.execute(&tx_id, &signer1, 101);

View file

@ -7,8 +7,8 @@
//! - Automated fund streams //! - Automated fund streams
use borsh::{BorshDeserialize, BorshSerialize}; use borsh::{BorshDeserialize, BorshSerialize};
use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use synor_types::{Address, Hash256}; use synor_types::{Address, Hash256};
use crate::multisig::{MultisigTxId, MultisigWallet}; use crate::multisig::{MultisigTxId, MultisigWallet};
@ -16,7 +16,16 @@ use crate::ProposalId;
/// Unique identifier for a treasury pool. /// Unique identifier for a treasury pool.
#[derive( #[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)] )]
pub struct TreasuryPoolId(pub [u8; 32]); pub struct TreasuryPoolId(pub [u8; 32]);
@ -497,13 +506,21 @@ impl TreasuryPool {
} }
// Check spending limits // Check spending limits
self.config.spending_limit.can_spend(amount, timestamp) self.config
.spending_limit
.can_spend(amount, timestamp)
.map_err(TreasuryError::SpendingLimitExceeded)?; .map_err(TreasuryError::SpendingLimitExceeded)?;
// Determine initial state based on requirements // Determine initial state based on requirements
let initial_state = if self.config.approvals.multisig_required && self.multisig_wallet.is_some() { let initial_state =
if self.config.approvals.multisig_required && self.multisig_wallet.is_some() {
SpendingRequestState::PendingMultisig SpendingRequestState::PendingMultisig
} else if self.config.approvals.dao_threshold.map_or(false, |t| amount >= t) { } else if self
.config
.approvals
.dao_threshold
.map_or(false, |t| amount >= t)
{
SpendingRequestState::PendingDao SpendingRequestState::PendingDao
} else if self.config.approvals.council_required { } else if self.config.approvals.council_required {
SpendingRequestState::PendingCouncil SpendingRequestState::PendingCouncil
@ -538,7 +555,9 @@ impl TreasuryPool {
multisig_tx: MultisigTxId, multisig_tx: MultisigTxId,
timestamp: u64, timestamp: u64,
) -> Result<(), TreasuryError> { ) -> Result<(), TreasuryError> {
let request = self.pending_requests.get_mut(request_id) let request = self
.pending_requests
.get_mut(request_id)
.ok_or(TreasuryError::RequestNotFound)?; .ok_or(TreasuryError::RequestNotFound)?;
if request.state != SpendingRequestState::PendingMultisig { if request.state != SpendingRequestState::PendingMultisig {
@ -548,7 +567,10 @@ impl TreasuryPool {
request.multisig_tx = Some(multisig_tx); request.multisig_tx = Some(multisig_tx);
// Move to next approval stage // Move to next approval stage
let needs_dao = self.config.approvals.dao_threshold let needs_dao = self
.config
.approvals
.dao_threshold
.map_or(false, |t| request.amount >= t); .map_or(false, |t| request.amount >= t);
if needs_dao { if needs_dao {
@ -571,7 +593,9 @@ impl TreasuryPool {
proposal_id: ProposalId, proposal_id: ProposalId,
timestamp: u64, timestamp: u64,
) -> Result<(), TreasuryError> { ) -> Result<(), TreasuryError> {
let request = self.pending_requests.get_mut(request_id) let request = self
.pending_requests
.get_mut(request_id)
.ok_or(TreasuryError::RequestNotFound)?; .ok_or(TreasuryError::RequestNotFound)?;
if request.state != SpendingRequestState::PendingDao { if request.state != SpendingRequestState::PendingDao {
@ -599,7 +623,9 @@ impl TreasuryPool {
required_approvals: usize, required_approvals: usize,
timestamp: u64, timestamp: u64,
) -> Result<(), TreasuryError> { ) -> Result<(), TreasuryError> {
let request = self.pending_requests.get_mut(request_id) let request = self
.pending_requests
.get_mut(request_id)
.ok_or(TreasuryError::RequestNotFound)?; .ok_or(TreasuryError::RequestNotFound)?;
if request.state != SpendingRequestState::PendingCouncil { if request.state != SpendingRequestState::PendingCouncil {
@ -625,7 +651,9 @@ impl TreasuryPool {
request_id: &Hash256, request_id: &Hash256,
timestamp: u64, timestamp: u64,
) -> Result<SpendingRequest, TreasuryError> { ) -> Result<SpendingRequest, TreasuryError> {
let request = self.pending_requests.get(request_id) let request = self
.pending_requests
.get(request_id)
.ok_or(TreasuryError::RequestNotFound)?; .ok_or(TreasuryError::RequestNotFound)?;
// Check if ready // Check if ready
@ -654,7 +682,9 @@ impl TreasuryPool {
// Execute // Execute
self.balance -= amount; self.balance -= amount;
self.total_spent += amount; self.total_spent += amount;
self.config.spending_limit.record_spending(amount, timestamp); self.config
.spending_limit
.record_spending(amount, timestamp);
// Update request state and remove from pending // Update request state and remove from pending
let mut executed_request = self.pending_requests.remove(request_id).unwrap(); let mut executed_request = self.pending_requests.remove(request_id).unwrap();
@ -669,7 +699,9 @@ impl TreasuryPool {
request_id: &Hash256, request_id: &Hash256,
canceller: &Address, canceller: &Address,
) -> Result<(), TreasuryError> { ) -> Result<(), TreasuryError> {
let request = self.pending_requests.get_mut(request_id) let request = self
.pending_requests
.get_mut(request_id)
.ok_or(TreasuryError::RequestNotFound)?; .ok_or(TreasuryError::RequestNotFound)?;
// Only proposer can cancel // Only proposer can cancel
@ -710,14 +742,7 @@ impl TreasuryPool {
// Reserve the amount // Reserve the amount
self.balance -= amount; self.balance -= amount;
let stream = FundingStream::new( let stream = FundingStream::new(id, recipient, amount, start_time, duration, description);
id,
recipient,
amount,
start_time,
duration,
description,
);
self.streams.insert(id, stream); self.streams.insert(id, stream);
Ok(self.streams.get(&id).unwrap()) Ok(self.streams.get(&id).unwrap())
@ -730,7 +755,9 @@ impl TreasuryPool {
claimer: &Address, claimer: &Address,
timestamp: u64, timestamp: u64,
) -> Result<u64, TreasuryError> { ) -> Result<u64, TreasuryError> {
let stream = self.streams.get_mut(stream_id) let stream = self
.streams
.get_mut(stream_id)
.ok_or(TreasuryError::StreamNotFound)?; .ok_or(TreasuryError::StreamNotFound)?;
if &stream.recipient != claimer { if &stream.recipient != claimer {
@ -745,7 +772,9 @@ impl TreasuryPool {
/// Cancels a funding stream (returns unvested to pool). /// Cancels a funding stream (returns unvested to pool).
pub fn cancel_stream(&mut self, stream_id: &Hash256) -> Result<u64, TreasuryError> { pub fn cancel_stream(&mut self, stream_id: &Hash256) -> Result<u64, TreasuryError> {
let stream = self.streams.get_mut(stream_id) let stream = self
.streams
.get_mut(stream_id)
.ok_or(TreasuryError::StreamNotFound)?; .ok_or(TreasuryError::StreamNotFound)?;
let remaining = stream.total_amount - stream.claimed_amount; let remaining = stream.total_amount - stream.claimed_amount;
@ -842,7 +871,9 @@ impl Treasury {
) -> Result<(), TreasuryError> { ) -> Result<(), TreasuryError> {
let wallet_id = wallet.id; let wallet_id = wallet.id;
let pool = self.pools.get_mut(pool_id) let pool = self
.pools
.get_mut(pool_id)
.ok_or(TreasuryError::PoolNotFound)?; .ok_or(TreasuryError::PoolNotFound)?;
pool.set_multisig(wallet_id); pool.set_multisig(wallet_id);
@ -985,8 +1016,15 @@ impl std::fmt::Display for SpendingError {
Self::CoolingPeriod { remaining } => { Self::CoolingPeriod { remaining } => {
write!(f, "Cooling period: {} seconds remaining", remaining) write!(f, "Cooling period: {} seconds remaining", remaining)
} }
Self::ExceedsPeriodLimit { requested, remaining } => { Self::ExceedsPeriodLimit {
write!(f, "Requested {} exceeds remaining period allowance of {}", requested, remaining) requested,
remaining,
} => {
write!(
f,
"Requested {} exceeds remaining period allowance of {}",
requested, remaining
)
} }
} }
} }
@ -1025,8 +1063,15 @@ impl std::fmt::Display for TreasuryError {
Self::PoolNotFound => write!(f, "Treasury pool not found"), Self::PoolNotFound => write!(f, "Treasury pool not found"),
Self::PoolFrozen => write!(f, "Treasury pool is frozen"), Self::PoolFrozen => write!(f, "Treasury pool is frozen"),
Self::DepositsNotAllowed => write!(f, "Deposits not allowed for this pool"), Self::DepositsNotAllowed => write!(f, "Deposits not allowed for this pool"),
Self::InsufficientBalance { requested, available } => { Self::InsufficientBalance {
write!(f, "Insufficient balance: {} requested, {} available", requested, available) requested,
available,
} => {
write!(
f,
"Insufficient balance: {} requested, {} available",
requested, available
)
} }
Self::SpendingLimitExceeded(e) => write!(f, "Spending limit exceeded: {}", e), Self::SpendingLimitExceeded(e) => write!(f, "Spending limit exceeded: {}", e),
Self::RequestNotFound => write!(f, "Spending request not found"), Self::RequestNotFound => write!(f, "Spending request not found"),
@ -1049,7 +1094,11 @@ mod tests {
fn test_address(n: u8) -> Address { fn test_address(n: u8) -> Address {
let mut bytes = [0u8; 32]; let mut bytes = [0u8; 32];
bytes[0] = n; bytes[0] = n;
Address::from_parts(synor_types::Network::Devnet, synor_types::address::AddressType::P2PKH, bytes) Address::from_parts(
synor_types::Network::Devnet,
synor_types::address::AddressType::P2PKH,
bytes,
)
} }
fn test_hash(n: u8) -> Hash256 { fn test_hash(n: u8) -> Hash256 {
@ -1088,13 +1137,7 @@ mod tests {
fn test_treasury_pool() { fn test_treasury_pool() {
let pool_id = TreasuryPoolId::new([1u8; 32]); let pool_id = TreasuryPoolId::new([1u8; 32]);
let config = TreasuryConfig::ecosystem(1_000_000); let config = TreasuryConfig::ecosystem(1_000_000);
let mut pool = TreasuryPool::new( let mut pool = TreasuryPool::new(pool_id, "Test Pool".to_string(), config, 1_000_000, 0);
pool_id,
"Test Pool".to_string(),
config,
1_000_000,
0,
);
// Create spending request // Create spending request
let request_id = test_hash(1); let request_id = test_hash(1);
@ -1108,7 +1151,8 @@ mod tests {
"Test payment".to_string(), "Test payment".to_string(),
proposer, proposer,
100, 100,
).unwrap(); )
.unwrap();
assert!(pool.pending_requests.contains_key(&request_id)); assert!(pool.pending_requests.contains_key(&request_id));
} }

View file

@ -105,7 +105,8 @@ impl VestingSchedule {
// Linear vesting between cliff and end // Linear vesting between cliff and end
// vested = total * elapsed / vesting_duration // vested = total * elapsed / vesting_duration
let vested = (self.total_amount as u128 * elapsed as u128 / self.vesting_duration as u128) as u64; let vested =
(self.total_amount as u128 * elapsed as u128 / self.vesting_duration as u128) as u64;
vested vested
} }
@ -131,7 +132,9 @@ impl VestingSchedule {
} }
/// Current state of a vesting contract. /// Current state of a vesting contract.
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] #[derive(
Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
)]
pub enum VestingState { pub enum VestingState {
/// Vesting is active. /// Vesting is active.
Active, Active,
@ -302,7 +305,9 @@ impl VestingContract {
/// Returns the unvested amount. /// Returns the unvested amount.
pub fn unvested_amount(&self, timestamp: u64) -> u64 { pub fn unvested_amount(&self, timestamp: u64) -> u64 {
self.schedule.total_amount.saturating_sub(self.vested_amount(timestamp)) self.schedule
.total_amount
.saturating_sub(self.vested_amount(timestamp))
} }
/// Claims available tokens. /// Claims available tokens.
@ -429,7 +434,8 @@ impl VestingManager {
schedule: VestingSchedule, schedule: VestingSchedule,
description: String, description: String,
) -> Result<Hash256, VestingError> { ) -> Result<Hash256, VestingError> {
let contract = VestingContract::new(beneficiary.clone(), grantor.clone(), schedule, description)?; let contract =
VestingContract::new(beneficiary.clone(), grantor.clone(), schedule, description)?;
let id = contract.id; let id = contract.id;
// Index by beneficiary // Index by beneficiary
@ -462,11 +468,7 @@ impl VestingManager {
pub fn get_by_beneficiary(&self, beneficiary: &Address) -> Vec<&VestingContract> { pub fn get_by_beneficiary(&self, beneficiary: &Address) -> Vec<&VestingContract> {
self.by_beneficiary self.by_beneficiary
.get(beneficiary) .get(beneficiary)
.map(|ids| { .map(|ids| ids.iter().filter_map(|id| self.contracts.get(id)).collect())
ids.iter()
.filter_map(|id| self.contracts.get(id))
.collect()
})
.unwrap_or_default() .unwrap_or_default()
} }
@ -474,22 +476,28 @@ impl VestingManager {
pub fn get_by_grantor(&self, grantor: &Address) -> Vec<&VestingContract> { pub fn get_by_grantor(&self, grantor: &Address) -> Vec<&VestingContract> {
self.by_grantor self.by_grantor
.get(grantor) .get(grantor)
.map(|ids| { .map(|ids| ids.iter().filter_map(|id| self.contracts.get(id)).collect())
ids.iter()
.filter_map(|id| self.contracts.get(id))
.collect()
})
.unwrap_or_default() .unwrap_or_default()
} }
/// Claims tokens from a vesting contract. /// Claims tokens from a vesting contract.
pub fn claim(&mut self, id: &Hash256, caller: &Address, timestamp: u64) -> Result<u64, VestingError> { pub fn claim(
&mut self,
id: &Hash256,
caller: &Address,
timestamp: u64,
) -> Result<u64, VestingError> {
let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?; let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?;
contract.claim(caller, timestamp) contract.claim(caller, timestamp)
} }
/// Revokes a vesting contract. /// Revokes a vesting contract.
pub fn revoke(&mut self, id: &Hash256, caller: &Address, timestamp: u64) -> Result<u64, VestingError> { pub fn revoke(
&mut self,
id: &Hash256,
caller: &Address,
timestamp: u64,
) -> Result<u64, VestingError> {
let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?; let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?;
contract.revoke(caller, timestamp) contract.revoke(caller, timestamp)
} }
@ -523,9 +531,18 @@ impl VestingManager {
let contracts: Vec<_> = self.contracts.values().collect(); let contracts: Vec<_> = self.contracts.values().collect();
VestingStats { VestingStats {
total_contracts: contracts.len(), total_contracts: contracts.len(),
active_contracts: contracts.iter().filter(|c| c.state == VestingState::Active).count(), active_contracts: contracts
revoked_contracts: contracts.iter().filter(|c| c.state == VestingState::Revoked).count(), .iter()
completed_contracts: contracts.iter().filter(|c| c.state == VestingState::Completed).count(), .filter(|c| c.state == VestingState::Active)
.count(),
revoked_contracts: contracts
.iter()
.filter(|c| c.state == VestingState::Revoked)
.count(),
completed_contracts: contracts
.iter()
.filter(|c| c.state == VestingState::Completed)
.count(),
total_amount: contracts.iter().map(|c| c.schedule.total_amount).sum(), total_amount: contracts.iter().map(|c| c.schedule.total_amount).sum(),
total_claimed: contracts.iter().map(|c| c.claimed_amount).sum(), total_claimed: contracts.iter().map(|c| c.claimed_amount).sum(),
} }
@ -556,7 +573,11 @@ mod tests {
fn test_address(n: u8) -> Address { fn test_address(n: u8) -> Address {
let mut bytes = [0u8; 32]; let mut bytes = [0u8; 32];
bytes[0] = n; bytes[0] = n;
Address::from_parts(synor_types::Network::Devnet, synor_types::address::AddressType::P2PKH, bytes) Address::from_parts(
synor_types::Network::Devnet,
synor_types::address::AddressType::P2PKH,
bytes,
)
} }
#[test] #[test]
@ -591,7 +612,8 @@ mod tests {
grantor, grantor,
VestingSchedule::new(1_000_000, 0, 12, start_time, true), // 1 year, no cliff VestingSchedule::new(1_000_000, 0, 12, start_time, true), // 1 year, no cliff
"Test vesting".to_string(), "Test vesting".to_string(),
).unwrap(); )
.unwrap();
// After 6 months, should have ~50% vested // After 6 months, should have ~50% vested
let six_months = 6 * 30 * 24 * 60 * 60; let six_months = 6 * 30 * 24 * 60 * 60;
@ -617,7 +639,8 @@ mod tests {
grantor.clone(), grantor.clone(),
VestingSchedule::new(1_000_000, 0, 12, 0, true), VestingSchedule::new(1_000_000, 0, 12, 0, true),
"Revocable".to_string(), "Revocable".to_string(),
).unwrap(); )
.unwrap();
// Revoke at 6 months // Revoke at 6 months
let six_months = 6 * 30 * 24 * 60 * 60; let six_months = 6 * 30 * 24 * 60 * 60;
@ -637,7 +660,8 @@ mod tests {
grantor.clone(), grantor.clone(),
VestingSchedule::new(1_000_000, 0, 12, 0, false), // non-revocable VestingSchedule::new(1_000_000, 0, 12, 0, false), // non-revocable
"Non-revocable".to_string(), "Non-revocable".to_string(),
).unwrap(); )
.unwrap();
let result = contract.revoke(&grantor, 0); let result = contract.revoke(&grantor, 0);
assert!(matches!(result, Err(VestingError::NotRevocable))); assert!(matches!(result, Err(VestingError::NotRevocable)));
@ -649,12 +673,14 @@ mod tests {
let beneficiary = test_address(1); let beneficiary = test_address(1);
let grantor = test_address(2); let grantor = test_address(2);
let id = manager.create_vesting( let id = manager
.create_vesting(
beneficiary.clone(), beneficiary.clone(),
grantor, grantor,
VestingSchedule::new(1_000_000, 0, 12, 0, true), VestingSchedule::new(1_000_000, 0, 12, 0, true),
"Test".to_string(), "Test".to_string(),
).unwrap(); )
.unwrap();
assert!(manager.get(&id).is_some()); assert!(manager.get(&id).is_some());
assert_eq!(manager.get_by_beneficiary(&beneficiary).len(), 1); assert_eq!(manager.get_by_beneficiary(&beneficiary).len(), 1);

View file

@ -9,9 +9,7 @@
//! //!
//! Run with: cargo bench -p synor-mining //! Run with: cargo bench -p synor-mining
use criterion::{ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
};
use synor_mining::kheavyhash::{HashrateBenchmark, KHeavyHash, ParallelMiner}; use synor_mining::kheavyhash::{HashrateBenchmark, KHeavyHash, ParallelMiner};
use synor_mining::matrix::{gf_mul, HeavyMatrix, OptimizedMatrix}; use synor_mining::matrix::{gf_mul, HeavyMatrix, OptimizedMatrix};
use synor_mining::Target; use synor_mining::Target;
@ -141,17 +139,13 @@ fn bench_mining_throughput(c: &mut Criterion) {
for count in [100, 1000, 10000] { for count in [100, 1000, 10000] {
group.throughput(Throughput::Elements(count)); group.throughput(Throughput::Elements(count));
group.bench_with_input( group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &count| {
BenchmarkId::from_parameter(count),
&count,
|b, &count| {
b.iter(|| { b.iter(|| {
for nonce in 0..count { for nonce in 0..count {
black_box(hasher.finalize(&pre_hash, nonce)); black_box(hasher.finalize(&pre_hash, nonce));
} }
}) })
}, });
);
} }
group.finish(); group.finish();
@ -168,9 +162,7 @@ fn bench_mining_with_target(c: &mut Criterion) {
group.bench_function("mine_easy_target", |b| { group.bench_function("mine_easy_target", |b| {
b.iter_batched( b.iter_batched(
|| 0u64, || 0u64,
|start_nonce| { |start_nonce| black_box(hasher.mine(&header, &target, start_nonce, 10000)),
black_box(hasher.mine(&header, &target, start_nonce, 10000))
},
criterion::BatchSize::SmallInput, criterion::BatchSize::SmallInput,
) )
}); });
@ -212,13 +204,9 @@ fn bench_parallel_miner_creation(c: &mut Criterion) {
let mut group = c.benchmark_group("parallel_miner_create"); let mut group = c.benchmark_group("parallel_miner_create");
for threads in [1, 2, 4, 8] { for threads in [1, 2, 4, 8] {
group.bench_with_input( group.bench_with_input(BenchmarkId::from_parameter(threads), &threads, |b, &t| {
BenchmarkId::from_parameter(threads),
&threads,
|b, &t| {
b.iter(|| black_box(ParallelMiner::new(t))) b.iter(|| black_box(ParallelMiner::new(t)))
}, });
);
} }
group.finish(); group.finish();
@ -296,11 +284,7 @@ fn bench_matrix_memory(c: &mut Criterion) {
// ==================== Criterion Groups ==================== // ==================== Criterion Groups ====================
criterion_group!( criterion_group!(gf_benches, bench_gf_mul, bench_gf_mul_single,);
gf_benches,
bench_gf_mul,
bench_gf_mul_single,
);
criterion_group!( criterion_group!(
matrix_benches, matrix_benches,
@ -332,10 +316,7 @@ criterion_group!(
bench_verify, bench_verify,
); );
criterion_group!( criterion_group!(parallel_benches, bench_parallel_miner_creation,);
parallel_benches,
bench_parallel_miner_creation,
);
criterion_main!( criterion_main!(
gf_benches, gf_benches,

View file

@ -9,14 +9,14 @@
//! //!
//! Run with: cargo bench -p synor-mining --bench mining_bench //! Run with: cargo bench -p synor-mining --bench mining_bench
use criterion::{ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
};
use synor_mining::{ use synor_mining::{
kheavyhash::{KHeavyHash, ParallelMiner}, kheavyhash::{KHeavyHash, ParallelMiner},
matrix::{GfTables, HeavyMatrix, OptimizedMatrix}, matrix::{GfTables, HeavyMatrix, OptimizedMatrix},
miner::{BlockMiner, MinerConfig, ParallelBlockMiner}, miner::{BlockMiner, MinerConfig, ParallelBlockMiner},
template::{BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction}, template::{
BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction,
},
Target, Target,
}; };
use synor_types::{Address, Hash256, Network}; use synor_types::{Address, Hash256, Network};
@ -146,9 +146,7 @@ fn matrix_initialization(c: &mut Criterion) {
b.iter(|| black_box(OptimizedMatrix::new())) b.iter(|| black_box(OptimizedMatrix::new()))
}); });
group.bench_function("gf_tables_new", |b| { group.bench_function("gf_tables_new", |b| b.iter(|| black_box(GfTables::new())));
b.iter(|| black_box(GfTables::new()))
});
// Custom seed initialization // Custom seed initialization
for seed_idx in [0u8, 42u8, 255u8] { for seed_idx in [0u8, 42u8, 255u8] {
@ -156,9 +154,7 @@ fn matrix_initialization(c: &mut Criterion) {
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("matrix_from_seed", seed_idx), BenchmarkId::new("matrix_from_seed", seed_idx),
&seed, &seed,
|b, s| { |b, s| b.iter(|| black_box(HeavyMatrix::from_seed(s))),
b.iter(|| black_box(HeavyMatrix::from_seed(s)))
},
); );
} }
@ -232,27 +228,15 @@ fn nonce_search_rate(c: &mut Criterion) {
for range in [1000u64, 5000, 10000] { for range in [1000u64, 5000, 10000] {
let target = Target::from_bytes([0x00; 32]); // Impossible target let target = Target::from_bytes([0x00; 32]); // Impossible target
group.throughput(Throughput::Elements(range)); group.throughput(Throughput::Elements(range));
group.bench_with_input( group.bench_with_input(BenchmarkId::new("range_search", range), &range, |b, &r| {
BenchmarkId::new("range_search", range),
&range,
|b, &r| {
b.iter(|| black_box(hasher.mine(&header, &target, 0, r))) b.iter(|| black_box(hasher.mine(&header, &target, 0, r)))
}, });
);
} }
// Search with progress callback // Search with progress callback
group.bench_function("search_with_callback", |b| { group.bench_function("search_with_callback", |b| {
let target = Target::max(); let target = Target::max();
b.iter(|| { b.iter(|| hasher.mine_with_callback(&header, &target, 0, 5000, |_tried, _nonce| true))
hasher.mine_with_callback(
&header,
&target,
0,
5000,
|_tried, _nonce| true,
)
})
}); });
group.finish(); group.finish();
@ -265,14 +249,10 @@ fn parallel_nonce_search(c: &mut Criterion) {
let target = Target::max(); let target = Target::max();
for threads in [1, 2, 4] { for threads in [1, 2, 4] {
group.bench_with_input( group.bench_with_input(BenchmarkId::new("threads", threads), &threads, |b, &t| {
BenchmarkId::new("threads", threads),
&threads,
|b, &t| {
let miner = ParallelMiner::new(t); let miner = ParallelMiner::new(t);
b.iter(|| black_box(miner.mine(&header, &target, 0))) b.iter(|| black_box(miner.mine(&header, &target, 0)))
}, });
);
} }
group.finish(); group.finish();
@ -291,9 +271,8 @@ fn block_template_creation(c: &mut Criterion) {
b.iter_batched( b.iter_batched(
|| { || {
// Setup: create transactions // Setup: create transactions
let txs: Vec<TemplateTransaction> = (0..count) let txs: Vec<TemplateTransaction> =
.map(|i| test_template_tx(i as u64)) (0..count).map(|i| test_template_tx(i as u64)).collect();
.collect();
txs txs
}, },
|txs| { |txs| {
@ -331,17 +310,13 @@ fn template_header_creation(c: &mut Criterion) {
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("header_for_mining", tx_count), BenchmarkId::new("header_for_mining", tx_count),
&template, &template,
|b, tmpl| { |b, tmpl| b.iter(|| black_box(tmpl.header_for_mining())),
b.iter(|| black_box(tmpl.header_for_mining()))
},
); );
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("header_with_extra_nonce", tx_count), BenchmarkId::new("header_with_extra_nonce", tx_count),
&template, &template,
|b, tmpl| { |b, tmpl| b.iter(|| black_box(tmpl.header_with_extra_nonce(12345))),
b.iter(|| black_box(tmpl.header_with_extra_nonce(12345)))
},
); );
} }
@ -357,17 +332,13 @@ fn template_validation(c: &mut Criterion) {
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("validate", tx_count), BenchmarkId::new("validate", tx_count),
&template, &template,
|b, tmpl| { |b, tmpl| b.iter(|| black_box(tmpl.validate())),
b.iter(|| black_box(tmpl.validate()))
},
); );
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("get_target", tx_count), BenchmarkId::new("get_target", tx_count),
&template, &template,
|b, tmpl| { |b, tmpl| b.iter(|| black_box(tmpl.get_target())),
b.iter(|| black_box(tmpl.get_target()))
},
); );
} }
@ -378,9 +349,7 @@ fn coinbase_building(c: &mut Criterion) {
let mut group = c.benchmark_group("coinbase_building"); let mut group = c.benchmark_group("coinbase_building");
group.bench_function("coinbase_simple", |b| { group.bench_function("coinbase_simple", |b| {
b.iter(|| { b.iter(|| black_box(CoinbaseBuilder::new(test_address(), 1000).build()))
black_box(CoinbaseBuilder::new(test_address(), 1000).build())
})
}); });
group.bench_function("coinbase_with_data", |b| { group.bench_function("coinbase_with_data", |b| {
@ -390,7 +359,7 @@ fn coinbase_building(c: &mut Criterion) {
.extra_data(b"Synor Mining Pool v1.0.0".to_vec()) .extra_data(b"Synor Mining Pool v1.0.0".to_vec())
.reward(500_00000000) .reward(500_00000000)
.fees(1_00000000) .fees(1_00000000)
.build() .build(),
) )
}) })
}); });
@ -407,7 +376,7 @@ fn coinbase_building(c: &mut Criterion) {
CoinbaseBuilder::new(test_address(), 1000) CoinbaseBuilder::new(test_address(), 1000)
.extra_data(data.clone()) .extra_data(data.clone())
.reward(500_00000000) .reward(500_00000000)
.build() .build(),
) )
}) })
}, },
@ -477,9 +446,7 @@ fn target_operations(c: &mut Criterion) {
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("from_bits", format!("{:08x}", bits)), BenchmarkId::new("from_bits", format!("{:08x}", bits)),
&bits, &bits,
|b, &bit| { |b, &bit| b.iter(|| black_box(Target::from_bits(bit))),
b.iter(|| black_box(Target::from_bits(bit)))
},
); );
} }
@ -607,11 +574,7 @@ criterion_group!(
matrix_memory_patterns, matrix_memory_patterns,
); );
criterion_group!( criterion_group!(nonce_benches, nonce_search_rate, parallel_nonce_search,);
nonce_benches,
nonce_search_rate,
parallel_nonce_search,
);
criterion_group!( criterion_group!(
template_benches, template_benches,
@ -628,16 +591,9 @@ criterion_group!(
parallel_block_miner_creation, parallel_block_miner_creation,
); );
criterion_group!( criterion_group!(misc_benches, target_operations, mining_stats_operations,);
misc_benches,
target_operations,
mining_stats_operations,
);
criterion_group!( criterion_group!(e2e_benches, end_to_end_mining,);
e2e_benches,
end_to_end_mining,
);
criterion_main!( criterion_main!(
throughput_benches, throughput_benches,

View file

@ -251,12 +251,7 @@ impl ParallelMiner {
/// Mines using all threads. /// Mines using all threads.
/// ///
/// Each thread gets a different nonce range to search. /// Each thread gets a different nonce range to search.
pub fn mine( pub fn mine(&self, header: &[u8], target: &Target, start_nonce: u64) -> Option<PowHash> {
&self,
header: &[u8],
target: &Target,
start_nonce: u64,
) -> Option<PowHash> {
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;

View file

@ -43,9 +43,13 @@ pub mod template;
pub use kheavyhash::{KHeavyHash, PowHash}; pub use kheavyhash::{KHeavyHash, PowHash};
pub use matrix::HeavyMatrix; pub use matrix::HeavyMatrix;
pub use miner::{BlockMiner, MinerCommand, MinerConfig, MinerEvent, MiningResult, ParallelBlockMiner}; pub use miner::{
BlockMiner, MinerCommand, MinerConfig, MinerEvent, MiningResult, ParallelBlockMiner,
};
pub use stratum::{StratumClient, StratumJob, StratumServer}; pub use stratum::{StratumClient, StratumJob, StratumServer};
pub use template::{BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction}; pub use template::{
BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction,
};
use synor_types::{Address, Hash256}; use synor_types::{Address, Hash256};
@ -297,27 +301,24 @@ mod tests {
#[test] #[test]
fn test_target_comparison() { fn test_target_comparison() {
let target = Target::from_bytes([ let target = Target::from_bytes([
0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
]); ]);
// Hash with more leading zeros should pass // Hash with more leading zeros should pass
let easy_hash = Hash256::from_bytes([ let easy_hash = Hash256::from_bytes([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
]); ]);
assert!(target.is_met_by(&easy_hash)); assert!(target.is_met_by(&easy_hash));
// Hash with fewer leading zeros should fail // Hash with fewer leading zeros should fail
let hard_hash = Hash256::from_bytes([ let hard_hash = Hash256::from_bytes([
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]); ]);
assert!(!target.is_met_by(&hard_hash)); assert!(!target.is_met_by(&hard_hash));
} }

View file

@ -3,7 +3,6 @@
//! The matrix is a 64x64 matrix where each element is in GF(2^8). //! The matrix is a 64x64 matrix where each element is in GF(2^8).
//! Matrix multiplication is performed using Galois field arithmetic. //! Matrix multiplication is performed using Galois field arithmetic.
/// The irreducible polynomial for GF(2^8): x^8 + x^4 + x^3 + x + 1 /// The irreducible polynomial for GF(2^8): x^8 + x^4 + x^3 + x + 1
const GF_POLY: u16 = 0x11B; const GF_POLY: u16 = 0x11B;

View file

@ -301,7 +301,8 @@ impl StratumServer {
}; };
// Reconstruct header: header_hash + extra_nonce1 + extra_nonce2 // Reconstruct header: header_hash + extra_nonce1 + extra_nonce2
let mut header_data = Vec::with_capacity(header_hash.len() + extra_nonce1.len() + extra_nonce2.len()); let mut header_data =
Vec::with_capacity(header_hash.len() + extra_nonce1.len() + extra_nonce2.len());
header_data.extend_from_slice(&header_hash); header_data.extend_from_slice(&header_hash);
header_data.extend_from_slice(&extra_nonce1); header_data.extend_from_slice(&extra_nonce1);
header_data.extend_from_slice(&extra_nonce2); header_data.extend_from_slice(&extra_nonce2);
@ -442,19 +443,13 @@ impl StratumServer {
}; };
let id = request.get("id").and_then(|v| v.as_u64()).unwrap_or(0); let id = request.get("id").and_then(|v| v.as_u64()).unwrap_or(0);
let method = request let method = request.get("method").and_then(|v| v.as_str()).unwrap_or("");
.get("method")
.and_then(|v| v.as_str())
.unwrap_or("");
let response = match method { let response = match method {
"mining.subscribe" => { "mining.subscribe" => {
let extra_nonce = self.allocate_extra_nonce(); let extra_nonce = self.allocate_extra_nonce();
let result = serde_json::json!([ let result =
[["mining.notify", "1"]], serde_json::json!([[["mining.notify", "1"]], extra_nonce, 8]);
extra_nonce,
8
]);
StratumResponse { StratumResponse {
id, id,
result: Some(result), result: Some(result),
@ -463,7 +458,8 @@ impl StratumServer {
} }
"mining.authorize" => { "mining.authorize" => {
let params = request.get("params").cloned().unwrap_or_default(); let params = request.get("params").cloned().unwrap_or_default();
let worker = params.get(0).and_then(|v| v.as_str()).unwrap_or("unknown"); let worker =
params.get(0).and_then(|v| v.as_str()).unwrap_or("unknown");
_worker_name = Some(worker.to_string()); _worker_name = Some(worker.to_string());
authorized = true; authorized = true;
self.register_worker(worker.to_string(), self.allocate_extra_nonce()); self.register_worker(worker.to_string(), self.allocate_extra_nonce());
@ -485,10 +481,26 @@ impl StratumServer {
// Parse submission // Parse submission
let params = request.get("params").cloned().unwrap_or_default(); let params = request.get("params").cloned().unwrap_or_default();
let submission = ShareSubmission { let submission = ShareSubmission {
worker: params.get(0).and_then(|v| v.as_str()).unwrap_or("").to_string(), worker: params
job_id: params.get(1).and_then(|v| v.as_str()).unwrap_or("").to_string(), .get(0)
extra_nonce2: params.get(2).and_then(|v| v.as_str()).unwrap_or("").to_string(), .and_then(|v| v.as_str())
nonce: params.get(3).and_then(|v| v.as_str()).unwrap_or("").to_string(), .unwrap_or("")
.to_string(),
job_id: params
.get(1)
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
extra_nonce2: params
.get(2)
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
nonce: params
.get(3)
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
timestamp: current_timestamp(), timestamp: current_timestamp(),
}; };
@ -680,7 +692,10 @@ mod tests {
hashrate: 1000.0, hashrate: 1000.0,
}; };
assert_eq!(info.shares_valid + info.shares_invalid, info.shares_submitted); assert_eq!(
info.shares_valid + info.shares_invalid,
info.shares_submitted
);
} }
#[test] #[test]
@ -745,7 +760,11 @@ mod tests {
timestamp: 1234567890, timestamp: 1234567890,
}; };
let result = server.validate_share(&valid_submission); let result = server.validate_share(&valid_submission);
assert!(matches!(result, ShareResult::ValidShare), "Expected ValidShare, got {:?}", result); assert!(
matches!(result, ShareResult::ValidShare),
"Expected ValidShare, got {:?}",
result
);
// Test duplicate share // Test duplicate share
let duplicate_submission = ShareSubmission { let duplicate_submission = ShareSubmission {

View file

@ -239,13 +239,17 @@ impl BlockTemplateBuilder {
/// Builds the block template. /// Builds the block template.
pub fn build(mut self, template_id: u64) -> Result<BlockTemplate, MiningError> { pub fn build(mut self, template_id: u64) -> Result<BlockTemplate, MiningError> {
let selected = self.selected_parent.or_else(|| self.parents.first().copied()) let selected = self
.selected_parent
.or_else(|| self.parents.first().copied())
.ok_or_else(|| MiningError::InvalidTemplate("No parents".into()))?; .ok_or_else(|| MiningError::InvalidTemplate("No parents".into()))?;
// Build header data first before taking coinbase // Build header data first before taking coinbase
let header_data = self.build_header(&selected)?; let header_data = self.build_header(&selected)?;
let coinbase = self.coinbase.take() let coinbase = self
.coinbase
.take()
.ok_or_else(|| MiningError::InvalidTemplate("No coinbase".into()))?; .ok_or_else(|| MiningError::InvalidTemplate("No coinbase".into()))?;
// Calculate fees // Calculate fees
@ -321,7 +325,11 @@ impl BlockTemplateBuilder {
} }
// Collect all txids // Collect all txids
let txids: Vec<&[u8; 32]> = self.transactions.iter().map(|tx| tx.txid.as_bytes()).collect(); let txids: Vec<&[u8; 32]> = self
.transactions
.iter()
.map(|tx| tx.txid.as_bytes())
.collect();
// Simple merkle tree // Simple merkle tree
merkle_root(&txids) merkle_root(&txids)

View file

@ -7,8 +7,7 @@ use libp2p::{
gossipsub::{self, IdentTopic, MessageAuthenticity, MessageId, ValidationMode}, gossipsub::{self, IdentTopic, MessageAuthenticity, MessageId, ValidationMode},
identify, identify,
kad::{self, store::MemoryStore}, kad::{self, store::MemoryStore},
mdns, mdns, ping,
ping,
request_response::{self, ProtocolSupport}, request_response::{self, ProtocolSupport},
swarm::NetworkBehaviour, swarm::NetworkBehaviour,
PeerId, StreamProtocol, PeerId, StreamProtocol,
@ -35,7 +34,10 @@ pub struct SynorBehaviour {
impl SynorBehaviour { impl SynorBehaviour {
/// Creates a new behaviour with the given configuration. /// Creates a new behaviour with the given configuration.
pub fn new(keypair: &libp2p::identity::Keypair, config: &NetworkConfig) -> Result<Self, Box<dyn std::error::Error>> { pub fn new(
keypair: &libp2p::identity::Keypair,
config: &NetworkConfig,
) -> Result<Self, Box<dyn std::error::Error>> {
let local_peer_id = keypair.public().to_peer_id(); let local_peer_id = keypair.public().to_peer_id();
// GossipSub configuration // GossipSub configuration
@ -68,19 +70,13 @@ impl SynorBehaviour {
// Request-response configuration // Request-response configuration
let request_response = request_response::Behaviour::new( let request_response = request_response::Behaviour::new(
[( [(StreamProtocol::new(SYNOR_PROTOCOL), ProtocolSupport::Full)],
StreamProtocol::new(SYNOR_PROTOCOL), request_response::Config::default().with_request_timeout(config.sync.request_timeout),
ProtocolSupport::Full,
)],
request_response::Config::default()
.with_request_timeout(config.sync.request_timeout),
); );
// Identify configuration // Identify configuration
let identify_config = identify::Config::new( let identify_config =
"/synor/id/1.0.0".to_string(), identify::Config::new("/synor/id/1.0.0".to_string(), keypair.public())
keypair.public(),
)
.with_agent_version(crate::user_agent()) .with_agent_version(crate::user_agent())
.with_push_listen_addr_updates(true); .with_push_listen_addr_updates(true);
@ -94,10 +90,7 @@ impl SynorBehaviour {
); );
// mDNS configuration // mDNS configuration
let mdns = mdns::tokio::Behaviour::new( let mdns = mdns::tokio::Behaviour::new(mdns::Config::default(), local_peer_id)?;
mdns::Config::default(),
local_peer_id,
)?;
Ok(SynorBehaviour { Ok(SynorBehaviour {
gossipsub, gossipsub,
@ -135,22 +128,35 @@ impl SynorBehaviour {
} }
/// Publishes a block announcement. /// Publishes a block announcement.
pub fn publish_block(&mut self, data: Vec<u8>) -> Result<gossipsub::MessageId, gossipsub::PublishError> { pub fn publish_block(
&mut self,
data: Vec<u8>,
) -> Result<gossipsub::MessageId, gossipsub::PublishError> {
self.publish(topics::BLOCKS, data) self.publish(topics::BLOCKS, data)
} }
/// Publishes a transaction announcement. /// Publishes a transaction announcement.
pub fn publish_transaction(&mut self, data: Vec<u8>) -> Result<gossipsub::MessageId, gossipsub::PublishError> { pub fn publish_transaction(
&mut self,
data: Vec<u8>,
) -> Result<gossipsub::MessageId, gossipsub::PublishError> {
self.publish(topics::TRANSACTIONS, data) self.publish(topics::TRANSACTIONS, data)
} }
/// Publishes a header announcement. /// Publishes a header announcement.
pub fn publish_header(&mut self, data: Vec<u8>) -> Result<gossipsub::MessageId, gossipsub::PublishError> { pub fn publish_header(
&mut self,
data: Vec<u8>,
) -> Result<gossipsub::MessageId, gossipsub::PublishError> {
self.publish(topics::HEADERS, data) self.publish(topics::HEADERS, data)
} }
/// Sends a request to a peer. /// Sends a request to a peer.
pub fn send_request(&mut self, peer: &PeerId, request: SynorRequest) -> request_response::OutboundRequestId { pub fn send_request(
&mut self,
peer: &PeerId,
request: SynorRequest,
) -> request_response::OutboundRequestId {
self.request_response.send_request(peer, request) self.request_response.send_request(peer, request)
} }
@ -191,10 +197,7 @@ impl SynorBehaviour {
/// Returns peers subscribed to a topic. /// Returns peers subscribed to a topic.
pub fn topic_peers(&self, topic: &str) -> Vec<PeerId> { pub fn topic_peers(&self, topic: &str) -> Vec<PeerId> {
let topic = IdentTopic::new(topic); let topic = IdentTopic::new(topic);
self.gossipsub self.gossipsub.mesh_peers(&topic.hash()).cloned().collect()
.mesh_peers(&topic.hash())
.cloned()
.collect()
} }
} }

View file

@ -40,7 +40,9 @@ impl Default for NetworkConfig {
NetworkConfig { NetworkConfig {
chain_id: ChainId::Mainnet, chain_id: ChainId::Mainnet,
listen_addresses: vec![ listen_addresses: vec![
format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT).parse().unwrap(), format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT)
.parse()
.unwrap(),
format!("/ip6/::/tcp/{}", DEFAULT_PORT).parse().unwrap(), format!("/ip6/::/tcp/{}", DEFAULT_PORT).parse().unwrap(),
], ],
bootstrap_peers: Vec::new(), bootstrap_peers: Vec::new(),
@ -74,11 +76,9 @@ impl NetworkConfig {
NetworkConfig { NetworkConfig {
chain_id: ChainId::Testnet, chain_id: ChainId::Testnet,
bootstrap_peers: testnet_bootstrap_peers(), bootstrap_peers: testnet_bootstrap_peers(),
listen_addresses: vec![ listen_addresses: vec![format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT + 1000)
format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT + 1000)
.parse() .parse()
.unwrap(), .unwrap()],
],
..Default::default() ..Default::default()
} }
} }
@ -229,10 +229,7 @@ fn testnet_bootstrap_peers() -> Vec<Multiaddr> {
// "/dns4/testnet-seed3.synor.cc/tcp/17511/p2p/12D3KooW...", // "/dns4/testnet-seed3.synor.cc/tcp/17511/p2p/12D3KooW...",
]; ];
seeds seeds.iter().filter_map(|s| s.parse().ok()).collect()
.iter()
.filter_map(|s| s.parse().ok())
.collect()
} }
/// Returns devnet bootstrap peers for local development. /// Returns devnet bootstrap peers for local development.

View file

@ -414,7 +414,9 @@ mod tests {
// Different subnet should be allowed // Different subnet should be allowed
let different_ip = Some(IpAddr::V4(Ipv4Addr::new(10, 1, 1, 1))); let different_ip = Some(IpAddr::V4(Ipv4Addr::new(10, 1, 1, 1)));
assert!(protection.check_connection(&peer3, different_ip, false).is_ok()); assert!(protection
.check_connection(&peer3, different_ip, false)
.is_ok());
} }
#[test] #[test]

View file

@ -41,7 +41,9 @@ pub use behaviour::SynorBehaviour;
pub use config::NetworkConfig; pub use config::NetworkConfig;
pub use eclipse::{EclipseConfig, EclipseProtection, EclipseStats, PeerType}; pub use eclipse::{EclipseConfig, EclipseProtection, EclipseStats, PeerType};
pub use message::{BlockAnnouncement, Message, TransactionAnnouncement}; pub use message::{BlockAnnouncement, Message, TransactionAnnouncement};
pub use partition::{PartitionConfig, PartitionDetector, PartitionReason, PartitionStats, PartitionStatus}; pub use partition::{
PartitionConfig, PartitionDetector, PartitionReason, PartitionStats, PartitionStatus,
};
pub use peer::{PeerInfo, PeerManager, PeerState}; pub use peer::{PeerInfo, PeerManager, PeerState};
pub use protocol::{SynorProtocol, SynorRequest, SynorResponse}; pub use protocol::{SynorProtocol, SynorRequest, SynorResponse};
pub use rate_limit::{ pub use rate_limit::{

View file

@ -278,7 +278,9 @@ impl Headers {
/// Creates an empty headers response. /// Creates an empty headers response.
pub fn empty() -> Self { pub fn empty() -> Self {
Headers { headers: Vec::new() } Headers {
headers: Vec::new(),
}
} }
} }

View file

@ -119,15 +119,24 @@ pub enum PartitionReason {
BlockProductionStalled { duration_secs: u64 }, BlockProductionStalled { duration_secs: u64 },
/// Block production rate significantly lower than expected. /// Block production rate significantly lower than expected.
/// Rates are stored as millibps (blocks per 1000 seconds) for precision. /// Rates are stored as millibps (blocks per 1000 seconds) for precision.
LowBlockRate { current_rate_millibps: u32, expected_rate_millibps: u32 }, LowBlockRate {
current_rate_millibps: u32,
expected_rate_millibps: u32,
},
/// Our tips don't match peer tips. /// Our tips don't match peer tips.
/// `matching_peers_pct` is 0-100. /// `matching_peers_pct` is 0-100.
TipDivergence { matching_peers_pct: u8, threshold_pct: u8 }, TipDivergence {
matching_peers_pct: u8,
threshold_pct: u8,
},
/// Tip is too old. /// Tip is too old.
/// `age_secs` and `max_age_secs` are in seconds. /// `age_secs` and `max_age_secs` are in seconds.
StaleTip { age_secs: u64, max_age_secs: u64 }, StaleTip { age_secs: u64, max_age_secs: u64 },
/// Protocol version mismatch with majority. /// Protocol version mismatch with majority.
ProtocolVersionSkew { our_version: u32, majority_version: u32 }, ProtocolVersionSkew {
our_version: u32,
majority_version: u32,
},
/// Most peers have higher blue scores, we may be on a minority fork. /// Most peers have higher blue scores, we may be on a minority fork.
BehindNetwork { our_score: u64, network_score: u64 }, BehindNetwork { our_score: u64, network_score: u64 },
/// All connections are inbound (potential eclipse attack). /// All connections are inbound (potential eclipse attack).
@ -141,40 +150,59 @@ impl PartitionReason {
pub fn description(&self) -> String { pub fn description(&self) -> String {
match self { match self {
PartitionReason::InsufficientPeers { current, required } => { PartitionReason::InsufficientPeers { current, required } => {
format!("Only {} peers connected, need at least {}", current, required) format!(
"Only {} peers connected, need at least {}",
current, required
)
} }
PartitionReason::InsufficientOutbound { current, required } => { PartitionReason::InsufficientOutbound { current, required } => {
format!("Only {} outbound peers, need at least {}", current, required) format!(
"Only {} outbound peers, need at least {}",
current, required
)
} }
PartitionReason::LowSubnetDiversity { current, required } => { PartitionReason::LowSubnetDiversity { current, required } => {
format!("Only {} unique subnets, need at least {}", current, required) format!(
"Only {} unique subnets, need at least {}",
current, required
)
} }
PartitionReason::SubnetConcentration { subnet, percentage } => { PartitionReason::SubnetConcentration { subnet, percentage } => {
format!( format!(
"Subnet {:X} has {}% of peers, max allowed 50%", "Subnet {:X} has {}% of peers, max allowed 50%",
subnet, subnet, percentage
percentage
) )
} }
PartitionReason::BlockProductionStalled { duration_secs } => { PartitionReason::BlockProductionStalled { duration_secs } => {
format!("No new blocks for {} seconds", duration_secs) format!("No new blocks for {} seconds", duration_secs)
} }
PartitionReason::LowBlockRate { current_rate_millibps, expected_rate_millibps } => { PartitionReason::LowBlockRate {
current_rate_millibps,
expected_rate_millibps,
} => {
format!( format!(
"Block rate {:.2}/s, expected {:.2}/s", "Block rate {:.2}/s, expected {:.2}/s",
*current_rate_millibps as f64 / 1000.0, *current_rate_millibps as f64 / 1000.0,
*expected_rate_millibps as f64 / 1000.0 *expected_rate_millibps as f64 / 1000.0
) )
} }
PartitionReason::TipDivergence { matching_peers_pct, threshold_pct } => { PartitionReason::TipDivergence {
matching_peers_pct,
threshold_pct,
} => {
format!( format!(
"Only {}% of peers agree on tips, need {}%", "Only {}% of peers agree on tips, need {}%",
matching_peers_pct, matching_peers_pct, threshold_pct
threshold_pct
) )
} }
PartitionReason::StaleTip { age_secs, max_age_secs } => { PartitionReason::StaleTip {
format!("Best tip is {} seconds old, max allowed {} seconds", age_secs, max_age_secs) age_secs,
max_age_secs,
} => {
format!(
"Best tip is {} seconds old, max allowed {} seconds",
age_secs, max_age_secs
)
} }
PartitionReason::ProtocolVersionSkew { PartitionReason::ProtocolVersionSkew {
our_version, our_version,
@ -404,12 +432,7 @@ impl PartitionDetector {
} }
/// Records a peer connection. /// Records a peer connection.
pub fn record_peer_connected( pub fn record_peer_connected(&self, peer_id: PeerId, ip: Option<IpAddr>, is_outbound: bool) {
&self,
peer_id: PeerId,
ip: Option<IpAddr>,
is_outbound: bool,
) {
let info = PeerPartitionInfo::new(peer_id, ip, is_outbound); let info = PeerPartitionInfo::new(peer_id, ip, is_outbound);
self.peers.write().insert(peer_id, info); self.peers.write().insert(peer_id, info);
@ -743,7 +766,9 @@ impl PartitionDetector {
// Any critical reason means we're partitioned // Any critical reason means we're partitioned
let mut all_reasons = critical_reasons; let mut all_reasons = critical_reasons;
all_reasons.extend(warning_reasons); all_reasons.extend(warning_reasons);
PartitionStatus::Partitioned { reasons: all_reasons } PartitionStatus::Partitioned {
reasons: all_reasons,
}
} else if !warning_reasons.is_empty() { } else if !warning_reasons.is_empty() {
PartitionStatus::Degraded { PartitionStatus::Degraded {
reasons: warning_reasons, reasons: warning_reasons,
@ -801,11 +826,7 @@ impl PartitionDetector {
let (status_str, warning_count, critical_count) = match &status { let (status_str, warning_count, critical_count) = match &status {
PartitionStatus::Connected => ("Connected".to_string(), 0, 0), PartitionStatus::Connected => ("Connected".to_string(), 0, 0),
PartitionStatus::Degraded { reasons } => ( PartitionStatus::Degraded { reasons } => ("Degraded".to_string(), reasons.len(), 0),
"Degraded".to_string(),
reasons.len(),
0,
),
PartitionStatus::Partitioned { reasons } => { PartitionStatus::Partitioned { reasons } => {
let critical = reasons.iter().filter(|r| r.is_critical()).count(); let critical = reasons.iter().filter(|r| r.is_critical()).count();
( (
@ -966,10 +987,9 @@ mod tests {
match status { match status {
PartitionStatus::Degraded { reasons } => { PartitionStatus::Degraded { reasons } => {
assert!(reasons.iter().any(|r| matches!( assert!(reasons
r, .iter()
PartitionReason::InsufficientPeers { .. } .any(|r| matches!(r, PartitionReason::InsufficientPeers { .. })));
)));
} }
_ => panic!("Expected degraded status"), _ => panic!("Expected degraded status"),
} }
@ -993,10 +1013,9 @@ mod tests {
match status { match status {
PartitionStatus::Partitioned { reasons } => { PartitionStatus::Partitioned { reasons } => {
assert!(reasons.iter().any(|r| matches!( assert!(reasons
r, .iter()
PartitionReason::NoOutboundConnections .any(|r| matches!(r, PartitionReason::NoOutboundConnections)));
)));
} }
_ => panic!("Expected partitioned status"), _ => panic!("Expected partitioned status"),
} }
@ -1098,9 +1117,7 @@ mod tests {
required: 3, required: 3,
}, },
PartitionReason::NoOutboundConnections, PartitionReason::NoOutboundConnections,
PartitionReason::BlockProductionStalled { PartitionReason::BlockProductionStalled { duration_secs: 60 },
duration_secs: 60,
},
]; ];
for reason in reasons { for reason in reasons {

View file

@ -334,10 +334,9 @@ impl PeerManager {
// Ban after releasing the peers lock to avoid deadlock // Ban after releasing the peers lock to avoid deadlock
if should_ban { if should_ban {
self.banned.write().insert( self.banned
*peer_id, .write()
Instant::now() + self.ban_duration, .insert(*peer_id, Instant::now() + self.ban_duration);
);
} }
true true
@ -436,8 +435,14 @@ impl PeerManager {
PeerStats { PeerStats {
total: peers.len(), total: peers.len(),
active: active.len(), active: active.len(),
inbound: active.iter().filter(|p| p.direction == Direction::Inbound).count(), inbound: active
outbound: active.iter().filter(|p| p.direction == Direction::Outbound).count(), .iter()
.filter(|p| p.direction == Direction::Inbound)
.count(),
outbound: active
.iter()
.filter(|p| p.direction == Direction::Outbound)
.count(),
banned: self.banned.read().len(), banned: self.banned.read().len(),
avg_latency: active avg_latency: active
.iter() .iter()

View file

@ -269,13 +269,9 @@ impl Codec for SynorCodec {
} }
/// Creates a request-response behaviour for the Synor protocol. /// Creates a request-response behaviour for the Synor protocol.
pub fn create_request_response_behaviour( pub fn create_request_response_behaviour() -> request_response::Behaviour<SynorCodec> {
) -> request_response::Behaviour<SynorCodec> {
request_response::Behaviour::new( request_response::Behaviour::new(
[( [(StreamProtocol::new(SYNOR_PROTOCOL), ProtocolSupport::Full)],
StreamProtocol::new(SYNOR_PROTOCOL),
ProtocolSupport::Full,
)],
request_response::Config::default(), request_response::Config::default(),
) )
} }

View file

@ -371,7 +371,9 @@ impl PerPeerLimiter {
pub fn cleanup(&self, connected_peers: &[PeerId]) { pub fn cleanup(&self, connected_peers: &[PeerId]) {
let connected: std::collections::HashSet<_> = connected_peers.iter().collect(); let connected: std::collections::HashSet<_> = connected_peers.iter().collect();
self.peers.write().retain(|id, _| connected.contains(id)); self.peers.write().retain(|id, _| connected.contains(id));
self.cooldowns.write().retain(|id, _| connected.contains(id)); self.cooldowns
.write()
.retain(|id, _| connected.contains(id));
} }
/// Returns the number of tracked peers. /// Returns the number of tracked peers.

View file

@ -224,7 +224,10 @@ impl RateLimiter {
/// Returns the number of requests made by a peer in the current window. /// Returns the number of requests made by a peer in the current window.
pub fn request_count(&self, peer_id: &PeerId) -> u32 { pub fn request_count(&self, peer_id: &PeerId) -> u32 {
let peers = self.peers.read(); let peers = self.peers.read();
peers.get(peer_id).map(|s| s.requests.len() as u32).unwrap_or(0) peers
.get(peer_id)
.map(|s| s.requests.len() as u32)
.unwrap_or(0)
} }
/// Returns the number of violations for a peer. /// Returns the number of violations for a peer.

View file

@ -178,7 +178,10 @@ impl PeerReputation {
pub fn record_success(&mut self) { pub fn record_success(&mut self) {
self.successes += 1; self.successes += 1;
self.last_seen = Instant::now(); self.last_seen = Instant::now();
self.score = self.score.saturating_add(Self::SUCCESS_BONUS).min(Self::MAX_SCORE); self.score = self
.score
.saturating_add(Self::SUCCESS_BONUS)
.min(Self::MAX_SCORE);
} }
/// Records a violation and returns true if the peer should be banned. /// Records a violation and returns true if the peer should be banned.
@ -602,7 +605,10 @@ mod tests {
rep.record_success(); rep.record_success();
assert_eq!(rep.successes, 1); assert_eq!(rep.successes, 1);
assert_eq!(rep.score, PeerReputation::INITIAL_SCORE + PeerReputation::SUCCESS_BONUS); assert_eq!(
rep.score,
PeerReputation::INITIAL_SCORE + PeerReputation::SUCCESS_BONUS
);
} }
#[test] #[test]
@ -690,7 +696,10 @@ mod tests {
manager.record_success(&peer_id); manager.record_success(&peer_id);
let score = manager.get_score(&peer_id).unwrap(); let score = manager.get_score(&peer_id).unwrap();
assert_eq!(score, PeerReputation::INITIAL_SCORE + PeerReputation::SUCCESS_BONUS); assert_eq!(
score,
PeerReputation::INITIAL_SCORE + PeerReputation::SUCCESS_BONUS
);
} }
#[test] #[test]

View file

@ -3,7 +3,7 @@
use crate::behaviour::SynorBehaviour; use crate::behaviour::SynorBehaviour;
use crate::config::NetworkConfig; use crate::config::NetworkConfig;
use crate::message::{BlockAnnouncement, TransactionAnnouncement}; use crate::message::{BlockAnnouncement, TransactionAnnouncement};
use crate::peer::{Direction, PeerInfo, PeerManager, reputation}; use crate::peer::{reputation, Direction, PeerInfo, PeerManager};
use crate::protocol::{SynorRequest, SynorResponse}; use crate::protocol::{SynorRequest, SynorResponse};
use crate::rate_limit::{PerPeerLimiter, RateLimitConfig as TokenBucketConfig}; use crate::rate_limit::{PerPeerLimiter, RateLimitConfig as TokenBucketConfig};
use crate::ratelimit::{RateLimitResult, RateLimiters}; use crate::ratelimit::{RateLimitResult, RateLimiters};
@ -12,14 +12,8 @@ use crate::sync::{SyncManager, SyncStatus};
use crate::topics; use crate::topics;
use futures::StreamExt; use futures::StreamExt;
use libp2p::{ use libp2p::{
gossipsub, gossipsub, identify, kad, mdns, ping, request_response, swarm::SwarmEvent, Multiaddr, PeerId,
identify, Swarm, SwarmBuilder,
kad,
mdns,
ping,
request_response,
swarm::SwarmEvent,
Multiaddr, PeerId, Swarm, SwarmBuilder,
}; };
use parking_lot::RwLock; use parking_lot::RwLock;
use std::sync::Arc; use std::sync::Arc;
@ -125,7 +119,10 @@ impl NetworkHandle {
} }
/// Broadcasts a block to the network. /// Broadcasts a block to the network.
pub async fn broadcast_block(&self, announcement: BlockAnnouncement) -> Result<(), NetworkError> { pub async fn broadcast_block(
&self,
announcement: BlockAnnouncement,
) -> Result<(), NetworkError> {
self.command_tx self.command_tx
.send(NetworkCommand::BroadcastBlock(announcement)) .send(NetworkCommand::BroadcastBlock(announcement))
.await .await
@ -133,7 +130,10 @@ impl NetworkHandle {
} }
/// Broadcasts a transaction to the network. /// Broadcasts a transaction to the network.
pub async fn broadcast_transaction(&self, announcement: TransactionAnnouncement) -> Result<(), NetworkError> { pub async fn broadcast_transaction(
&self,
announcement: TransactionAnnouncement,
) -> Result<(), NetworkError> {
self.command_tx self.command_tx
.send(NetworkCommand::BroadcastTransaction(announcement)) .send(NetworkCommand::BroadcastTransaction(announcement))
.await .await
@ -278,10 +278,8 @@ pub struct NetworkService {
/// Event sender. /// Event sender.
event_tx: broadcast::Sender<NetworkEvent>, event_tx: broadcast::Sender<NetworkEvent>,
/// Pending requests. /// Pending requests.
pending_requests: RwLock<std::collections::HashMap< pending_requests:
request_response::OutboundRequestId, RwLock<std::collections::HashMap<request_response::OutboundRequestId, PendingRequest>>,
PendingRequest,
>>,
} }
struct PendingRequest { struct PendingRequest {
@ -290,7 +288,9 @@ struct PendingRequest {
impl NetworkService { impl NetworkService {
/// Creates a new network service. /// Creates a new network service.
pub async fn new(config: NetworkConfig) -> Result<(Self, NetworkHandle), Box<dyn std::error::Error>> { pub async fn new(
config: NetworkConfig,
) -> Result<(Self, NetworkHandle), Box<dyn std::error::Error>> {
// Generate keypair // Generate keypair
let local_keypair = libp2p::identity::Keypair::generate_ed25519(); let local_keypair = libp2p::identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_keypair.public()); let local_peer_id = PeerId::from(local_keypair.public());
@ -310,9 +310,7 @@ impl NetworkService {
.with_behaviour(|key| { .with_behaviour(|key| {
SynorBehaviour::new(key, &config).expect("Behaviour creation failed") SynorBehaviour::new(key, &config).expect("Behaviour creation failed")
})? })?
.with_swarm_config(|cfg| { .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(config.idle_timeout))
cfg.with_idle_connection_timeout(config.idle_timeout)
})
.build(); .build();
// Create peer, sync, and reputation managers // Create peer, sync, and reputation managers
@ -397,13 +395,18 @@ impl NetworkService {
} }
/// Handles a swarm event. /// Handles a swarm event.
async fn handle_swarm_event(&mut self, event: SwarmEvent<crate::behaviour::SynorBehaviourEvent>) { async fn handle_swarm_event(
&mut self,
event: SwarmEvent<crate::behaviour::SynorBehaviourEvent>,
) {
match event { match event {
SwarmEvent::NewListenAddr { address, .. } => { SwarmEvent::NewListenAddr { address, .. } => {
info!("Listening on {}", address); info!("Listening on {}", address);
} }
SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } => { SwarmEvent::ConnectionEstablished {
peer_id, endpoint, ..
} => {
// Check if peer is banned via reputation system // Check if peer is banned via reputation system
if self.reputation_manager.is_banned(&peer_id) { if self.reputation_manager.is_banned(&peer_id) {
warn!("Rejecting connection from banned peer: {}", peer_id); warn!("Rejecting connection from banned peer: {}", peer_id);
@ -458,22 +461,26 @@ impl NetworkService {
message_id: _, message_id: _,
message, message,
}) => { }) => {
self.handle_gossip_message(propagation_source, message).await; self.handle_gossip_message(propagation_source, message)
.await;
} }
SynorBehaviourEvent::RequestResponse(request_response::Event::Message { SynorBehaviourEvent::RequestResponse(request_response::Event::Message {
peer, peer,
message, message,
}) => { }) => match message {
match message { request_response::Message::Request {
request_response::Message::Request { request, channel, .. } => { request, channel, ..
} => {
self.handle_request(peer, request, channel).await; self.handle_request(peer, request, channel).await;
} }
request_response::Message::Response { request_id, response } => { request_response::Message::Response {
request_id,
response,
} => {
self.handle_response(peer, request_id, response).await; self.handle_response(peer, request_id, response).await;
} }
} },
}
SynorBehaviourEvent::RequestResponse(request_response::Event::OutboundFailure { SynorBehaviourEvent::RequestResponse(request_response::Event::OutboundFailure {
peer, peer,
@ -484,7 +491,9 @@ impl NetworkService {
self.sync_manager.on_request_failed(request_id); self.sync_manager.on_request_failed(request_id);
self.peer_manager.update_peer(&peer, |p| p.record_failure()); self.peer_manager.update_peer(&peer, |p| p.record_failure());
// Record violation in reputation system (no response from peer) // Record violation in reputation system (no response from peer)
let auto_banned = self.reputation_manager.record_violation(&peer, ViolationType::NoResponse); let auto_banned = self
.reputation_manager
.record_violation(&peer, ViolationType::NoResponse);
if auto_banned { if auto_banned {
warn!("Auto-banned peer {} for repeated failures", peer); warn!("Auto-banned peer {} for repeated failures", peer);
let _ = self.swarm.disconnect_peer_id(peer); let _ = self.swarm.disconnect_peer_id(peer);
@ -508,8 +517,7 @@ impl NetworkService {
} }
} }
SynorBehaviourEvent::Ping(ping::Event { peer, result, .. }) => { SynorBehaviourEvent::Ping(ping::Event { peer, result, .. }) => match result {
match result {
Ok(rtt) => { Ok(rtt) => {
trace!("Ping to {} succeeded: {:?}", peer, rtt); trace!("Ping to {} succeeded: {:?}", peer, rtt);
self.peer_manager.update_peer(&peer, |p| { self.peer_manager.update_peer(&peer, |p| {
@ -519,14 +527,15 @@ impl NetworkService {
Err(e) => { Err(e) => {
debug!("Ping to {} failed: {}", peer, e); debug!("Ping to {} failed: {}", peer, e);
} }
} },
}
SynorBehaviourEvent::Mdns(mdns::Event::Discovered(peers)) => { SynorBehaviourEvent::Mdns(mdns::Event::Discovered(peers)) => {
for (peer_id, addr) in peers { for (peer_id, addr) in peers {
debug!("mDNS discovered peer: {} at {}", peer_id, addr); debug!("mDNS discovered peer: {} at {}", peer_id, addr);
if self.peer_manager.can_connect_outbound() { if self.peer_manager.can_connect_outbound() {
self.swarm.behaviour_mut().add_address(&peer_id, addr.clone()); self.swarm
.behaviour_mut()
.add_address(&peer_id, addr.clone());
if let Err(e) = self.swarm.dial(addr) { if let Err(e) = self.swarm.dial(addr) {
debug!("Failed to dial discovered peer: {}", e); debug!("Failed to dial discovered peer: {}", e);
} }
@ -540,8 +549,9 @@ impl NetworkService {
} }
} }
SynorBehaviourEvent::Kademlia(kad::Event::OutboundQueryProgressed { result, .. }) => { SynorBehaviourEvent::Kademlia(kad::Event::OutboundQueryProgressed {
match result { result, ..
}) => match result {
kad::QueryResult::GetClosestPeers(Ok(ok)) => { kad::QueryResult::GetClosestPeers(Ok(ok)) => {
for peer in ok.peers { for peer in ok.peers {
debug!("Kademlia found peer: {:?}", peer); debug!("Kademlia found peer: {:?}", peer);
@ -551,8 +561,7 @@ impl NetworkService {
info!("Kademlia bootstrap complete"); info!("Kademlia bootstrap complete");
} }
_ => {} _ => {}
} },
}
_ => {} _ => {}
} }
@ -565,14 +574,24 @@ impl NetworkService {
match topic { match topic {
t if t == topics::BLOCKS => { t if t == topics::BLOCKS => {
if let Ok(announcement) = borsh::from_slice::<BlockAnnouncement>(&message.data) { if let Ok(announcement) = borsh::from_slice::<BlockAnnouncement>(&message.data) {
debug!("Received block announcement from {}: {}", source, announcement.hash); debug!(
"Received block announcement from {}: {}",
source, announcement.hash
);
let _ = self.event_tx.send(NetworkEvent::NewBlock(announcement)); let _ = self.event_tx.send(NetworkEvent::NewBlock(announcement));
} }
} }
t if t == topics::TRANSACTIONS => { t if t == topics::TRANSACTIONS => {
if let Ok(announcement) = borsh::from_slice::<TransactionAnnouncement>(&message.data) { if let Ok(announcement) =
debug!("Received tx announcement from {}: {}", source, announcement.txid); borsh::from_slice::<TransactionAnnouncement>(&message.data)
let _ = self.event_tx.send(NetworkEvent::NewTransaction(announcement)); {
debug!(
"Received tx announcement from {}: {}",
source, announcement.txid
);
let _ = self
.event_tx
.send(NetworkEvent::NewTransaction(announcement));
} }
} }
_ => { _ => {
@ -614,7 +633,9 @@ impl NetworkService {
p.add_reputation(reputation::INVALID_DATA); p.add_reputation(reputation::INVALID_DATA);
}); });
// Record spam violation in reputation system (may auto-ban) // Record spam violation in reputation system (may auto-ban)
let auto_banned = self.reputation_manager.record_violation(&peer, ViolationType::Spam); let auto_banned = self
.reputation_manager
.record_violation(&peer, ViolationType::Spam);
if auto_banned { if auto_banned {
warn!("Auto-banned peer {} for repeated spam violations", peer); warn!("Auto-banned peer {} for repeated spam violations", peer);
let _ = self.swarm.disconnect_peer_id(peer); let _ = self.swarm.disconnect_peer_id(peer);
@ -640,14 +661,15 @@ impl NetworkService {
p.add_reputation(reputation::TIMEOUT); p.add_reputation(reputation::TIMEOUT);
}); });
// Record spam violation in reputation system // Record spam violation in reputation system
let auto_banned = self.reputation_manager.record_violation(&peer, ViolationType::Spam); let auto_banned = self
.reputation_manager
.record_violation(&peer, ViolationType::Spam);
if auto_banned { if auto_banned {
warn!("Auto-banned peer {} for spam violations", peer); warn!("Auto-banned peer {} for spam violations", peer);
let _ = self.swarm.disconnect_peer_id(peer); let _ = self.swarm.disconnect_peer_id(peer);
} }
let response = SynorResponse::Error( let response =
"Rate limited. Too many requests per second.".to_string() SynorResponse::Error("Rate limited. Too many requests per second.".to_string());
);
if let Err(e) = self.swarm.behaviour_mut().send_response(channel, response) { if let Err(e) = self.swarm.behaviour_mut().send_response(channel, response) {
warn!("Failed to send rate limit response to {}: {:?}", peer, e); warn!("Failed to send rate limit response to {}: {:?}", peer, e);
} }
@ -688,7 +710,8 @@ impl NetworkService {
p.add_reputation(reputation::TIMEOUT); p.add_reputation(reputation::TIMEOUT);
}); });
// Record slow response violation // Record slow response violation
self.reputation_manager.record_violation(&peer, ViolationType::SlowResponse); self.reputation_manager
.record_violation(&peer, ViolationType::SlowResponse);
let response = SynorResponse::Error(format!( let response = SynorResponse::Error(format!(
"Rate limited. Retry after {} seconds.", "Rate limited. Retry after {} seconds.",
retry_after.as_secs() retry_after.as_secs()
@ -710,9 +733,14 @@ impl NetworkService {
p.add_reputation(reputation::INVALID_DATA); p.add_reputation(reputation::INVALID_DATA);
}); });
// Record spam violation (may auto-ban) // Record spam violation (may auto-ban)
let auto_banned = self.reputation_manager.record_violation(&peer, ViolationType::Spam); let auto_banned = self
.reputation_manager
.record_violation(&peer, ViolationType::Spam);
if auto_banned { if auto_banned {
warn!("Auto-banned peer {} for repeated rate limit violations", peer); warn!(
"Auto-banned peer {} for repeated rate limit violations",
peer
);
let _ = self.swarm.disconnect_peer_id(peer); let _ = self.swarm.disconnect_peer_id(peer);
} }
let response = SynorResponse::Error(format!( let response = SynorResponse::Error(format!(
@ -794,7 +822,8 @@ impl NetworkService {
let _ = self.event_tx.send(NetworkEvent::HeadersReceived(headers)); let _ = self.event_tx.send(NetworkEvent::HeadersReceived(headers));
} }
SynorResponse::Blocks(blocks) => { SynorResponse::Blocks(blocks) => {
self.sync_manager.on_blocks_response(request_id, blocks.clone()); self.sync_manager
.on_blocks_response(request_id, blocks.clone());
let _ = self.event_tx.send(NetworkEvent::BlocksReceived(blocks)); let _ = self.event_tx.send(NetworkEvent::BlocksReceived(blocks));
} }
_ => {} _ => {}
@ -848,17 +877,30 @@ impl NetworkService {
} }
} }
NetworkCommand::RequestBlocks { peer, block_ids, response } => { NetworkCommand::RequestBlocks {
peer,
block_ids,
response,
} => {
let request = SynorRequest::GetBlocks(block_ids); let request = SynorRequest::GetBlocks(block_ids);
let _request_id = self.swarm.behaviour_mut().send_request(&peer, request); let _request_id = self.swarm.behaviour_mut().send_request(&peer, request);
// Would need to track the response sender // Would need to track the response sender
let _ = response.send(Err(NetworkError::RequestFailed("Not implemented".to_string()))); let _ = response.send(Err(NetworkError::RequestFailed(
"Not implemented".to_string(),
)));
} }
NetworkCommand::RequestHeaders { peer, start, max_count, response } => { NetworkCommand::RequestHeaders {
peer,
start,
max_count,
response,
} => {
let request = SynorRequest::GetHeaders { start, max_count }; let request = SynorRequest::GetHeaders { start, max_count };
let _request_id = self.swarm.behaviour_mut().send_request(&peer, request); let _request_id = self.swarm.behaviour_mut().send_request(&peer, request);
let _ = response.send(Err(NetworkError::RequestFailed("Not implemented".to_string()))); let _ = response.send(Err(NetworkError::RequestFailed(
"Not implemented".to_string(),
)));
} }
NetworkCommand::GetPeerCount(response) => { NetworkCommand::GetPeerCount(response) => {

View file

@ -188,11 +188,7 @@ pub struct SyncManager {
impl SyncManager { impl SyncManager {
/// Creates a new sync manager. /// Creates a new sync manager.
pub fn new( pub fn new(config: SyncConfig, peer_manager: Arc<PeerManager>, genesis_hash: Hash256) -> Self {
config: SyncConfig,
peer_manager: Arc<PeerManager>,
genesis_hash: Hash256,
) -> Self {
SyncManager { SyncManager {
config, config,
status: RwLock::new(SyncStatus::default()), status: RwLock::new(SyncStatus::default()),

View file

@ -174,7 +174,8 @@ pub trait UtxoApi {
/// Gets balances by addresses. /// Gets balances by addresses.
#[method(name = "getBalancesByAddresses")] #[method(name = "getBalancesByAddresses")]
async fn get_balances_by_addresses(&self, addresses: Vec<String>) -> RpcResult<Vec<RpcBalance>>; async fn get_balances_by_addresses(&self, addresses: Vec<String>)
-> RpcResult<Vec<RpcBalance>>;
/// Gets coin supply. /// Gets coin supply.
#[method(name = "getCoinSupply")] #[method(name = "getCoinSupply")]
@ -194,7 +195,10 @@ pub trait MiningApi {
/// Submits a mined block. /// Submits a mined block.
#[method(name = "submitBlock")] #[method(name = "submitBlock")]
async fn submit_block(&self, request: RpcSubmitBlockRequest) -> RpcResult<RpcSubmitBlockResponse>; async fn submit_block(
&self,
request: RpcSubmitBlockRequest,
) -> RpcResult<RpcSubmitBlockResponse>;
/// Gets current network hashrate. /// Gets current network hashrate.
#[method(name = "getNetworkHashrate")] #[method(name = "getNetworkHashrate")]

View file

@ -38,13 +38,13 @@ pub mod server;
pub mod types; pub mod types;
pub use api::{ pub use api::{
BlockApiServer, MiningApiServer, NetworkApiServer, TransactionApiServer, UtxoApiServer, BlockApiServer, ContractApiServer, MiningApiServer, NetworkApiServer, SubscriptionApiServer,
ContractApiServer, SubscriptionApiServer, TransactionApiServer, UtxoApiServer,
}; };
pub use pool::{ pub use pool::{
ConnectionPool, ConnectionPoolExt, PoolConfig, PoolError, PoolStats, PoolStatsSnapshot, global_pool, init_global_pool, ConnectionPool, ConnectionPoolExt, PoolConfig, PoolError,
PooledHttpClient, PooledHttpClientGuard, PooledWsClient, PooledWsClientGuard, PoolStats, PoolStatsSnapshot, PooledHttpClient, PooledHttpClientGuard, PooledWsClient,
global_pool, init_global_pool, PooledWsClientGuard,
}; };
pub use server::{RpcConfig, RpcServer}; pub use server::{RpcConfig, RpcServer};
pub use types::*; pub use types::*;

View file

@ -102,7 +102,9 @@ impl PoolStats {
PoolStatsSnapshot { PoolStatsSnapshot {
connections_created: self.connections_created.load(Ordering::Relaxed), connections_created: self.connections_created.load(Ordering::Relaxed),
connections_closed: self.connections_closed.load(Ordering::Relaxed), connections_closed: self.connections_closed.load(Ordering::Relaxed),
connections_active: self.connections_created.load(Ordering::Relaxed) connections_active: self
.connections_created
.load(Ordering::Relaxed)
.saturating_sub(self.connections_closed.load(Ordering::Relaxed)), .saturating_sub(self.connections_closed.load(Ordering::Relaxed)),
requests_total: self.requests_total.load(Ordering::Relaxed), requests_total: self.requests_total.load(Ordering::Relaxed),
requests_failed: self.requests_failed.load(Ordering::Relaxed), requests_failed: self.requests_failed.load(Ordering::Relaxed),
@ -289,7 +291,9 @@ impl ConnectionPool {
// Try to get an existing client // Try to get an existing client
if let Some(client) = entry_lock.http_clients.pop() { if let Some(client) = entry_lock.http_clients.pop() {
self.stats.connections_reused.fetch_add(1, Ordering::Relaxed); self.stats
.connections_reused
.fetch_add(1, Ordering::Relaxed);
client.touch(); client.touch();
return Ok(client); return Ok(client);
} }
@ -332,7 +336,9 @@ impl ConnectionPool {
// Try to get an existing client // Try to get an existing client
if let Some(client) = entry_lock.ws_clients.pop() { if let Some(client) = entry_lock.ws_clients.pop() {
self.stats.connections_reused.fetch_add(1, Ordering::Relaxed); self.stats
.connections_reused
.fetch_add(1, Ordering::Relaxed);
client.touch(); client.touch();
return Ok(client); return Ok(client);
} }
@ -355,7 +361,9 @@ impl ConnectionPool {
{ {
entry_lock.http_clients.push(client); entry_lock.http_clients.push(client);
} else { } else {
self.stats.connections_closed.fetch_add(1, Ordering::Relaxed); self.stats
.connections_closed
.fetch_add(1, Ordering::Relaxed);
} }
} }
} }
@ -373,7 +381,9 @@ impl ConnectionPool {
{ {
entry_lock.ws_clients.push(client); entry_lock.ws_clients.push(client);
} else { } else {
self.stats.connections_closed.fetch_add(1, Ordering::Relaxed); self.stats
.connections_closed
.fetch_add(1, Ordering::Relaxed);
} }
} }
} }
@ -394,7 +404,9 @@ impl ConnectionPool {
entry_lock.consecutive_failures += 1; entry_lock.consecutive_failures += 1;
if entry_lock.consecutive_failures >= self.config.max_retries { if entry_lock.consecutive_failures >= self.config.max_retries {
entry_lock.healthy = false; entry_lock.healthy = false;
self.stats.health_check_failures.fetch_add(1, Ordering::Relaxed); self.stats
.health_check_failures
.fetch_add(1, Ordering::Relaxed);
} }
} }
} }
@ -509,14 +521,17 @@ impl ConnectionPool {
consecutive_failures: 0, consecutive_failures: 0,
})); }));
endpoints.insert(endpoint.to_string(), Mutex::new(EndpointEntry { endpoints.insert(
endpoint.to_string(),
Mutex::new(EndpointEntry {
http_clients: Vec::new(), http_clients: Vec::new(),
ws_clients: Vec::new(), ws_clients: Vec::new(),
semaphore: Arc::new(Semaphore::new(self.config.max_connections_per_endpoint)), semaphore: Arc::new(Semaphore::new(self.config.max_connections_per_endpoint)),
last_health_check: Instant::now(), last_health_check: Instant::now(),
healthy: true, healthy: true,
consecutive_failures: 0, consecutive_failures: 0,
})); }),
);
entry entry
} }
@ -533,7 +548,9 @@ impl ConnectionPool {
match self.try_create_http_client(endpoint).await { match self.try_create_http_client(endpoint).await {
Ok(client) => { Ok(client) => {
self.stats.connections_created.fetch_add(1, Ordering::Relaxed); self.stats
.connections_created
.fetch_add(1, Ordering::Relaxed);
self.mark_healthy(endpoint); self.mark_healthy(endpoint);
return Ok(client); return Ok(client);
} }
@ -545,9 +562,7 @@ impl ConnectionPool {
} }
self.stats.requests_failed.fetch_add(1, Ordering::Relaxed); self.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
Err(last_error.unwrap_or(PoolError::ConnectionFailed( Err(last_error.unwrap_or(PoolError::ConnectionFailed("Unknown error".to_string())))
"Unknown error".to_string(),
)))
} }
/// Creates a new WebSocket client with retries. /// Creates a new WebSocket client with retries.
@ -562,7 +577,9 @@ impl ConnectionPool {
match self.try_create_ws_client(endpoint).await { match self.try_create_ws_client(endpoint).await {
Ok(client) => { Ok(client) => {
self.stats.connections_created.fetch_add(1, Ordering::Relaxed); self.stats
.connections_created
.fetch_add(1, Ordering::Relaxed);
self.mark_healthy(endpoint); self.mark_healthy(endpoint);
return Ok(client); return Ok(client);
} }
@ -574,9 +591,7 @@ impl ConnectionPool {
} }
self.stats.requests_failed.fetch_add(1, Ordering::Relaxed); self.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
Err(last_error.unwrap_or(PoolError::ConnectionFailed( Err(last_error.unwrap_or(PoolError::ConnectionFailed("Unknown error".to_string())))
"Unknown error".to_string(),
)))
} }
/// Attempts to create an HTTP client. /// Attempts to create an HTTP client.
@ -599,10 +614,7 @@ impl ConnectionPool {
} }
/// Attempts to create a WebSocket client. /// Attempts to create a WebSocket client.
async fn try_create_ws_client( async fn try_create_ws_client(&self, endpoint: &str) -> Result<Arc<PooledWsClient>, PoolError> {
&self,
endpoint: &str,
) -> Result<Arc<PooledWsClient>, PoolError> {
let client = WsClientBuilder::default() let client = WsClientBuilder::default()
.request_timeout(self.config.request_timeout) .request_timeout(self.config.request_timeout)
.connection_timeout(self.config.connect_timeout) .connection_timeout(self.config.connect_timeout)
@ -674,7 +686,10 @@ impl<'a> PooledHttpClientGuard<'a> {
pub fn success(&self) { pub fn success(&self) {
if let Some(ref client) = self.client { if let Some(ref client) = self.client {
client.touch(); client.touch();
self.pool.stats.requests_total.fetch_add(1, Ordering::Relaxed); self.pool
.stats
.requests_total
.fetch_add(1, Ordering::Relaxed);
} }
} }
@ -682,14 +697,20 @@ impl<'a> PooledHttpClientGuard<'a> {
pub fn failed(&self) { pub fn failed(&self) {
if let Some(ref client) = self.client { if let Some(ref client) = self.client {
self.pool.mark_unhealthy(client.endpoint()); self.pool.mark_unhealthy(client.endpoint());
self.pool.stats.requests_failed.fetch_add(1, Ordering::Relaxed); self.pool
.stats
.requests_failed
.fetch_add(1, Ordering::Relaxed);
} }
} }
/// Discards the connection (don't return to pool). /// Discards the connection (don't return to pool).
pub fn discard(mut self) { pub fn discard(mut self) {
if let Some(client) = self.client.take() { if let Some(client) = self.client.take() {
self.pool.stats.connections_closed.fetch_add(1, Ordering::Relaxed); self.pool
.stats
.connections_closed
.fetch_add(1, Ordering::Relaxed);
drop(client); drop(client);
} }
} }
@ -727,7 +748,10 @@ impl<'a> PooledWsClientGuard<'a> {
pub fn success(&self) { pub fn success(&self) {
if let Some(ref client) = self.client { if let Some(ref client) = self.client {
client.touch(); client.touch();
self.pool.stats.requests_total.fetch_add(1, Ordering::Relaxed); self.pool
.stats
.requests_total
.fetch_add(1, Ordering::Relaxed);
} }
} }
@ -735,14 +759,20 @@ impl<'a> PooledWsClientGuard<'a> {
pub fn failed(&self) { pub fn failed(&self) {
if let Some(ref client) = self.client { if let Some(ref client) = self.client {
self.pool.mark_unhealthy(client.endpoint()); self.pool.mark_unhealthy(client.endpoint());
self.pool.stats.requests_failed.fetch_add(1, Ordering::Relaxed); self.pool
.stats
.requests_failed
.fetch_add(1, Ordering::Relaxed);
} }
} }
/// Discards the connection (don't return to pool). /// Discards the connection (don't return to pool).
pub fn discard(mut self) { pub fn discard(mut self) {
if let Some(client) = self.client.take() { if let Some(client) = self.client.take() {
self.pool.stats.connections_closed.fetch_add(1, Ordering::Relaxed); self.pool
.stats
.connections_closed
.fetch_add(1, Ordering::Relaxed);
drop(client); drop(client);
} }
} }
@ -760,7 +790,10 @@ impl<'a> Drop for PooledWsClientGuard<'a> {
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait ConnectionPoolExt { pub trait ConnectionPoolExt {
/// Acquires an HTTP client and returns a guard. /// Acquires an HTTP client and returns a guard.
async fn acquire_http_guard(&self, endpoint: &str) -> Result<PooledHttpClientGuard<'_>, PoolError>; async fn acquire_http_guard(
&self,
endpoint: &str,
) -> Result<PooledHttpClientGuard<'_>, PoolError>;
/// Acquires a WebSocket client and returns a guard. /// Acquires a WebSocket client and returns a guard.
async fn acquire_ws_guard(&self, endpoint: &str) -> Result<PooledWsClientGuard<'_>, PoolError>; async fn acquire_ws_guard(&self, endpoint: &str) -> Result<PooledWsClientGuard<'_>, PoolError>;
@ -768,7 +801,10 @@ pub trait ConnectionPoolExt {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ConnectionPoolExt for ConnectionPool { impl ConnectionPoolExt for ConnectionPool {
async fn acquire_http_guard(&self, endpoint: &str) -> Result<PooledHttpClientGuard<'_>, PoolError> { async fn acquire_http_guard(
&self,
endpoint: &str,
) -> Result<PooledHttpClientGuard<'_>, PoolError> {
let client = self.acquire_http(endpoint).await?; let client = self.acquire_http(endpoint).await?;
Ok(PooledHttpClientGuard::new(self, client)) Ok(PooledHttpClientGuard::new(self, client))
} }
@ -806,7 +842,10 @@ mod tests {
#[test] #[test]
fn test_pool_config_high_throughput() { fn test_pool_config_high_throughput() {
let config = PoolConfig::high_throughput(); let config = PoolConfig::high_throughput();
assert!(config.max_connections_per_endpoint > PoolConfig::default().max_connections_per_endpoint); assert!(
config.max_connections_per_endpoint
> PoolConfig::default().max_connections_per_endpoint
);
} }
#[test] #[test]

View file

@ -131,7 +131,9 @@ impl RpcServer {
.await .await
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?; .map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
let addr = server.local_addr().map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?; let addr = server
.local_addr()
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
tracing::info!("HTTP RPC server listening on {}", addr); tracing::info!("HTTP RPC server listening on {}", addr);
self.http_handle = Some(server.start(module)); self.http_handle = Some(server.start(module));
@ -150,7 +152,9 @@ impl RpcServer {
.await .await
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?; .map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
let addr = server.local_addr().map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?; let addr = server
.local_addr()
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
tracing::info!("WebSocket RPC server listening on {}", addr); tracing::info!("WebSocket RPC server listening on {}", addr);
self.ws_handle = Some(server.start(module)); self.ws_handle = Some(server.start(module));
@ -235,9 +239,9 @@ impl RpcModuleBuilder {
F: Fn(jsonrpsee::types::Params<'_>) -> R + Send + Sync + Clone + 'static, F: Fn(jsonrpsee::types::Params<'_>) -> R + Send + Sync + Clone + 'static,
R: jsonrpsee::IntoResponse + Send + 'static, R: jsonrpsee::IntoResponse + Send + 'static,
{ {
let _ = self.module.register_method(name, move |params, _| { let _ = self
callback(params) .module
}); .register_method(name, move |params, _| callback(params));
self self
} }

View file

@ -131,7 +131,10 @@ impl fmt::Display for Error {
Error::Failed(msg) => write!(f, "Failed: {}", msg), Error::Failed(msg) => write!(f, "Failed: {}", msg),
Error::InvalidMethod => write!(f, "Invalid method"), Error::InvalidMethod => write!(f, "Invalid method"),
Error::InvalidArgs(msg) => write!(f, "Invalid arguments: {}", msg), Error::InvalidArgs(msg) => write!(f, "Invalid arguments: {}", msg),
Error::InsufficientBalance { required, available } => { Error::InsufficientBalance {
required,
available,
} => {
write!( write!(
f, f,
"Insufficient balance: required {}, available {}", "Insufficient balance: required {}, available {}",

View file

@ -61,7 +61,13 @@ pub fn storage_read(key: &[u8; 32], out: &mut [u8]) -> i32 {
/// Writes to storage. /// Writes to storage.
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn storage_write(key: &[u8; 32], value: &[u8]) -> i32 { pub fn storage_write(key: &[u8; 32], value: &[u8]) -> i32 {
unsafe { synor_storage_write(key.as_ptr() as i32, value.as_ptr() as i32, value.len() as i32) } unsafe {
synor_storage_write(
key.as_ptr() as i32,
value.as_ptr() as i32,
value.len() as i32,
)
}
} }
/// Deletes from storage. Returns 1 if key existed, 0 otherwise. /// Deletes from storage. Returns 1 if key existed, 0 otherwise.
@ -152,7 +158,13 @@ pub fn get_chain_id() -> u64 {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn sha3(data: &[u8]) -> [u8; 32] { pub fn sha3(data: &[u8]) -> [u8; 32] {
let mut out = [0u8; 32]; let mut out = [0u8; 32];
unsafe { synor_sha3(data.as_ptr() as i32, data.len() as i32, out.as_mut_ptr() as i32) }; unsafe {
synor_sha3(
data.as_ptr() as i32,
data.len() as i32,
out.as_mut_ptr() as i32,
)
};
out out
} }
@ -160,7 +172,13 @@ pub fn sha3(data: &[u8]) -> [u8; 32] {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn blake3(data: &[u8]) -> [u8; 32] { pub fn blake3(data: &[u8]) -> [u8; 32] {
let mut out = [0u8; 32]; let mut out = [0u8; 32];
unsafe { synor_blake3(data.as_ptr() as i32, data.len() as i32, out.as_mut_ptr() as i32) }; unsafe {
synor_blake3(
data.as_ptr() as i32,
data.len() as i32,
out.as_mut_ptr() as i32,
)
};
out out
} }

View file

@ -186,9 +186,7 @@ macro_rules! entry_point {
$crate::host::return_data(&data); $crate::host::return_data(&data);
data.len() as i32 data.len() as i32
} }
Err(e) => { Err(e) => $crate::host::revert(&e.to_string()),
$crate::host::revert(&e.to_string())
}
} }
} }
}; };

View file

@ -9,17 +9,16 @@
//! //!
//! Run with: cargo bench -p synor-storage --bench storage_bench //! Run with: cargo bench -p synor-storage --bench storage_bench
use criterion::{ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
};
use std::sync::Arc; use std::sync::Arc;
use synor_storage::{ use synor_storage::{
cache::{CacheConfig, LruCache, StorageCache}, cache::{CacheConfig, LruCache, StorageCache},
cf, Database, DatabaseConfig, cf,
stores::{ stores::{
ChainState, GhostdagStore, HeaderStore, MetadataStore, RelationsStore, ChainState, GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredGhostdagData,
StoredGhostdagData, StoredRelations, StoredUtxo, UtxoStore, StoredRelations, StoredUtxo, UtxoStore,
}, },
Database, DatabaseConfig,
}; };
use synor_types::{BlockHeader, BlockId, BlueScore, Hash256, Timestamp, TransactionId}; use synor_types::{BlockHeader, BlockId, BlueScore, Hash256, Timestamp, TransactionId};
use tempfile::TempDir; use tempfile::TempDir;
@ -83,16 +82,22 @@ fn make_ghostdag_data(n: u64) -> StoredGhostdagData {
StoredGhostdagData { StoredGhostdagData {
blue_score: n * 10, blue_score: n * 10,
selected_parent: make_block_id(n.saturating_sub(1)), selected_parent: make_block_id(n.saturating_sub(1)),
merge_set_blues: (0..5).map(|i| make_block_id(n.saturating_sub(i + 1))).collect(), merge_set_blues: (0..5)
.map(|i| make_block_id(n.saturating_sub(i + 1)))
.collect(),
merge_set_reds: (0..2).map(|i| make_block_id(n + i + 100)).collect(), merge_set_reds: (0..2).map(|i| make_block_id(n + i + 100)).collect(),
blues_anticone_sizes: (0..3).map(|i| (make_block_id(n.saturating_sub(i + 1)), i + 1)).collect(), blues_anticone_sizes: (0..3)
.map(|i| (make_block_id(n.saturating_sub(i + 1)), i + 1))
.collect(),
} }
} }
/// Creates a test relations entry. /// Creates a test relations entry.
fn make_relations(n: u64) -> StoredRelations { fn make_relations(n: u64) -> StoredRelations {
StoredRelations { StoredRelations {
parents: (1..=3).map(|i| make_block_id(n.saturating_sub(i))).collect(), parents: (1..=3)
.map(|i| make_block_id(n.saturating_sub(i)))
.collect(),
children: (1..=2).map(|i| make_block_id(n + i)).collect(), children: (1..=2).map(|i| make_block_id(n + i)).collect(),
} }
} }
@ -157,10 +162,7 @@ fn header_write_batch(c: &mut Criterion) {
let headers: Vec<BlockHeader> = (0..count).map(|i| make_header(i as u64)).collect(); let headers: Vec<BlockHeader> = (0..count).map(|i| make_header(i as u64)).collect();
group.throughput(Throughput::Elements(count as u64)); group.throughput(Throughput::Elements(count as u64));
group.bench_with_input( group.bench_with_input(BenchmarkId::from_parameter(count), &headers, |b, hdrs| {
BenchmarkId::from_parameter(count),
&headers,
|b, hdrs| {
b.iter_batched( b.iter_batched(
|| { || {
let (dir, db) = setup_db(); let (dir, db) = setup_db();
@ -174,8 +176,7 @@ fn header_write_batch(c: &mut Criterion) {
}, },
criterion::BatchSize::SmallInput, criterion::BatchSize::SmallInput,
) )
}, });
);
} }
group.finish(); group.finish();
@ -189,9 +190,7 @@ fn header_read_single(c: &mut Criterion) {
let store = populate_headers(&db, 1000); let store = populate_headers(&db, 1000);
let target_hash = make_header(500).block_id(); let target_hash = make_header(500).block_id();
group.bench_function("single", |b| { group.bench_function("single", |b| b.iter(|| black_box(store.get(&target_hash))));
b.iter(|| black_box(store.get(&target_hash)))
});
drop(dir); drop(dir);
group.finish(); group.finish();
@ -208,9 +207,7 @@ fn header_read_varying_db_sizes(c: &mut Criterion) {
group.bench_with_input( group.bench_with_input(
BenchmarkId::from_parameter(db_size), BenchmarkId::from_parameter(db_size),
&target_hash, &target_hash,
|b, hash| { |b, hash| b.iter(|| black_box(store.get(hash))),
b.iter(|| black_box(store.get(hash)))
},
); );
drop(dir); drop(dir);
@ -231,13 +228,9 @@ fn header_multi_get(c: &mut Criterion) {
.collect(); .collect();
group.throughput(Throughput::Elements(count as u64)); group.throughput(Throughput::Elements(count as u64));
group.bench_with_input( group.bench_with_input(BenchmarkId::from_parameter(count), &hashes, |b, h| {
BenchmarkId::from_parameter(count),
&hashes,
|b, h| {
b.iter(|| black_box(store.multi_get(h))) b.iter(|| black_box(store.multi_get(h)))
}, });
);
} }
drop(dir); drop(dir);
@ -304,9 +297,7 @@ fn utxo_lookup(c: &mut Criterion) {
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("size", db_size), BenchmarkId::new("size", db_size),
&target_txid, &target_txid,
|b, txid| { |b, txid| b.iter(|| black_box(store.get(txid, 0))),
b.iter(|| black_box(store.get(txid, 0)))
},
); );
drop(dir); drop(dir);
@ -320,10 +311,7 @@ fn utxo_delete(c: &mut Criterion) {
group.throughput(Throughput::Elements(1)); group.throughput(Throughput::Elements(1));
for db_size in [1000, 10000] { for db_size in [1000, 10000] {
group.bench_with_input( group.bench_with_input(BenchmarkId::new("size", db_size), &db_size, |b, &size| {
BenchmarkId::new("size", db_size),
&db_size,
|b, &size| {
b.iter_batched( b.iter_batched(
|| { || {
let (dir, db) = setup_db(); let (dir, db) = setup_db();
@ -338,8 +326,7 @@ fn utxo_delete(c: &mut Criterion) {
}, },
criterion::BatchSize::SmallInput, criterion::BatchSize::SmallInput,
) )
}, });
);
} }
group.finish(); group.finish();
@ -474,10 +461,7 @@ fn iterator_full_scan(c: &mut Criterion) {
let _ = populate_headers(&db, db_size); let _ = populate_headers(&db, db_size);
group.throughput(Throughput::Elements(db_size as u64)); group.throughput(Throughput::Elements(db_size as u64));
group.bench_with_input( group.bench_with_input(BenchmarkId::from_parameter(db_size), &db, |b, database| {
BenchmarkId::from_parameter(db_size),
&db,
|b, database| {
b.iter(|| { b.iter(|| {
let mut count = 0; let mut count = 0;
for _ in database.iter(cf::HEADERS).unwrap() { for _ in database.iter(cf::HEADERS).unwrap() {
@ -485,8 +469,7 @@ fn iterator_full_scan(c: &mut Criterion) {
} }
black_box(count) black_box(count)
}) })
}, });
);
drop(dir); drop(dir);
} }
@ -502,7 +485,13 @@ fn iterator_prefix_scan(c: &mut Criterion) {
for tx_num in 0..100 { for tx_num in 0..100 {
let txid = make_txid(tx_num); let txid = make_txid(tx_num);
for output_idx in 0..10 { for output_idx in 0..10 {
store.put(&txid, output_idx, &make_utxo(tx_num * 10 + output_idx as u64)).unwrap(); store
.put(
&txid,
output_idx,
&make_utxo(tx_num * 10 + output_idx as u64),
)
.unwrap();
} }
} }
@ -535,9 +524,7 @@ fn ghostdag_operations(c: &mut Criterion) {
// Read // Read
let target = make_block_id(500); let target = make_block_id(500);
group.bench_function("get", |b| { group.bench_function("get", |b| b.iter(|| black_box(store.get(&target))));
b.iter(|| black_box(store.get(&target)))
});
group.bench_function("get_blue_score", |b| { group.bench_function("get_blue_score", |b| {
b.iter(|| black_box(store.get_blue_score(&target))) b.iter(|| black_box(store.get_blue_score(&target)))
@ -581,9 +568,7 @@ fn relations_operations(c: &mut Criterion) {
let target = make_block_id(500); let target = make_block_id(500);
group.bench_function("get", |b| { group.bench_function("get", |b| b.iter(|| black_box(store.get(&target))));
b.iter(|| black_box(store.get(&target)))
});
group.bench_function("get_parents", |b| { group.bench_function("get_parents", |b| {
b.iter(|| black_box(store.get_parents(&target))) b.iter(|| black_box(store.get_parents(&target)))
@ -621,18 +606,14 @@ fn metadata_operations(c: &mut Criterion) {
let mut group = c.benchmark_group("metadata_store"); let mut group = c.benchmark_group("metadata_store");
group.bench_function("get_tips", |b| { group.bench_function("get_tips", |b| b.iter(|| black_box(store.get_tips())));
b.iter(|| black_box(store.get_tips()))
});
group.bench_function("set_tips", |b| { group.bench_function("set_tips", |b| {
let new_tips = vec![make_block_id(200), make_block_id(201)]; let new_tips = vec![make_block_id(200), make_block_id(201)];
b.iter(|| black_box(store.set_tips(&new_tips))) b.iter(|| black_box(store.set_tips(&new_tips)))
}); });
group.bench_function("get_genesis", |b| { group.bench_function("get_genesis", |b| b.iter(|| black_box(store.get_genesis())));
b.iter(|| black_box(store.get_genesis()))
});
group.bench_function("get_chain_state", |b| { group.bench_function("get_chain_state", |b| {
b.iter(|| black_box(store.get_chain_state())) b.iter(|| black_box(store.get_chain_state()))
@ -747,9 +728,7 @@ fn storage_cache_operations(c: &mut Criterion) {
}); });
// Stats access // Stats access
group.bench_function("stats", |b| { group.bench_function("stats", |b| b.iter(|| black_box(cache.stats())));
b.iter(|| black_box(cache.stats()))
});
// Total entries // Total entries
group.bench_function("total_entries", |b| { group.bench_function("total_entries", |b| {
@ -812,17 +791,9 @@ criterion_group!(
utxo_get_by_tx, utxo_get_by_tx,
); );
criterion_group!( criterion_group!(batch_benches, batch_write_mixed, batch_headers_and_utxos,);
batch_benches,
batch_write_mixed,
batch_headers_and_utxos,
);
criterion_group!( criterion_group!(iterator_benches, iterator_full_scan, iterator_prefix_scan,);
iterator_benches,
iterator_full_scan,
iterator_prefix_scan,
);
criterion_group!( criterion_group!(
store_benches, store_benches,
@ -837,10 +808,7 @@ criterion_group!(
storage_cache_operations, storage_cache_operations,
); );
criterion_group!( criterion_group!(db_benches, database_creation,);
db_benches,
database_creation,
);
criterion_main!( criterion_main!(
header_benches, header_benches,

View file

@ -3,9 +3,7 @@
//! Provides a typed interface to the underlying RocksDB instance. //! Provides a typed interface to the underlying RocksDB instance.
use crate::cf; use crate::cf;
use rocksdb::{ use rocksdb::{ColumnFamily, ColumnFamilyDescriptor, DBCompressionType, Options, WriteBatch, DB};
ColumnFamily, ColumnFamilyDescriptor, DBCompressionType, Options, WriteBatch, DB,
};
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
@ -182,7 +180,10 @@ impl Database {
} }
/// Opens a database in read-only mode. /// Opens a database in read-only mode.
pub fn open_read_only<P: AsRef<Path>>(path: P, config: &DatabaseConfig) -> Result<Self, DbError> { pub fn open_read_only<P: AsRef<Path>>(
path: P,
config: &DatabaseConfig,
) -> Result<Self, DbError> {
let path_str = path.as_ref().to_string_lossy().to_string(); let path_str = path.as_ref().to_string_lossy().to_string();
let opts = config.to_options(); let opts = config.to_options();
@ -241,7 +242,10 @@ impl Database {
} }
/// Iterates over all keys in a column family. /// Iterates over all keys in a column family.
pub fn iter(&self, cf_name: &str) -> Result<impl Iterator<Item = (Box<[u8]>, Box<[u8]>)> + '_, DbError> { pub fn iter(
&self,
cf_name: &str,
) -> Result<impl Iterator<Item = (Box<[u8]>, Box<[u8]>)> + '_, DbError> {
let cf = self.get_cf(cf_name)?; let cf = self.get_cf(cf_name)?;
let iter = self.db.iterator_cf(cf, rocksdb::IteratorMode::Start); let iter = self.db.iterator_cf(cf, rocksdb::IteratorMode::Start);
Ok(iter.map(|r| r.unwrap())) Ok(iter.map(|r| r.unwrap()))

View file

@ -32,8 +32,8 @@ pub use cache::{CacheConfig, CacheSizeInfo, CacheStats, StorageCache};
pub use db::{Database, DatabaseConfig, DbError}; pub use db::{Database, DatabaseConfig, DbError};
pub use stores::{ pub use stores::{
BatchStore, BlockBody, BlockStore, ChainState, ContractStateStore, ContractStore, BatchStore, BlockBody, BlockStore, ChainState, ContractStateStore, ContractStore,
GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredContract, GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredContract, StoredGhostdagData,
StoredGhostdagData, StoredRelations, StoredUtxo, TransactionStore, UtxoStore, StoredRelations, StoredUtxo, TransactionStore, UtxoStore,
}; };
/// Column family names. /// Column family names.

View file

@ -39,8 +39,7 @@ impl HeaderStore {
/// Stores a header. /// Stores a header.
pub fn put(&self, header: &BlockHeader) -> Result<(), DbError> { pub fn put(&self, header: &BlockHeader) -> Result<(), DbError> {
let hash = header.block_id(); let hash = header.block_id();
let bytes = borsh::to_vec(header) let bytes = borsh::to_vec(header).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::HEADERS, hash.as_bytes(), &bytes) self.db.put(cf::HEADERS, hash.as_bytes(), &bytes)
} }
@ -133,8 +132,7 @@ impl BlockStore {
/// Stores a block body. /// Stores a block body.
pub fn put(&self, hash: &Hash256, body: &BlockBody) -> Result<(), DbError> { pub fn put(&self, hash: &Hash256, body: &BlockBody) -> Result<(), DbError> {
let bytes = borsh::to_vec(body) let bytes = borsh::to_vec(body).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::BLOCKS, hash.as_bytes(), &bytes) self.db.put(cf::BLOCKS, hash.as_bytes(), &bytes)
} }
@ -181,8 +179,7 @@ impl TransactionStore {
/// Stores a transaction. /// Stores a transaction.
pub fn put(&self, tx: &Transaction) -> Result<(), DbError> { pub fn put(&self, tx: &Transaction) -> Result<(), DbError> {
let txid = tx.txid(); let txid = tx.txid();
let bytes = borsh::to_vec(tx) let bytes = borsh::to_vec(tx).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::TRANSACTIONS, txid.as_bytes(), &bytes) self.db.put(cf::TRANSACTIONS, txid.as_bytes(), &bytes)
} }
@ -268,8 +265,7 @@ impl UtxoStore {
/// Stores a UTXO. /// Stores a UTXO.
pub fn put(&self, txid: &TransactionId, index: u32, utxo: &StoredUtxo) -> Result<(), DbError> { pub fn put(&self, txid: &TransactionId, index: u32, utxo: &StoredUtxo) -> Result<(), DbError> {
let key = Self::make_key(txid, index); let key = Self::make_key(txid, index);
let bytes = borsh::to_vec(utxo) let bytes = borsh::to_vec(utxo).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::UTXOS, &key, &bytes) self.db.put(cf::UTXOS, &key, &bytes)
} }
@ -352,8 +348,7 @@ impl RelationsStore {
/// Stores relations for a block. /// Stores relations for a block.
pub fn put(&self, block_id: &BlockId, relations: &StoredRelations) -> Result<(), DbError> { pub fn put(&self, block_id: &BlockId, relations: &StoredRelations) -> Result<(), DbError> {
let bytes = borsh::to_vec(relations) let bytes = borsh::to_vec(relations).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::RELATIONS, block_id.as_bytes(), &bytes) self.db.put(cf::RELATIONS, block_id.as_bytes(), &bytes)
} }
@ -429,8 +424,7 @@ impl GhostdagStore {
/// Stores GHOSTDAG data for a block. /// Stores GHOSTDAG data for a block.
pub fn put(&self, block_id: &BlockId, data: &StoredGhostdagData) -> Result<(), DbError> { pub fn put(&self, block_id: &BlockId, data: &StoredGhostdagData) -> Result<(), DbError> {
let bytes = borsh::to_vec(data) let bytes = borsh::to_vec(data).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::GHOSTDAG, block_id.as_bytes(), &bytes) self.db.put(cf::GHOSTDAG, block_id.as_bytes(), &bytes)
} }
@ -500,8 +494,7 @@ impl MetadataStore {
/// Sets the current DAG tips. /// Sets the current DAG tips.
pub fn set_tips(&self, tips: &[BlockId]) -> Result<(), DbError> { pub fn set_tips(&self, tips: &[BlockId]) -> Result<(), DbError> {
let bytes = borsh::to_vec(tips) let bytes = borsh::to_vec(tips).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::METADATA, Self::KEY_TIPS, &bytes) self.db.put(cf::METADATA, Self::KEY_TIPS, &bytes)
} }
@ -519,8 +512,7 @@ impl MetadataStore {
/// Sets the pruning point. /// Sets the pruning point.
pub fn set_pruning_point(&self, point: &BlockId) -> Result<(), DbError> { pub fn set_pruning_point(&self, point: &BlockId) -> Result<(), DbError> {
let bytes = borsh::to_vec(point) let bytes = borsh::to_vec(point).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::METADATA, Self::KEY_PRUNING_POINT, &bytes) self.db.put(cf::METADATA, Self::KEY_PRUNING_POINT, &bytes)
} }
@ -538,8 +530,7 @@ impl MetadataStore {
/// Sets the chain state. /// Sets the chain state.
pub fn set_chain_state(&self, state: &ChainState) -> Result<(), DbError> { pub fn set_chain_state(&self, state: &ChainState) -> Result<(), DbError> {
let bytes = borsh::to_vec(state) let bytes = borsh::to_vec(state).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::METADATA, Self::KEY_CHAIN_STATE, &bytes) self.db.put(cf::METADATA, Self::KEY_CHAIN_STATE, &bytes)
} }
@ -557,8 +548,7 @@ impl MetadataStore {
/// Sets the genesis block ID. /// Sets the genesis block ID.
pub fn set_genesis(&self, genesis: &BlockId) -> Result<(), DbError> { pub fn set_genesis(&self, genesis: &BlockId) -> Result<(), DbError> {
let bytes = borsh::to_vec(genesis) let bytes = borsh::to_vec(genesis).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::METADATA, Self::KEY_GENESIS, &bytes) self.db.put(cf::METADATA, Self::KEY_GENESIS, &bytes)
} }
@ -631,8 +621,7 @@ impl ContractStore {
/// Stores a contract. /// Stores a contract.
pub fn put(&self, contract: &StoredContract) -> Result<(), DbError> { pub fn put(&self, contract: &StoredContract) -> Result<(), DbError> {
let bytes = borsh::to_vec(contract) let bytes = borsh::to_vec(contract).map_err(|e| DbError::Serialization(e.to_string()))?;
.map_err(|e| DbError::Serialization(e.to_string()))?;
self.db.put(cf::CONTRACTS, &contract.code_hash, &bytes) self.db.put(cf::CONTRACTS, &contract.code_hash, &bytes)
} }
@ -694,11 +683,7 @@ impl ContractStateStore {
} }
/// Deletes a storage value. /// Deletes a storage value.
pub fn delete( pub fn delete(&self, contract_id: &[u8; 32], storage_key: &[u8; 32]) -> Result<(), DbError> {
&self,
contract_id: &[u8; 32],
storage_key: &[u8; 32],
) -> Result<(), DbError> {
let key = Self::make_key(contract_id, storage_key); let key = Self::make_key(contract_id, storage_key);
self.db.delete(cf::CONTRACT_STATE, &key) self.db.delete(cf::CONTRACT_STATE, &key)
} }

View file

@ -95,11 +95,7 @@ impl Address {
} }
/// Creates an address from raw components. /// Creates an address from raw components.
pub fn from_parts( pub fn from_parts(network: Network, addr_type: AddressType, payload: [u8; 32]) -> Self {
network: Network,
addr_type: AddressType,
payload: [u8; 32],
) -> Self {
Address { Address {
network, network,
addr_type, addr_type,
@ -110,8 +106,8 @@ impl Address {
/// Parses an address from a Bech32m string. /// Parses an address from a Bech32m string.
pub fn from_str(s: &str) -> Result<Self, AddressError> { pub fn from_str(s: &str) -> Result<Self, AddressError> {
// Decode Bech32m // Decode Bech32m
let (hrp, data) = bech32::decode(s) let (hrp, data) =
.map_err(|e| AddressError::Bech32Error(e.to_string()))?; bech32::decode(s).map_err(|e| AddressError::Bech32Error(e.to_string()))?;
// Determine network from HRP // Determine network from HRP
let network = match hrp.as_str() { let network = match hrp.as_str() {
@ -169,10 +165,7 @@ impl Address {
/// Returns true if this is a post-quantum address. /// Returns true if this is a post-quantum address.
pub fn is_post_quantum(&self) -> bool { pub fn is_post_quantum(&self) -> bool {
matches!( matches!(self.addr_type, AddressType::P2pkhPqc | AddressType::P2shPqc)
self.addr_type,
AddressType::P2pkhPqc | AddressType::P2shPqc
)
} }
/// Returns a short representation of the address for display. /// Returns a short representation of the address for display.
@ -258,9 +251,8 @@ impl borsh::BorshDeserialize for Address {
let mut type_byte = [0u8; 1]; let mut type_byte = [0u8; 1];
reader.read_exact(&mut type_byte)?; reader.read_exact(&mut type_byte)?;
let addr_type = AddressType::from_byte(type_byte[0]).map_err(|e| { let addr_type = AddressType::from_byte(type_byte[0])
std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()) .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
})?;
let mut payload = [0u8; 32]; let mut payload = [0u8; 32];
reader.read_exact(&mut payload)?; reader.read_exact(&mut payload)?;
@ -324,8 +316,7 @@ mod tests {
let ed_pubkey = [1u8; 32]; let ed_pubkey = [1u8; 32];
let dilithium_pubkey = vec![2u8; 1312]; // Dilithium3 public key size let dilithium_pubkey = vec![2u8; 1312]; // Dilithium3 public key size
let addr = let addr = Address::from_hybrid_pubkey(Network::Mainnet, &ed_pubkey, &dilithium_pubkey);
Address::from_hybrid_pubkey(Network::Mainnet, &ed_pubkey, &dilithium_pubkey);
assert!(addr.is_post_quantum()); assert!(addr.is_post_quantum());
assert_eq!(addr.addr_type(), AddressType::P2pkhPqc); assert_eq!(addr.addr_type(), AddressType::P2pkhPqc);

View file

@ -159,11 +159,7 @@ impl BlockBody {
/// Computes the Merkle root of transactions. /// Computes the Merkle root of transactions.
pub fn merkle_root(&self) -> Hash256 { pub fn merkle_root(&self) -> Hash256 {
let tx_hashes: Vec<Hash256> = self let tx_hashes: Vec<Hash256> = self.transactions.iter().map(|tx| tx.txid()).collect();
.transactions
.iter()
.map(|tx| tx.txid())
.collect();
Hash256::merkle_root(&tx_hashes) Hash256::merkle_root(&tx_hashes)
} }

View file

@ -248,9 +248,8 @@ mod tests {
#[test] #[test]
fn test_hash_display() { fn test_hash_display() {
let hash = Hash256::from_hex( let hash =
"abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", Hash256::from_hex("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")
)
.unwrap(); .unwrap();
let display = format!("{}", hash); let display = format!("{}", hash);
assert!(display.contains("...")); assert!(display.contains("..."));

View file

@ -16,7 +16,9 @@ pub use hash::{Hash256, HashError};
pub use transaction::{Transaction, TransactionId, TxInput, TxOutput}; pub use transaction::{Transaction, TransactionId, TxInput, TxOutput};
/// Network identifier for Synor chains. /// Network identifier for Synor chains.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] #[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub enum Network { pub enum Network {
/// Main production network /// Main production network
Mainnet, Mainnet,

View file

@ -13,7 +13,16 @@ pub type TransactionId = Hash256;
/// Outpoint - references a specific output of a previous transaction. /// Outpoint - references a specific output of a previous transaction.
#[derive( #[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
)] )]
pub struct Outpoint { pub struct Outpoint {
/// The transaction ID containing the output. /// The transaction ID containing the output.
@ -184,7 +193,16 @@ impl ScriptPubKey {
/// Script type enumeration. /// Script type enumeration.
#[derive( #[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
)] )]
#[borsh(use_discriminant = true)] #[borsh(use_discriminant = true)]
#[repr(u8)] #[repr(u8)]
@ -222,10 +240,7 @@ pub struct Transaction {
impl Transaction { impl Transaction {
/// Creates a new transaction. /// Creates a new transaction.
pub fn new( pub fn new(inputs: Vec<TxInput>, outputs: Vec<TxOutput>) -> Self {
inputs: Vec<TxInput>,
outputs: Vec<TxOutput>,
) -> Self {
Transaction { Transaction {
version: 1, version: 1,
inputs, inputs,
@ -263,9 +278,7 @@ impl Transaction {
/// Computes the total output value. /// Computes the total output value.
pub fn total_output(&self) -> Amount { pub fn total_output(&self) -> Amount {
self.outputs self.outputs.iter().fold(Amount::ZERO, |acc, output| {
.iter()
.fold(Amount::ZERO, |acc, output| {
acc.saturating_add(output.amount) acc.saturating_add(output.amount)
}) })
} }
@ -303,7 +316,16 @@ impl fmt::Display for Transaction {
/// Subnetwork identifier. /// Subnetwork identifier.
#[derive( #[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
)] )]
pub struct SubnetworkId([u8; 20]); pub struct SubnetworkId([u8; 20]);
@ -312,10 +334,12 @@ impl SubnetworkId {
pub const NATIVE: SubnetworkId = SubnetworkId([0u8; 20]); pub const NATIVE: SubnetworkId = SubnetworkId([0u8; 20]);
/// Coinbase subnetwork. /// Coinbase subnetwork.
pub const COINBASE: SubnetworkId = SubnetworkId([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); pub const COINBASE: SubnetworkId =
SubnetworkId([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
/// Registry subnetwork (for registrations). /// Registry subnetwork (for registrations).
pub const REGISTRY: SubnetworkId = SubnetworkId([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); pub const REGISTRY: SubnetworkId =
SubnetworkId([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
/// Creates a new subnetwork ID from bytes. /// Creates a new subnetwork ID from bytes.
pub fn from_bytes(bytes: [u8; 20]) -> Self { pub fn from_bytes(bytes: [u8; 20]) -> Self {
@ -362,10 +386,7 @@ mod tests {
#[test] #[test]
fn test_coinbase_transaction() { fn test_coinbase_transaction() {
let output = TxOutput::new( let output = TxOutput::new(Amount::from_synor(500), ScriptPubKey::p2pkh(&[0u8; 32]));
Amount::from_synor(500),
ScriptPubKey::p2pkh(&[0u8; 32]),
);
let tx = Transaction::coinbase(vec![output], b"Synor Genesis".to_vec()); let tx = Transaction::coinbase(vec![output], b"Synor Genesis".to_vec());
assert!(tx.is_coinbase()); assert!(tx.is_coinbase());
@ -375,14 +396,8 @@ mod tests {
#[test] #[test]
fn test_transaction_id() { fn test_transaction_id() {
let input = TxInput::new( let input = TxInput::new(Outpoint::new(Hash256::blake3(b"prev_tx"), 0), vec![]);
Outpoint::new(Hash256::blake3(b"prev_tx"), 0), let output = TxOutput::new(Amount::from_synor(10), ScriptPubKey::p2pkh(&[1u8; 32]));
vec![],
);
let output = TxOutput::new(
Amount::from_synor(10),
ScriptPubKey::p2pkh(&[1u8; 32]),
);
let tx = Transaction::new(vec![input], vec![output]); let tx = Transaction::new(vec![input], vec![output]);
let txid = tx.txid(); let txid = tx.txid();

View file

@ -10,11 +10,9 @@ use std::sync::Arc;
use parking_lot::RwLock; use parking_lot::RwLock;
use wasmtime::{ use wasmtime::{
Config, Engine, Instance, Linker, Memory, Module, Store, StoreLimits, Config, Engine, Instance, Linker, Memory, Module, Store, StoreLimits, StoreLimitsBuilder,
StoreLimitsBuilder,
}; };
use crate::context::ExecutionContext; use crate::context::ExecutionContext;
use crate::gas::GasMeter; use crate::gas::GasMeter;
use crate::storage::{ContractStorage, MemoryStorage}; use crate::storage::{ContractStorage, MemoryStorage};
@ -243,7 +241,11 @@ impl VmEngine {
// Read from storage // Read from storage
let data = caller.data_mut(); let data = caller.data_mut();
let storage_key = crate::storage::StorageKey::new(key); let storage_key = crate::storage::StorageKey::new(key);
match data.context.storage.get(&data.context.call.contract, &storage_key) { match data
.context
.storage
.get(&data.context.call.contract, &storage_key)
{
Some(value) => { Some(value) => {
let len = value.0.len(); let len = value.0.len();
// Write to output buffer // Write to output buffer

View file

@ -100,7 +100,10 @@ impl GasEstimator {
); );
// Execute init // Execute init
match self.engine.execute(&module, "__synor_init", init_params, context, self.max_gas) { match self
.engine
.execute(&module, "__synor_init", init_params, context, self.max_gas)
{
Ok(result) => GasEstimate::success(result.gas_used, result.return_data), Ok(result) => GasEstimate::success(result.gas_used, result.return_data),
Err(e) => { Err(e) => {
// Try to extract gas used from error // Try to extract gas used from error
@ -141,7 +144,10 @@ impl GasEstimator {
); );
// Execute call // Execute call
match self.engine.execute(module, "__synor_call", &call_data, context, self.max_gas) { match self
.engine
.execute(module, "__synor_call", &call_data, context, self.max_gas)
{
Ok(result) => GasEstimate::success(result.gas_used, result.return_data), Ok(result) => GasEstimate::success(result.gas_used, result.return_data),
Err(e) => { Err(e) => {
let gas = match &e { let gas = match &e {
@ -167,7 +173,14 @@ impl GasEstimator {
storage: MemoryStorage, storage: MemoryStorage,
) -> GasEstimate { ) -> GasEstimate {
// First, try with max gas to see if it succeeds // First, try with max gas to see if it succeeds
let initial = self.estimate_call(module, method, params, caller.clone(), value, storage.clone()); let initial = self.estimate_call(
module,
method,
params,
caller.clone(),
value,
storage.clone(),
);
if !initial.success { if !initial.success {
return initial; return initial;
@ -197,7 +210,10 @@ impl GasEstimator {
1, // chain_id 1, // chain_id
); );
match self.engine.execute(module, "__synor_call", &call_data, context, mid) { match self
.engine
.execute(module, "__synor_call", &call_data, context, mid)
{
Ok(_) => high = mid, Ok(_) => high = mid,
Err(_) => low = mid + 1, Err(_) => low = mid + 1,
} }
@ -334,7 +350,10 @@ mod tests {
fn test_calldata_cost() { fn test_calldata_cost() {
let data = [0u8, 1, 0, 2, 0, 3]; // 3 zeros, 3 non-zeros let data = [0u8, 1, 0, 2, 0, 3]; // 3 zeros, 3 non-zeros
let cost = costs::calldata_cost(&data); let cost = costs::calldata_cost(&data);
assert_eq!(cost, 3 * costs::CALLDATA_ZERO_BYTE + 3 * costs::CALLDATA_BYTE); assert_eq!(
cost,
3 * costs::CALLDATA_ZERO_BYTE + 3 * costs::CALLDATA_BYTE
);
} }
#[test] #[test]

View file

@ -83,7 +83,10 @@ impl HostFunctions {
// Add refund for deletion // Add refund for deletion
ctx.gas.add_refund(ctx.gas.config().storage_delete_refund); ctx.gas.add_refund(ctx.gas.config().storage_delete_refund);
let existed = ctx.storage.delete(&ctx.call.contract, &storage_key).is_some(); let existed = ctx
.storage
.delete(&ctx.call.contract, &storage_key)
.is_some();
Ok(existed) Ok(existed)
} }

View file

@ -13,7 +13,9 @@ use synor_types::Hash256;
use crate::ContractId; use crate::ContractId;
/// Storage key (256-bit). /// Storage key (256-bit).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, BorshSerialize, BorshDeserialize)] #[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, BorshSerialize, BorshDeserialize,
)]
pub struct StorageKey(pub [u8; 32]); pub struct StorageKey(pub [u8; 32]);
impl StorageKey { impl StorageKey {
@ -209,10 +211,7 @@ impl MemoryStorage {
for (contract, pending_changes) in self.pending.drain() { for (contract, pending_changes) in self.pending.drain() {
for (key, new_value) in pending_changes { for (key, new_value) in pending_changes {
// Get old value from committed data // Get old value from committed data
let old_value = self.data let old_value = self.data.get(&contract).and_then(|m| m.get(&key)).cloned();
.get(&contract)
.and_then(|m| m.get(&key))
.cloned();
changes.push(crate::StorageChange { changes.push(crate::StorageChange {
contract, contract,
@ -233,10 +232,7 @@ impl MemoryStorage {
for (contract, pending_changes) in &self.pending { for (contract, pending_changes) in &self.pending {
for (key, new_value) in pending_changes { for (key, new_value) in pending_changes {
// Get old value from committed data // Get old value from committed data
let old_value = self.data let old_value = self.data.get(contract).and_then(|m| m.get(key)).cloned();
.get(contract)
.and_then(|m| m.get(key))
.cloned();
changes.push(crate::StorageChange { changes.push(crate::StorageChange {
contract: *contract, contract: *contract,
@ -261,10 +257,7 @@ impl ContractStorage for MemoryStorage {
} }
// Then check committed // Then check committed
self.data self.data.get(contract).and_then(|m| m.get(key)).cloned()
.get(contract)
.and_then(|m| m.get(key))
.cloned()
} }
fn set(&mut self, contract: &ContractId, key: StorageKey, value: StorageValue) { fn set(&mut self, contract: &ContractId, key: StorageKey, value: StorageValue) {
@ -514,13 +507,21 @@ mod tests {
let root1 = storage.root(&contract); let root1 = storage.root(&contract);
storage.set(&contract, StorageKey::from_str("a"), StorageValue::from_u64(1)); storage.set(
&contract,
StorageKey::from_str("a"),
StorageValue::from_u64(1),
);
storage.commit(); storage.commit();
let root2 = storage.root(&contract); let root2 = storage.root(&contract);
assert_ne!(root1, root2); assert_ne!(root1, root2);
storage.set(&contract, StorageKey::from_str("b"), StorageValue::from_u64(2)); storage.set(
&contract,
StorageKey::from_str("b"),
StorageValue::from_u64(2),
);
storage.commit(); storage.commit();
let root3 = storage.root(&contract); let root3 = storage.root(&contract);