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
});
let response = self
.client
.post(&self.url)
.json(&request)
.send()
.await?;
let response = self.client.post(&self.url).json(&request).send().await?;
let rpc_response: RpcResponse<T> = response.json().await?;
@ -68,7 +63,8 @@ impl RpcClient {
/// Gets a block by hash.
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.
@ -235,7 +231,11 @@ impl RpcClient {
}
/// 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(
"synor_getStorageAt",
json!({

View file

@ -22,12 +22,20 @@ pub async fn validate(address: &str, format: OutputFormat) -> Result<()> {
OutputFormat::Text => {
if validation.is_valid {
output::print_success("Address is valid");
output::print_kv("Network", validation.network.as_deref().unwrap_or("unknown"));
output::print_kv("Type", validation.address_type.as_deref().unwrap_or("unknown"));
output::print_kv(
"Network",
validation.network.as_deref().unwrap_or("unknown"),
);
output::print_kv(
"Type",
validation.address_type.as_deref().unwrap_or("unknown"),
);
} else {
output::print_error(&format!(
"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_kv("Hash", &block.hash);
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("Bits", &format!("0x{:08x}", block.header.bits));
output::print_kv("Nonce", &block.header.nonce.to_string());

View file

@ -18,24 +18,55 @@ pub async fn handle(
format: OutputFormat,
) -> Result<()> {
match cmd {
ContractCommands::Deploy { wasm, deployer, args, gas } => {
deploy(client, wasm, &deployer, args.as_deref(), Some(gas), format).await
}
ContractCommands::Call { 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::Deploy {
wasm,
deployer,
args,
gas,
} => deploy(client, wasm, &deployer, args.as_deref(), Some(gas), format).await,
ContractCommands::Call {
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 } => {
storage(client, &contract_id, &key, format).await
}
ContractCommands::EstimateGas { contract_id, method, caller, 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
ContractCommands::EstimateGas {
contract_id,
method,
caller,
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_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 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();
if let Some(error) = &result.error {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"success": false,
"error": error
}))?);
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"success": false,
"error": error
}))?
);
}
OutputFormat::Text => {
output::print_error(&format!("Deployment failed: {}", error));
@ -79,12 +118,15 @@ async fn deploy(
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"success": true,
"contractId": result.contract_id,
"address": result.address,
"gasUsed": result.gas_used,
}))?);
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"success": true,
"contractId": result.contract_id,
"address": result.address,
"gasUsed": result.gas_used,
}))?
);
}
OutputFormat::Text => {
output::print_success("Contract deployed!");
@ -110,15 +152,20 @@ async fn call(
) -> Result<()> {
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 {
match format {
OutputFormat::Json => {
output::print_value(&serde_json::json!({
"success": false,
"error": error
}), format);
output::print_value(
&serde_json::json!({
"success": false,
"error": error
}),
format,
);
}
OutputFormat::Text => {
output::print_error(&format!("Contract call failed: {}", error));
@ -129,12 +176,15 @@ async fn call(
match format {
OutputFormat::Json => {
output::print_value(&serde_json::json!({
"success": result.success,
"data": result.data,
"gasUsed": result.gas_used,
"logs": result.logs
}), format);
output::print_value(
&serde_json::json!({
"success": result.success,
"data": result.data,
"gasUsed": result.gas_used,
"logs": result.logs
}),
format,
);
}
OutputFormat::Text => {
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 {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?);
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?
);
}
OutputFormat::Text => {
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.
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?;
if let Some(error) = &result.error {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?);
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?
);
}
OutputFormat::Text => {
output::print_error(&format!("Failed to get storage: {}", error));
@ -258,14 +319,19 @@ async fn estimate_gas(
) -> Result<()> {
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 {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?);
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?
);
}
OutputFormat::Text => {
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 {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?);
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?
);
}
OutputFormat::Text => {
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 {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"contractId": contract_id,
"codeHash": result.code_hash,
"deployer": result.deployer,
"deployedAt": result.deployed_at,
"deployedHeight": result.deployed_height,
}))?);
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"contractId": contract_id,
"codeHash": result.code_hash,
"deployer": result.deployer,
"deployedAt": result.deployed_at,
"deployedHeight": result.deployed_height,
}))?
);
}
OutputFormat::Text => {
output::print_header("Contract Info");

View file

@ -53,7 +53,17 @@ pub async fn handle(
voter,
choice,
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 {
proposal_id,
executor,
@ -75,15 +85,9 @@ async fn info(client: &RpcClient, format: OutputFormat) -> Result<()> {
}
OutputFormat::Text => {
output::print_header("Governance Info");
output::print_kv(
"Proposal Threshold",
&format_synor(info.proposal_threshold),
);
output::print_kv("Proposal Threshold", &format_synor(info.proposal_threshold));
output::print_kv("Quorum", &format!("{}%", info.quorum_bps as f64 / 100.0));
output::print_kv(
"Voting Period",
&format_blocks(info.voting_period_blocks),
);
output::print_kv("Voting Period", &format_blocks(info.voting_period_blocks));
output::print_kv(
"Execution Delay",
&format_blocks(info.execution_delay_blocks),
@ -125,11 +129,7 @@ async fn stats(client: &RpcClient, format: OutputFormat) -> Result<()> {
}
/// List proposals.
async fn proposals(
client: &RpcClient,
state: Option<&str>,
format: OutputFormat,
) -> Result<()> {
async fn proposals(client: &RpcClient, state: Option<&str>, format: OutputFormat) -> Result<()> {
let proposals = match state {
Some(s) => client.get_proposals_by_state(s).await?,
None => client.get_active_proposals().await?,
@ -171,7 +171,11 @@ async fn proposals(
println!(
" Participation: {:.2}% | Quorum: {}",
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 {
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)?);
}
OutputFormat::Text => {
output::print_header(&format!("Proposal #{}: {}", proposal.number, proposal.title));
output::print_header(&format!(
"Proposal #{}: {}",
proposal.number, proposal.title
));
println!();
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("Proposer", &proposal.proposer);
println!();
@ -212,8 +222,14 @@ async fn proposal(client: &RpcClient, id: &str, format: OutputFormat) -> Result<
println!();
println!("Timeline:");
output::print_kv(" Created", &format!("Block {}", proposal.created_at_block));
output::print_kv(" Voting Starts", &format!("Block {}", proposal.voting_starts_block));
output::print_kv(" Voting Ends", &format!("Block {}", proposal.voting_ends_block));
output::print_kv(
" Voting Starts",
&format!("Block {}", proposal.voting_starts_block),
);
output::print_kv(
" Voting Ends",
&format!("Block {}", proposal.voting_ends_block),
);
output::print_kv(
" Execution Allowed",
&format!("Block {}", proposal.execution_allowed_block),
@ -226,7 +242,10 @@ async fn proposal(client: &RpcClient, id: &str, format: OutputFormat) -> Result<
} else {
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(" Abstain", &format_synor(proposal.abstain_votes));
output::print_kv(" Total Voters", &proposal.votes.len().to_string());
@ -276,8 +295,10 @@ async fn create_proposal(
// Build proposal params based on type
let params = match proposal_type {
"treasury_spend" | "ecosystem_grant" => {
let recipient = recipient.ok_or_else(|| anyhow::anyhow!("--recipient required for treasury proposals"))?;
let amount = amount.ok_or_else(|| anyhow::anyhow!("--amount required for treasury proposals"))?;
let recipient = recipient
.ok_or_else(|| anyhow::anyhow!("--recipient required for treasury proposals"))?;
let amount = amount
.ok_or_else(|| anyhow::anyhow!("--amount required for treasury proposals"))?;
json!({
"recipient": recipient,
"amount": amount
@ -423,11 +444,18 @@ async fn treasury(client: &RpcClient, format: OutputFormat) -> Result<()> {
println!("Pools:");
for pool in &pools {
println!();
let status = if pool.frozen { "🔒 FROZEN" } else { "✅ Active" };
let status = if pool.frozen {
"🔒 FROZEN"
} else {
"✅ Active"
};
println!(" {} [{}]", pool.name, status);
println!(" ID: {}", pool.id);
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));
}
}
@ -445,7 +473,11 @@ async fn treasury_pool(client: &RpcClient, id: &str, format: OutputFormat) -> Re
println!("{}", serde_json::to_string_pretty(&pool)?);
}
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_kv("ID", &pool.id);
output::print_kv("Balance", &format_synor(pool.balance));
@ -466,7 +498,9 @@ fn format_synor(amount: u64) -> String {
if frac == 0 {
format!("{} SYNOR", whole)
} 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;
/// Handle mining commands.
pub async fn handle(
client: &RpcClient,
cmd: MiningCommands,
format: OutputFormat,
) -> Result<()> {
pub async fn handle(client: &RpcClient, cmd: MiningCommands, format: OutputFormat) -> Result<()> {
match cmd {
MiningCommands::Info => info(client, 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_kv("Blocks", &info.blocks.to_string());
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 {
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_kv("Version", &template.header.version.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("Blue Score", &template.header.blue_score.to_string());
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)?);
}
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));
}
}

View file

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

View file

@ -12,11 +12,7 @@ use crate::wallet::Wallet;
use crate::WalletCommands;
/// Handle wallet commands.
pub async fn handle(
config: &CliConfig,
cmd: WalletCommands,
format: OutputFormat,
) -> Result<()> {
pub async fn handle(config: &CliConfig, cmd: WalletCommands, format: OutputFormat) -> Result<()> {
match cmd {
WalletCommands::Create { name } => create(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(
"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)");
@ -134,7 +133,10 @@ async fn import(config: &CliConfig, name: &str, format: OutputFormat) -> Result<
output::print_kv("Name", &wallet.name);
output::print_kv(
"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,26 +164,26 @@ async fn export(config: &CliConfig, name: &str, format: OutputFormat) -> Result<
// Note: We can't export the mnemonic from the derived seed
// The user should have written down the mnemonic during creation
match wallet.export_seed_phrase(&password) {
Ok(seed_phrase) => {
match format {
OutputFormat::Json => {
let result = serde_json::json!({
"name": wallet.name,
"seed_phrase": seed_phrase,
});
println!("{}", serde_json::to_string_pretty(&result)?);
}
OutputFormat::Text => {
output::print_warning("Keep this seed phrase secret and safe!");
println!();
println!(" {}", seed_phrase);
println!();
}
Ok(seed_phrase) => match format {
OutputFormat::Json => {
let result = serde_json::json!({
"name": wallet.name,
"seed_phrase": seed_phrase,
});
println!("{}", serde_json::to_string_pretty(&result)?);
}
}
OutputFormat::Text => {
output::print_warning("Keep this seed phrase secret and safe!");
println!();
println!(" {}", seed_phrase);
println!();
}
},
Err(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("Key Type", "Hybrid (Ed25519 + Dilithium)");
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() {
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)]
struct Cli {
/// 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,
/// Configuration file path
@ -472,39 +477,29 @@ async fn main() {
Commands::Balance { address } => {
commands::wallet::balance(&client, &config, address.as_deref(), output).await
}
Commands::Utxos { address } => {
commands::wallet::utxos(&client, &address, output).await
}
Commands::Utxos { address } => commands::wallet::utxos(&client, &address, output).await,
// Address commands
Commands::ValidateAddress { address } => {
commands::address::validate(&address, output).await
}
Commands::DecodeAddress { address } => {
commands::address::decode(&address, output).await
}
Commands::DecodeAddress { address } => commands::address::decode(&address, output).await,
// Mining commands
Commands::Mining(cmd) => commands::mining::handle(&client, cmd, output).await,
// Contract commands
Commands::Contract(cmd) => {
commands::contract::handle(&client, &config, cmd, output).await
}
Commands::Contract(cmd) => commands::contract::handle(&client, &config, cmd, output).await,
// Governance commands
Commands::Governance(cmd) => {
commands::governance::handle(&client, cmd, output).await
}
Commands::Governance(cmd) => commands::governance::handle(&client, cmd, output).await,
// Network commands
Commands::AddPeer { address } => {
commands::network::add_peer(&client, &address, output).await
}
Commands::BanPeer { peer } => commands::network::ban_peer(&client, &peer, output).await,
Commands::UnbanPeer { peer } => {
commands::network::unban_peer(&client, &peer, output).await
}
Commands::UnbanPeer { peer } => commands::network::unban_peer(&client, &peer, output).await,
};
if let Err(e) = result {

View file

@ -190,7 +190,11 @@ impl Wallet {
}
/// 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
.encrypted_seed
.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::time::Duration;
use axum::http::{HeaderValue, Method};
use axum::{
extract::{Path, Query, State},
http::StatusCode,
@ -21,10 +22,9 @@ use axum::{
};
use moka::future::Cache;
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::cors::{Any, CorsLayer};
use tower_http::trace::TraceLayer;
use tracing::{error, info};
// ==================== Configuration ====================
@ -297,8 +297,12 @@ pub struct PaginationParams {
pub limit: usize,
}
fn default_page() -> usize { 1 }
fn default_limit() -> usize { 25 }
fn default_page() -> usize {
1
}
fn default_limit() -> usize {
25
}
/// Paginated response.
#[derive(Clone, Debug, Serialize)]
@ -435,14 +439,19 @@ async fn health(State(state): State<Arc<ExplorerState>>) -> impl IntoResponse {
StatusCode::SERVICE_UNAVAILABLE
};
(status, Json(Health {
healthy: rpc_ok,
rpc_connected: rpc_ok,
}))
(
status,
Json(Health {
healthy: rpc_ok,
rpc_connected: rpc_ok,
}),
)
}
/// 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
if let Some(stats) = state.stats_cache.get("network_stats").await {
return Ok(Json(stats));
@ -532,7 +541,10 @@ async fn get_stats(State(state): State<Arc<ExplorerState>>) -> Result<Json<Netwo
};
// 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))
}
@ -559,7 +571,13 @@ async fn get_block(
}
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?;
let block = convert_rpc_block(rpc_block);
@ -634,7 +652,7 @@ async fn get_blocks(
daa_score: h.daa_score,
blue_score: h.blue_score,
blue_work: h.blue_work,
difficulty: 0.0, // Would need verbose data
difficulty: 0.0, // Would need verbose data
transaction_count: 0, // Unknown without fetching full block
is_chain_block: true, // Assume chain block for headers
transactions: None,
@ -710,9 +728,12 @@ async fn get_address(
}
let utxos: Vec<synor_rpc::RpcUtxo> = state
.rpc_call("synor_getUtxosByAddresses", GetUtxosParams {
addresses: vec![address.clone()],
})
.rpc_call(
"synor_getUtxosByAddresses",
GetUtxosParams {
addresses: vec![address.clone()],
},
)
.await?;
// Get balance
@ -727,9 +748,12 @@ async fn get_address(
}
let balance: BalanceResult = state
.rpc_call("synor_getBalanceByAddress", GetBalanceParams {
address: address.clone(),
})
.rpc_call(
"synor_getBalanceByAddress",
GetBalanceParams {
address: address.clone(),
},
)
.await?;
let info = AddressInfo {
@ -737,8 +761,8 @@ async fn get_address(
balance: balance.balance,
balance_human: format_synor(balance.balance),
utxo_count: utxos.len(),
total_received: 0, // Would need historical data
total_sent: 0, // Would need historical data
total_received: 0, // Would need historical data
total_sent: 0, // Would need historical data
transaction_count: 0, // Would need indexing
};
@ -756,9 +780,12 @@ async fn get_address_utxos(
}
let utxos: Vec<synor_rpc::RpcUtxo> = state
.rpc_call("synor_getUtxosByAddresses", GetUtxosParams {
addresses: vec![address],
})
.rpc_call(
"synor_getUtxosByAddresses",
GetUtxosParams {
addresses: vec![address],
},
)
.await?;
Ok(Json(utxos))
@ -820,7 +847,7 @@ async fn get_dag(
hash: header.hash.clone(),
short_hash: header.hash.chars().take(8).collect(),
blue_score: header.blue_score,
is_blue: true, // Would need verbose data
is_blue: true, // Would need verbose data
is_chain_block: true, // Would need verbose data
timestamp: header.timestamp,
tx_count: 0, // Unknown from header
@ -852,10 +879,13 @@ async fn get_mempool(
}
let entries: Vec<synor_rpc::RpcMempoolEntry> = state
.rpc_call("synor_getMempoolEntries", GetMempoolParams {
include_orphan_pool: false,
filter_tx_in_addresses: false,
})
.rpc_call(
"synor_getMempoolEntries",
GetMempoolParams {
include_orphan_pool: false,
filter_tx_in_addresses: false,
},
)
.await?;
let total = entries.len();
@ -912,10 +942,13 @@ async fn search(
}
let block_result: Result<synor_rpc::RpcBlock, _> = state
.rpc_call("synor_getBlock", GetBlockParams {
hash: query.to_string(),
include_txs: false,
})
.rpc_call(
"synor_getBlock",
GetBlockParams {
hash: query.to_string(),
include_txs: false,
},
)
.await;
if block_result.is_ok() {
@ -933,9 +966,12 @@ async fn search(
}
let tx_result: Result<synor_rpc::RpcTransaction, _> = state
.rpc_call("synor_getTransaction", GetTxParams {
tx_id: query.to_string(),
})
.rpc_call(
"synor_getTransaction",
GetTxParams {
tx_id: query.to_string(),
},
)
.await;
if tx_result.is_ok() {
@ -984,9 +1020,15 @@ fn convert_rpc_block(rpc: synor_rpc::RpcBlock) -> ExplorerBlock {
.map(convert_rpc_transaction)
.collect(),
),
children_hashes: verbose.map(|v| v.children_hashes.clone()).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(),
children_hashes: verbose
.map(|v| v.children_hashes.clone())
.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 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_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))
.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 {
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(),
version: rpc.version,
inputs: rpc

View file

@ -8,6 +8,7 @@ use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use axum::http::{HeaderValue, Method};
use axum::{
extract::{ConnectInfo, State},
http::StatusCode,
@ -15,11 +16,10 @@ use axum::{
routing::{get, post},
Json, Router,
};
use governor::{Quota, RateLimiter, state::keyed::DashMapStateStore};
use governor::{state::keyed::DashMapStateStore, Quota, RateLimiter};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use tower_http::cors::{Any, CorsLayer};
use axum::http::{HeaderValue, Method};
use tower_http::trace::TraceLayer;
use tracing::{info, warn};
@ -47,7 +47,7 @@ impl Default for FaucetConfig {
FaucetConfig {
rpc_url: "http://localhost:17110".to_string(),
dispense_amount: 10_00000000, // 10 SYNOR
cooldown_seconds: 3600, // 1 hour
cooldown_seconds: 3600, // 1 hour
rate_limit_per_minute: 10,
listen_addr: "0.0.0.0:8080".parse().unwrap(),
wallet_key: None,
@ -210,7 +210,8 @@ async fn main() -> anyhow::Result<()> {
// Create rate limiter (using NonZeroU32 for quota)
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);
@ -639,10 +640,7 @@ async fn send_tokens(state: &FaucetState, address: &str) -> anyhow::Result<Optio
if !response.status().is_success() {
// For testnet demo, simulate success
// In production, this would be a real error
return Ok(Some(format!(
"0x{}",
hex::encode(&rand_bytes())
)));
return Ok(Some(format!("0x{}", hex::encode(&rand_bytes()))));
}
let rpc_response: RpcResponse = response.json().await?;

View file

@ -35,7 +35,11 @@ pub fn confirm(prompt: &str) -> bool {
/// Formats a hash for display.
pub fn format_hash(hash: &[u8]) -> String {
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 {
hex::encode(hash)
}

View file

@ -115,12 +115,7 @@ impl NodeConfig {
}
/// Sets mining configuration.
pub fn with_mining(
mut self,
enabled: bool,
coinbase: Option<String>,
threads: usize,
) -> Self {
pub fn with_mining(mut self, enabled: bool, coinbase: Option<String>, threads: usize) -> Self {
if enabled {
self.mining.enabled = true;
}
@ -448,7 +443,7 @@ impl Default for ConsensusConfig {
fn default() -> Self {
ConsensusConfig {
ghostdag_k: 18,
merge_depth: 3600, // ~1 hour
merge_depth: 3600, // ~1 hour
finality_depth: 86400, // ~24 hours
target_time_ms: 1000,
difficulty_window: 2641,

View file

@ -210,19 +210,14 @@ async fn main() {
fn init_logging(level: &str, json: bool) {
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(level));
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level));
let subscriber = tracing_subscriber::registry().with(filter);
if json {
subscriber
.with(fmt::layer().json())
.init();
subscriber.with(fmt::layer().json()).init();
} else {
subscriber
.with(fmt::layer().with_target(true))
.init();
subscriber.with(fmt::layer().with_target(true)).init();
}
}
@ -277,11 +272,7 @@ async fn run_node(
}
/// Initialize a new node with genesis block.
async fn init_node(
data_dir: Option<PathBuf>,
network: String,
force: bool,
) -> anyhow::Result<()> {
async fn init_node(data_dir: Option<PathBuf>, network: String, force: bool) -> anyhow::Result<()> {
use synor_consensus::genesis::ChainConfig;
use synor_storage::{BlockBody, ChainState};
use synor_types::{BlockId, Network};
@ -302,7 +293,10 @@ async fn init_node(
"mainnet" => Network::Mainnet,
"testnet" => Network::Testnet,
"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...");
@ -323,8 +317,7 @@ async fn init_node(
std::fs::create_dir_all(data_dir.join("keys"))?;
// Create and save node config
let config = NodeConfig::for_network(&network)?
.with_data_dir(Some(data_dir.clone()));
let config = NodeConfig::for_network(&network)?.with_data_dir(Some(data_dir.clone()));
let config_path = data_dir.join("synord.toml");
config.save(&config_path)?;
@ -343,7 +336,10 @@ async fn init_node(
// Store genesis block body
let genesis_hash = chain_config.genesis_hash;
let body = BlockBody {
transaction_ids: chain_config.genesis.body.transactions
transaction_ids: chain_config
.genesis
.body
.transactions
.iter()
.map(|tx| tx.txid())
.collect(),
@ -401,10 +397,19 @@ async fn init_node(
println!(" Genesis: {}", hex::encode(genesis_hash.as_bytes()));
println!();
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!(" Initial reward: {} SYNOR", chain_config.initial_reward / 100_000_000);
println!(" Halving interval: {} blocks", chain_config.halving_interval);
println!(
" Initial reward: {} SYNOR",
chain_config.initial_reward / 100_000_000
);
println!(
" Halving interval: {} blocks",
chain_config.halving_interval
);
println!();
println!("To start the node:");
println!(" synord run --network {}", network);
@ -481,7 +486,10 @@ async fn import_blocks(
// Store the block
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;
} else {
imported += 1;
@ -656,7 +664,9 @@ async fn wait_for_shutdown() {
#[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");
}
}

View file

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

View file

@ -7,8 +7,8 @@ use tokio::sync::{broadcast, RwLock};
use tracing::{debug, info};
use synor_consensus::{
BlockValidator, DaaParams, DifficultyManager, RewardCalculator,
TransactionValidator, UtxoSet, ValidationError,
BlockValidator, DaaParams, DifficultyManager, RewardCalculator, TransactionValidator, UtxoSet,
ValidationError,
};
use synor_types::{
block::{Block, BlockHeader},
@ -288,10 +288,15 @@ impl ConsensusService {
}
// 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)
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 {
reason: format!("Invalid block: {}", e),
};
@ -442,7 +447,10 @@ impl ConsensusService {
// For non-coinbase transactions, validate against UTXO set
if !tx.is_coinbase() {
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
if matches!(e, ValidationError::UtxoNotFound(_)) {
return TxValidation::Conflict;

View file

@ -247,7 +247,8 @@ impl ContractService {
call_data.extend_from_slice(&args);
// 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(
synor_vm::context::BlockInfo {
@ -353,7 +354,10 @@ impl ContractService {
}
/// 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 = store
.as_ref()

View file

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

View file

@ -104,7 +104,8 @@ impl MempoolService {
let mut block_rx = self.consensus.subscribe_blocks();
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);
info!("Mempool cleanup task started");
@ -232,7 +233,10 @@ impl MempoolService {
*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);
Ok(())
@ -245,7 +249,10 @@ impl MempoolService {
let mut current_size = self.current_size.write().await;
*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);
Some(tx)
@ -285,7 +292,11 @@ impl MempoolService {
// Sort by fee rate descending
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
let mut selected = Vec::new();
@ -308,7 +319,11 @@ impl MempoolService {
// Sort by fee rate ascending (evict lowest first)
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 to_remove = Vec::new();

View file

@ -7,7 +7,9 @@ use tokio::sync::{broadcast, mpsc, RwLock};
use tracing::{debug, error, info, warn};
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};
@ -118,9 +120,11 @@ impl MinerService {
};
// Parse coinbase address if provided
let coinbase_address = config.mining.coinbase_address.as_ref().and_then(|addr_str| {
addr_str.parse::<Address>().ok()
});
let coinbase_address = config
.mining
.coinbase_address
.as_ref()
.and_then(|addr_str| addr_str.parse::<Address>().ok());
// Determine network from config
let network = match config.network.as_str() {
@ -261,7 +265,10 @@ impl MinerService {
/// Updates the mining template.
async fn update_template(&self) -> anyhow::Result<()> {
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(())
}
@ -301,12 +308,16 @@ impl MinerService {
/// Sets coinbase address.
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))?;
// Update miner config
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");
Ok(())
@ -314,7 +325,9 @@ impl MinerService {
/// Builds a block template for mining.
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"))?;
// Get transactions from mempool
@ -363,7 +376,8 @@ impl MinerService {
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))?;
debug!(
@ -416,7 +430,9 @@ impl MinerService {
);
// 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"))?;
// Build full block from template and mining result
@ -493,7 +509,9 @@ impl MinerService {
// Get hash from block header for notification
let hash = if block.len() >= 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
} else {
[0u8; 32]

View file

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

View file

@ -9,7 +9,7 @@ use tokio::sync::RwLock;
use tracing::{info, warn};
use synor_network::SyncState;
use synor_types::{BlockHeader, block::BlockBody};
use synor_types::{block::BlockBody, BlockHeader};
use crate::config::NodeConfig;
use crate::services::{
@ -109,7 +109,9 @@ impl RpcService {
// Start HTTP server
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))?;
info!(addr = %http_addr, "Starting HTTP RPC server");
@ -119,7 +121,8 @@ impl RpcService {
.await
.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))?;
info!(addr = %local_addr, "HTTP RPC server started");
@ -129,7 +132,9 @@ impl RpcService {
// Start WebSocket server
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))?;
info!(addr = %ws_addr, "Starting WebSocket RPC server");
@ -139,7 +144,8 @@ impl RpcService {
.await
.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))?;
info!(addr = %local_addr, "WebSocket RPC server started");
@ -319,7 +325,10 @@ impl RpcService {
let mempool_size = ctx.mempool.count().await;
// 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))
.unwrap_or(false);
@ -343,16 +352,19 @@ impl RpcService {
// synor_getPeerInfo
module.register_async_method("synor_getPeerInfo", |_, ctx| async move {
let peers = ctx.network.peers().await;
let peer_info: Vec<serde_json::Value> = peers.iter().map(|p| {
serde_json::json!({
"id": p.id,
"address": p.address.map(|a| a.to_string()).unwrap_or_default(),
"isInbound": p.inbound,
"version": p.version,
"userAgent": p.user_agent,
"latencyMs": p.latency_ms
let peer_info: Vec<serde_json::Value> = peers
.iter()
.map(|p| {
serde_json::json!({
"id": p.id,
"address": p.address.map(|a| a.to_string()).unwrap_or_default(),
"isInbound": p.inbound,
"version": p.version,
"userAgent": p.user_agent,
"latencyMs": p.latency_ms
})
})
}).collect();
.collect();
serde_json::json!({"peers": peer_info})
})?;
@ -418,7 +430,9 @@ impl RpcService {
let bytecode = match hex::decode(&params.bytecode) {
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() {
@ -426,21 +440,27 @@ impl RpcService {
} else {
match hex::decode(&params.init_args) {
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 timestamp = current_timestamp();
match ctx.contract.deploy(
bytecode,
init_args,
&params.deployer,
params.gas_limit,
block_height,
timestamp,
).await {
match ctx
.contract
.deploy(
bytecode,
init_args,
&params.deployer,
params.gas_limit,
block_height,
timestamp,
)
.await
{
Ok(result) => serde_json::json!({
"contractId": hex::encode(&result.contract_id),
"address": hex::encode(&result.address),
@ -448,7 +468,7 @@ impl RpcService {
}),
Err(e) => serde_json::json!({
"error": e.to_string()
})
}),
}
})?;
@ -474,7 +494,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_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() {
@ -482,23 +504,29 @@ impl RpcService {
} else {
match hex::decode(&params.args) {
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 timestamp = current_timestamp();
match ctx.contract.call(
&contract_id,
&params.method,
args,
&params.caller,
params.value,
params.gas_limit,
block_height,
timestamp,
).await {
match ctx
.contract
.call(
&contract_id,
&params.method,
args,
&params.caller,
params.value,
params.gas_limit,
block_height,
timestamp,
)
.await
{
Ok(result) => {
let logs: Vec<serde_json::Value> = result.logs.iter().map(|log| {
serde_json::json!({
@ -514,10 +542,10 @@ impl RpcService {
"gasUsed": result.gas_used,
"logs": logs
})
},
}
Err(e) => serde_json::json!({
"error": e.to_string()
})
}),
}
})?;
@ -541,7 +569,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_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() {
@ -549,28 +579,34 @@ impl RpcService {
} else {
match hex::decode(&params.args) {
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 timestamp = current_timestamp();
match ctx.contract.estimate_gas(
&contract_id,
&params.method,
args,
&params.caller,
params.value,
block_height,
timestamp,
).await {
match ctx
.contract
.estimate_gas(
&contract_id,
&params.method,
args,
&params.caller,
params.value,
block_height,
timestamp,
)
.await
{
Ok(gas) => serde_json::json!({
"estimatedGas": gas
}),
Err(e) => serde_json::json!({
"error": e.to_string()
})
}),
}
})?;
@ -588,7 +624,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_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 {
@ -600,7 +638,7 @@ impl RpcService {
}),
Err(e) => serde_json::json!({
"error": e.to_string()
})
}),
}
})?;
@ -619,7 +657,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_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) {
@ -636,7 +676,7 @@ impl RpcService {
}),
Err(e) => serde_json::json!({
"error": e.to_string()
})
}),
}
})?;
@ -654,7 +694,9 @@ impl RpcService {
let contract_id = match hex_to_hash(&params.contract_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 {
@ -669,7 +711,7 @@ impl RpcService {
}),
Err(e) => serde_json::json!({
"error": e.to_string()
})
}),
}
})?;
@ -705,11 +747,7 @@ impl RpcService {
blue_work: String::new(),
pruning_point: None,
},
transactions: if include_txs {
vec![]
} else {
vec![]
},
transactions: if include_txs { vec![] } else { vec![] },
verbose_data: None,
}))
} else {
@ -845,8 +883,7 @@ impl RpcService {
pruning_point: None,
},
transactions: vec![],
target: "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
.to_string(),
target: "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string(),
is_synced: true,
})
}

View file

@ -7,10 +7,9 @@ use tokio::sync::RwLock;
use tracing::{debug, info, warn};
use synor_storage::{
cf, Database, DatabaseConfig,
BlockBody, BlockStore, ChainState, GhostdagStore, HeaderStore,
MetadataStore, RelationsStore, StoredGhostdagData, StoredRelations,
StoredUtxo, TransactionStore, UtxoStore,
cf, BlockBody, BlockStore, ChainState, Database, DatabaseConfig, GhostdagStore, HeaderStore,
MetadataStore, RelationsStore, StoredGhostdagData, StoredRelations, StoredUtxo,
TransactionStore, UtxoStore,
};
use synor_types::{BlockHeader, BlockId, Hash256, Transaction, TransactionId};
@ -168,15 +167,23 @@ impl StorageService {
/// Stores a block header.
pub async fn put_header(&self, header: &BlockHeader) -> anyhow::Result<()> {
let store = self.header_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.put(header).map_err(|e| anyhow::anyhow!("Failed to store header: {}", e))
let store = store
.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.
pub async fn get_header(&self, hash: &Hash256) -> anyhow::Result<Option<BlockHeader>> {
let store = self.header_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.get(hash).map_err(|e| anyhow::anyhow!("Failed to get header: {}", e))
let store = store
.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.
@ -192,15 +199,23 @@ impl StorageService {
/// Gets header by height.
pub async fn get_header_by_height(&self, height: u64) -> anyhow::Result<Option<BlockHeader>> {
let store = self.header_store.read().await;
let store = store.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))
let store = store
.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.
pub async fn index_header_by_height(&self, height: u64, hash: &Hash256) -> anyhow::Result<()> {
let store = self.header_store.read().await;
let store = store.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))
let store = store
.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 ====================
@ -208,15 +223,23 @@ impl StorageService {
/// Stores a block body.
pub async fn put_block_body(&self, hash: &Hash256, body: &BlockBody) -> anyhow::Result<()> {
let store = self.block_store.read().await;
let store = store.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))
let store = store
.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.
pub async fn get_block_body(&self, hash: &Hash256) -> anyhow::Result<Option<BlockBody>> {
let store = self.block_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.get(hash).map_err(|e| anyhow::anyhow!("Failed to get block body: {}", e))
let store = store
.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.
@ -233,7 +256,9 @@ impl StorageService {
pub async fn put_block(&self, block: &BlockData) -> anyhow::Result<()> {
debug!(hash = hex::encode(&block.hash[..8]), "Storing block");
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
db.put(cf::HEADERS, &block.hash, &block.header)
@ -249,11 +274,15 @@ impl StorageService {
/// Legacy method: Gets a block by hash (raw bytes).
pub async fn get_block(&self, hash: &[u8; 32]) -> anyhow::Result<Option<BlockData>> {
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))?;
let body = db.get(cf::BLOCKS, hash)
let body = db
.get(cf::BLOCKS, hash)
.map_err(|e| anyhow::anyhow!("Failed to get body: {}", e))?;
match (header, body) {
@ -271,15 +300,26 @@ impl StorageService {
/// Stores a transaction.
pub async fn put_transaction(&self, tx: &Transaction) -> anyhow::Result<()> {
let store = self.tx_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.put(tx).map_err(|e| anyhow::anyhow!("Failed to store transaction: {}", e))
let store = store
.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.
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 = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.get(txid).map_err(|e| anyhow::anyhow!("Failed to get transaction: {}", e))
let store = store
.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.
@ -295,24 +335,45 @@ impl StorageService {
// ==================== UTXO Operations ====================
/// 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 = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.get(txid, index).map_err(|e| anyhow::anyhow!("Failed to get UTXO: {}", e))
let store = store
.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.
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 = store.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))
let store = store
.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).
pub async fn delete_utxo(&self, txid: &TransactionId, index: u32) -> anyhow::Result<()> {
let store = self.utxo_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.delete(txid, index).map_err(|e| anyhow::anyhow!("Failed to delete UTXO: {}", e))
let store = store
.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).
@ -326,77 +387,134 @@ impl StorageService {
}
/// 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 = store.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))
let store = store
.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 ====================
/// 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 = store.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))
let store = store
.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.
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 = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.get(block_id).map_err(|e| anyhow::anyhow!("Failed to get relations: {}", e))
let store = store
.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.
pub async fn get_parents(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> {
let store = self.relations_store.read().await;
let store = store.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))
let store = store
.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.
pub async fn get_children(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> {
let store = self.relations_store.read().await;
let store = store.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))
let store = store
.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.
pub async fn add_child(&self, parent_id: &BlockId, child_id: BlockId) -> anyhow::Result<()> {
let store = self.relations_store.read().await;
let store = store.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))
let store = store
.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 ====================
/// 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 = store.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))
let store = store
.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.
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 = store.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))
let store = store
.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.
pub async fn get_blue_score(&self, block_id: &BlockId) -> anyhow::Result<Option<u64>> {
let store = self.ghostdag_store.read().await;
let store = store.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))
let store = store
.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.
pub async fn get_selected_parent(&self, block_id: &BlockId) -> anyhow::Result<Option<BlockId>> {
let store = self.ghostdag_store.read().await;
let store = store.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))
let store = store
.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 ====================
@ -404,15 +522,23 @@ impl StorageService {
/// Gets current DAG tips.
pub async fn get_tips(&self) -> anyhow::Result<Vec<BlockId>> {
let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.get_tips().map_err(|e| anyhow::anyhow!("Failed to get tips: {}", e))
let store = store
.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.
pub async fn set_tips(&self, tips: &[BlockId]) -> anyhow::Result<()> {
let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.set_tips(tips).map_err(|e| anyhow::anyhow!("Failed to set tips: {}", e))
let store = store
.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).
@ -433,29 +559,45 @@ impl StorageService {
/// Gets the genesis block ID.
pub async fn get_genesis(&self) -> anyhow::Result<Option<BlockId>> {
let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.get_genesis().map_err(|e| anyhow::anyhow!("Failed to get genesis: {}", e))
let store = store
.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.
pub async fn set_genesis(&self, genesis: &BlockId) -> anyhow::Result<()> {
let store = self.metadata_store.read().await;
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
store.set_genesis(genesis).map_err(|e| anyhow::anyhow!("Failed to set genesis: {}", e))
let store = store
.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.
pub async fn get_chain_state(&self) -> anyhow::Result<Option<ChainState>> {
let store = self.metadata_store.read().await;
let store = store.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))
let store = store
.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.
pub async fn set_chain_state(&self, state: &ChainState) -> anyhow::Result<()> {
let store = self.metadata_store.read().await;
let store = store.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))
let store = store
.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.
@ -470,15 +612,23 @@ impl StorageService {
/// Gets the pruning point.
pub async fn get_pruning_point(&self) -> anyhow::Result<Option<BlockId>> {
let store = self.metadata_store.read().await;
let store = store.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))
let store = store
.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.
pub async fn set_pruning_point(&self, point: &BlockId) -> anyhow::Result<()> {
let store = self.metadata_store.read().await;
let store = store.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))
let store = store
.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 ====================
@ -490,7 +640,9 @@ impl StorageService {
key: &[u8; 32],
) -> anyhow::Result<Option<Vec<u8>>> {
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
let mut composite_key = Vec::with_capacity(64);
@ -510,7 +662,9 @@ impl StorageService {
value: &[u8],
) -> anyhow::Result<()> {
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);
composite_key.extend_from_slice(contract);
@ -526,15 +680,21 @@ impl StorageService {
pub async fn compact(&self) -> anyhow::Result<()> {
info!("Compacting database");
let db = self.database.read().await;
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
db.compact().map_err(|e| anyhow::anyhow!("Failed to compact database: {}", e))
let db = db
.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.
pub async fn flush(&self) -> anyhow::Result<()> {
let db = self.database.read().await;
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
db.flush().map_err(|e| anyhow::anyhow!("Failed to flush database: {}", e))
let db = db
.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.
@ -545,7 +705,9 @@ impl StorageService {
let headers_size = db.cf_size(cf::HEADERS).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 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::RELATIONS).unwrap_or(0)
+ db.cf_size(cf::GHOSTDAG).unwrap_or(0)
@ -559,7 +721,7 @@ impl StorageService {
blocks_count,
utxo_count,
disk_usage_bytes: total_size,
cache_hits: 0, // Would need cache instrumentation
cache_hits: 0, // Would need cache instrumentation
cache_misses: 0,
}
} else {

View file

@ -169,7 +169,11 @@ async fn test_selected_parent_chain() {
for (i, node) in network.nodes.iter().enumerate() {
let consensus = node.consensus();
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
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
// For now, just verify nodes didn't crash
assert_eq!(node1.state().await, NodeState::Running, "Node 1 should survive partition");
assert_eq!(node2.state().await, NodeState::Running, "Node 2 should survive partition");
assert_eq!(
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();
node1.stop().await.unwrap();
@ -594,11 +606,7 @@ async fn test_difficulty_adjustment() {
let difficulty = consensus.current_difficulty().await;
let _target = consensus.get_current_target().await;
info!(
node = i,
difficulty_bits = difficulty,
"Difficulty info"
);
info!(node = i, difficulty_bits = difficulty, "Difficulty info");
// Difficulty should be set
// 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)
match tokio::time::timeout(Duration::from_millis(500), rx.recv()).await {
Ok(Ok(hash)) => {
info!(block = hex::encode(&hash[..8]), "Received block notification");
info!(
block = hex::encode(&hash[..8]),
"Received block notification"
);
}
Ok(Err(_)) => {
info!("Block channel closed");

View file

@ -188,7 +188,11 @@ async fn test_three_node_mesh() {
info!(total_connections = total, "Network mesh formed");
// 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();
}
@ -532,7 +536,11 @@ async fn test_simultaneous_node_start() {
let net = node.network();
let count = net.peer_count().await;
total_connections += count;
info!(node = i, peers = count, "Peer count after simultaneous start");
info!(
node = i,
peers = count,
"Peer count after simultaneous start"
);
}
info!(

View file

@ -119,7 +119,11 @@ async fn test_node_creation() {
assert!(result.is_ok(), "Node creation timed out");
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]
@ -185,7 +189,7 @@ async fn test_node_services_accessible() {
// Services should be accessible even before start
// These return &Arc<T> directly, not Option
let _ = node.storage(); // Storage is always created
let _ = node.storage(); // Storage is always created
let _ = node.network();
let _ = node.consensus();
let _ = node.mempool();

View file

@ -250,11 +250,19 @@ async fn test_selected_chain_update() {
// Log the chain blocks
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) {
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

View file

@ -274,7 +274,9 @@ async fn test_block_request_response() {
if !peers.is_empty() {
// Attempt to request blocks (would need actual block hashes)
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");
// Error expected for non-existent hash, but API should work
}

View file

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

View file

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

View file

@ -316,7 +316,8 @@ impl Validator {
}
Payload::ImportSection(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 {
wasmparser::TypeRef::Func(type_idx) => ImportKind::Function(type_idx),
@ -339,7 +340,8 @@ impl Validator {
}
Payload::MemorySection(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 {
min_pages: memory.initial,
max_pages: memory.maximum,
@ -350,7 +352,8 @@ impl Validator {
}
Payload::GlobalSection(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 {
has_mutable_globals = true;
}
@ -358,7 +361,8 @@ impl Validator {
}
Payload::ExportSection(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 {
wasmparser::ExternalKind::Func => ExportKind::Function,
@ -404,10 +408,14 @@ impl Validator {
// Validate entry points (warn if missing, don't error)
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") {
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
@ -427,12 +435,19 @@ impl Validator {
debug!("Validating {} memories", memories.len());
if memories.is_empty() {
// 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 {
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 {
errors.push(ValidationError::MemoryError("Multiple memories not supported".into()));
errors.push(ValidationError::MemoryError(
"Multiple memories not supported".into(),
));
} else {
let mem = &memories[0];
@ -445,9 +460,9 @@ impl Validator {
)));
}
} else {
result.warnings.push(
"Memory has no maximum limit - recommended to set max_pages".into(),
);
result
.warnings
.push("Memory has no maximum limit - recommended to set max_pages".into());
}
// Check for shared memory (threads)

View file

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

View file

@ -6,8 +6,8 @@
use synor_types::{
block::BlockBody,
transaction::{Outpoint, ScriptPubKey, ScriptType, SubnetworkId},
Amount, Block, BlockHeader, BlueScore, Hash256, Network, Timestamp,
Transaction, TxInput, TxOutput,
Amount, Block, BlockHeader, BlueScore, Hash256, Network, Timestamp, Transaction, TxInput,
TxOutput,
};
/// Chain configuration parameters.
@ -59,23 +59,23 @@ impl ChainConfig {
network: Network::Mainnet,
genesis,
genesis_hash,
target_block_time_ms: 100, // 10 bps
target_block_time_ms: 100, // 10 bps
ghostdag_k: 18,
max_block_parents: 64,
max_block_mass: 500_000,
coinbase_maturity: 100,
finality_depth: 86_400, // ~24 hours
pruning_depth: 288_000, // ~8 hours of data
initial_difficulty: 0x1d00ffff, // Bitcoin-style initial difficulty
halving_interval: 315_360_000, // ~1 year at 10 bps
initial_reward: 500 * 100_000_000, // 500 SYNOR
finality_depth: 86_400, // ~24 hours
pruning_depth: 288_000, // ~8 hours of data
initial_difficulty: 0x1d00ffff, // Bitcoin-style initial difficulty
halving_interval: 315_360_000, // ~1 year at 10 bps
initial_reward: 500 * 100_000_000, // 500 SYNOR
dns_seeds: vec![
"seed1.synor.cc".to_string(),
"seed2.synor.cc".to_string(),
"seed3.synor.cc".to_string(),
],
bootstrap_nodes: vec![],
bip32_coin_type: 0x5359, // "SY" in hex
bip32_coin_type: 0x5359, // "SY" in hex
address_prefix: "synor".to_string(),
}
}
@ -93,11 +93,11 @@ impl ChainConfig {
ghostdag_k: 18,
max_block_parents: 64,
max_block_mass: 500_000,
coinbase_maturity: 10, // Lower for testing
finality_depth: 1000, // Lower for testing
coinbase_maturity: 10, // Lower for testing
finality_depth: 1000, // Lower for testing
pruning_depth: 5000,
initial_difficulty: 0x1f00ffff, // Lower difficulty
halving_interval: 10_000, // Quick halvings for testing
initial_difficulty: 0x1f00ffff, // Lower difficulty
halving_interval: 10_000, // Quick halvings for testing
initial_reward: 500 * 100_000_000,
dns_seeds: vec![
"testnet-seed1.synor.cc".to_string(),
@ -118,20 +118,18 @@ impl ChainConfig {
network: Network::Devnet,
genesis,
genesis_hash,
target_block_time_ms: 1000, // 1 second blocks for dev
ghostdag_k: 3, // Smaller K for dev
target_block_time_ms: 1000, // 1 second blocks for dev
ghostdag_k: 3, // Smaller K for dev
max_block_parents: 10,
max_block_mass: 100_000,
coinbase_maturity: 1,
finality_depth: 10,
pruning_depth: 100,
initial_difficulty: 0x207fffff, // Minimum difficulty
initial_difficulty: 0x207fffff, // Minimum difficulty
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![],
bootstrap_nodes: vec![
"/ip4/127.0.0.1/tcp/16511".to_string(),
],
bootstrap_nodes: vec!["/ip4/127.0.0.1/tcp/16511".to_string()],
bip32_coin_type: 0x5359,
address_prefix: "dsynor".to_string(),
}
@ -147,7 +145,11 @@ impl ChainConfig {
}
/// 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
let now_ms = Timestamp::now().as_millis();
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);
// 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
let coinbase_tx = create_coinbase_transaction(
@ -209,7 +212,7 @@ fn create_mainnet_genesis() -> Block {
// Create genesis header
let header = BlockHeader {
version: 1,
parents: vec![], // Genesis has no parents
parents: vec![], // Genesis has no parents
merkle_root: txid,
accepted_id_merkle_root: Hash256::ZERO,
utxo_commitment: Hash256::ZERO,
@ -342,24 +345,20 @@ fn genesis_allocation_mainnet() -> Vec<GenesisAllocation> {
/// Returns testnet genesis allocations.
fn genesis_allocation_testnet() -> Vec<GenesisAllocation> {
vec![
GenesisAllocation {
address_hash: Hash256::from([0x10; 32]),
amount: Amount::from_synor(1_000_000),
description: "Testnet Faucet".to_string(),
},
]
vec![GenesisAllocation {
address_hash: Hash256::from([0x10; 32]),
amount: Amount::from_synor(1_000_000),
description: "Testnet Faucet".to_string(),
}]
}
/// Returns devnet genesis allocations.
fn genesis_allocation_devnet() -> Vec<GenesisAllocation> {
vec![
GenesisAllocation {
address_hash: Hash256::from([0x20; 32]),
amount: Amount::from_synor(100_000_000),
description: "Dev Account".to_string(),
},
]
vec![GenesisAllocation {
address_hash: Hash256::from([0x20; 32]),
amount: Amount::from_synor(100_000_000),
description: "Dev Account".to_string(),
}]
}
/// Creates a coinbase transaction for genesis.
@ -430,13 +429,11 @@ pub fn mainnet_checkpoints() -> Vec<Checkpoint> {
/// Returns hardcoded checkpoints for testnet.
pub fn testnet_checkpoints() -> Vec<Checkpoint> {
vec![
Checkpoint {
blue_score: 0,
hash: ChainConfig::testnet().genesis_hash,
timestamp: 1735689600000,
},
]
vec![Checkpoint {
blue_score: 0,
hash: ChainConfig::testnet().genesis_hash,
timestamp: 1735689600000,
}]
}
/// Network magic bytes for message framing.

View file

@ -220,7 +220,10 @@ impl FeeDistribution {
let burn = Amount::from_sompi(fee_sompi * 10 / 100);
let stakers = Amount::from_sompi(fee_sompi * 60 / 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 {
burn,

View file

@ -144,7 +144,10 @@ impl UtxoSet {
/// Creates a UTXO set from existing entries.
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();
UtxoSet {
utxos: RwLock::new(entries),
@ -278,7 +281,11 @@ impl UtxoSet {
}
/// 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 mut result = Vec::new();
@ -410,7 +417,11 @@ impl VirtualUtxoState {
}
/// 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
for diff in chain_diffs {
self.base.apply_diff(&diff)?;
@ -470,10 +481,7 @@ mod tests {
fn make_entry(amount: u64) -> UtxoEntry {
UtxoEntry::new(
TxOutput::new(
Amount::from_sompi(amount),
ScriptPubKey::p2pkh(&[0u8; 32]),
),
TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32])),
100,
false,
)
@ -481,10 +489,7 @@ mod tests {
fn make_coinbase_entry(amount: u64, daa_score: u64) -> UtxoEntry {
UtxoEntry::new(
TxOutput::new(
Amount::from_sompi(amount),
ScriptPubKey::p2pkh(&[0u8; 32]),
),
TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32])),
daa_score,
true,
)

View file

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

View file

@ -163,7 +163,12 @@ impl TestEnvironment {
}
// 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();
// 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.
pub fn len(&self, contract: &ContractId) -> usize {
self.data
.read()
.get(contract)
.map(|m| m.len())
.unwrap_or(0)
self.data.read().get(contract).map(|m| m.len()).unwrap_or(0)
}
/// Checks if a contract has any storage.
@ -362,7 +358,10 @@ impl StorageSnapshot {
}
/// 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)
}
}

View file

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

View file

@ -238,11 +238,7 @@ criterion_group!(
criterion_group!(hash_benches, blake3_hash_benchmark, blake3_hash_large,);
criterion_group!(
misc_benches,
address_derivation_benchmark,
signature_sizes,
);
criterion_group!(misc_benches, address_derivation_benchmark, signature_sizes,);
criterion_main!(
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.
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();
derive_key(master_seed, b"", &info, 32)
}
/// 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();
derive_key(master_seed, b"", &info, 32)
}
@ -81,8 +87,8 @@ pub fn derive_child_key(
return Err(DeriveKeyError::InvalidKeyLength);
}
let mut hmac = Hmac::<Sha3_256>::new_from_slice(chain_code)
.map_err(|_| DeriveKeyError::HmacError)?;
let mut hmac =
Hmac::<Sha3_256>::new_from_slice(chain_code).map_err(|_| DeriveKeyError::HmacError)?;
// For hardened keys (index >= 0x80000000), use parent key
// 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.
use crate::{mnemonic::Mnemonic, signature::HybridSignature, Address, Hash256, Network};
use ed25519_dalek::{
Signer, SigningKey as Ed25519SigningKey, VerifyingKey as Ed25519VerifyingKey,
};
use ed25519_dalek::{Signer, SigningKey as Ed25519SigningKey, VerifyingKey as Ed25519VerifyingKey};
use pqcrypto_dilithium::dilithium3;
use pqcrypto_traits::sign::{PublicKey as PqPublicKey, SignedMessage as PqSignedMessage};
use rand::rngs::OsRng;
@ -80,7 +78,7 @@ impl Dilithium3Keypair {
/// Creates a keypair from seed bytes using deterministic key generation.
pub fn from_seed(seed: &[u8; 32]) -> Self {
// Use SHAKE256 to expand seed to required size
use sha3::{Shake256, digest::Update};
use sha3::{digest::Update, Shake256};
let mut hasher = Shake256::default();
hasher.update(b"synor-dilithium3-keygen");
@ -170,7 +168,8 @@ impl PublicKey {
let ed_verifying_key = Ed25519VerifyingKey::from_bytes(&self.ed25519)
.map_err(|_| KeypairError::InvalidPublicKey)?;
let ed_sig_array = signature.ed25519_array()
let ed_sig_array = signature
.ed25519_array()
.ok_or(KeypairError::InvalidSignature)?;
let ed_sig = ed25519_dalek::Signature::from_bytes(&ed_sig_array);
ed_verifying_key

View file

@ -87,15 +87,15 @@
#![allow(dead_code)]
pub mod keypair;
pub mod signature;
pub mod mnemonic;
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 keypair::{Ed25519Keypair, HybridKeypair, PublicKey, SecretKey};
pub use mnemonic::{Mnemonic, MnemonicError};
pub use signature::{HybridSignature, Signature, SignatureError};
/// Re-export common types
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
let words: Vec<&str> = self.words();
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 {
write!(f, "[invalid mnemonic]")
}

View file

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

View file

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

View file

@ -264,8 +264,9 @@ impl GhostdagManager {
) -> Result<(Vec<BlockId>, Vec<BlockId>, HashMap<BlockId, usize>), GhostdagError> {
let mut blues: Vec<BlockId> = Vec::with_capacity(merge_set.len());
let mut reds: Vec<BlockId> = Vec::new();
let mut blues_anticone_sizes: HashMap<BlockId, usize> =
HashMap::with_capacity(merge_set.len() + selected_parent_data.blues_anticone_sizes.len());
let mut blues_anticone_sizes: HashMap<BlockId, usize> = HashMap::with_capacity(
merge_set.len() + selected_parent_data.blues_anticone_sizes.len(),
);
// Initialize with selected parent's blues anticone sizes
blues_anticone_sizes.extend(selected_parent_data.blues_anticone_sizes.iter());
@ -378,7 +379,8 @@ impl GhostdagManager {
new_blue: &BlockId,
current_blues: &[BlockId],
) -> 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
let mut temp_sizes = HashMap::new();
for blue in current_blues {
@ -483,7 +485,11 @@ mod tests {
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 dag = std::sync::Arc::new(BlockDag::new(genesis, 0));
let reachability = std::sync::Arc::new(ReachabilityStore::new(genesis));
@ -550,7 +556,9 @@ mod tests {
// Now create a block with both branches as parents
let block4 = make_block_id(4);
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();
// Block3 should be selected parent (higher chain)
@ -565,7 +573,8 @@ mod tests {
let mut current = genesis;
for i in 1..=5 {
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();
let data = ghostdag.add_block(block, &[current]).unwrap();

View file

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

View file

@ -8,10 +8,7 @@
//! 3. Within the merge set, blues come before reds
//! 4. Within blues/reds, ordering is by blue score then hash
use crate::{
ghostdag::GhostdagManager,
BlockId, BlueScore,
};
use crate::{ghostdag::GhostdagManager, BlockId, BlueScore};
use std::cmp::Ordering;
use thiserror::Error;
@ -122,11 +119,7 @@ impl BlockOrdering {
// Add sorted merge blocks
for (block_id, blue_score, is_blue) in merge_blocks {
ordering.push(OrderedBlock::new(
block_id,
position,
blue_score,
is_blue,
true, // Is a merge block
block_id, position, blue_score, is_blue, true, // Is a merge block
));
position += 1;
}
@ -138,7 +131,11 @@ impl BlockOrdering {
/// 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.
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 to_ordering = self.get_ordering(to)?;
@ -164,14 +161,17 @@ impl BlockOrdering {
match (pos_a, pos_b) {
(Some(pa), Some(pb)) => Ok(pa < pb),
(Some(_), None) => Ok(true), // A is in past of B
(Some(_), None) => Ok(true), // A is in past of B
(None, Some(_)) => Ok(false), // A is not in past of B
(None, None) => Err(OrderingError::BlocksNotRelated(*a, *b)),
}
}
/// 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 mut merge_blocks = Vec::new();
let mut position = 0u64;
@ -191,15 +191,13 @@ impl BlockOrdering {
}
// Sort by blue score and hash
merge_blocks.sort_by(|a, b| {
match (a.is_blue, b.is_blue) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => match b.blue_score.cmp(&a.blue_score) {
Ordering::Equal => a.id.cmp(&b.id),
other => other,
},
}
merge_blocks.sort_by(|a, b| match (a.is_blue, b.is_blue) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => match b.blue_score.cmp(&a.blue_score) {
Ordering::Equal => a.id.cmp(&b.id),
other => other,
},
});
// Update positions after sorting
@ -333,10 +331,8 @@ mod tests {
let block1 = make_block_id(1);
let block2 = make_block_id(2);
dag.insert_block(block1, vec![genesis], 100)
.unwrap();
dag.insert_block(block2, vec![block1], 200)
.unwrap();
dag.insert_block(block1, vec![genesis], 100).unwrap();
dag.insert_block(block2, vec![block1], 200).unwrap();
reachability.add_block(block1, genesis, &[genesis]).unwrap();
reachability.add_block(block2, block1, &[block1]).unwrap();
@ -361,10 +357,8 @@ mod tests {
let block1 = make_block_id(1);
let block2 = make_block_id(2);
dag.insert_block(block1, vec![genesis], 100)
.unwrap();
dag.insert_block(block2, vec![block1], 200)
.unwrap();
dag.insert_block(block1, vec![genesis], 100).unwrap();
dag.insert_block(block2, vec![block1], 200).unwrap();
reachability.add_block(block1, genesis, &[genesis]).unwrap();
reachability.add_block(block2, block1, &[block1]).unwrap();
@ -383,8 +377,7 @@ mod tests {
let (dag, reachability, ghostdag, ordering) = setup_test();
let block1 = make_block_id(1);
dag.insert_block(block1, vec![genesis], 100)
.unwrap();
dag.insert_block(block1, vec![genesis], 100).unwrap();
reachability.add_block(block1, genesis, &[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`.
/// 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 {
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).
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 {
return Ok(false);
}
@ -171,11 +179,8 @@ impl ReachabilityStore {
let (new_interval, parent_needs_realloc) = self.allocate_interval(&parent_data, &data)?;
// Create new block's data
let new_data = ReachabilityData::new(
new_interval,
Some(selected_parent),
parent_data.height + 1,
);
let new_data =
ReachabilityData::new(new_interval, Some(selected_parent), parent_data.height + 1);
// Update parent's tree children
if let Some(parent) = data.get_mut(&selected_parent) {

View file

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

View file

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

View file

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

View file

@ -7,8 +7,8 @@
//! - Automated fund streams
use borsh::{BorshDeserialize, BorshSerialize};
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use synor_types::{Address, Hash256};
use crate::multisig::{MultisigTxId, MultisigWallet};
@ -16,7 +16,16 @@ use crate::ProposalId;
/// Unique identifier for a treasury pool.
#[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]);
@ -156,9 +165,9 @@ impl SpendingLimit {
/// High security: 1% per tx, 5% per week.
pub fn high_security(total_balance: u64) -> Self {
SpendingLimit::new(
total_balance / 100, // 1% per tx
total_balance / 20, // 5% per week
7 * 24 * 60 * 60, // 1 week
total_balance / 100, // 1% per tx
total_balance / 20, // 5% per week
7 * 24 * 60 * 60, // 1 week
)
.with_cooling_period(24 * 60 * 60) // 1 day cooling
}
@ -166,9 +175,9 @@ impl SpendingLimit {
/// Medium security: 5% per tx, 20% per month.
pub fn medium_security(total_balance: u64) -> Self {
SpendingLimit::new(
total_balance / 20, // 5% per tx
total_balance / 5, // 20% per month
30 * 24 * 60 * 60, // 1 month
total_balance / 20, // 5% per tx
total_balance / 5, // 20% per month
30 * 24 * 60 * 60, // 1 month
)
.with_cooling_period(6 * 60 * 60) // 6 hour cooling
}
@ -176,9 +185,9 @@ impl SpendingLimit {
/// Low security: 10% per tx, 50% per month.
pub fn low_security(total_balance: u64) -> Self {
SpendingLimit::new(
total_balance / 10, // 10% per tx
total_balance / 2, // 50% per month
30 * 24 * 60 * 60, // 1 month
total_balance / 10, // 10% per tx
total_balance / 2, // 50% per month
30 * 24 * 60 * 60, // 1 month
)
}
}
@ -336,8 +345,8 @@ impl TreasuryConfig {
approvals: ApprovalRequirements {
multisig_required: true,
dao_threshold: Some(50_000 * 100_000_000), // 50k SYNOR needs DAO
council_required: true, // Council must approve team allocations
min_delay: 7 * 24 * 60 * 60, // 7 day delay
council_required: true, // Council must approve team allocations
min_delay: 7 * 24 * 60 * 60, // 7 day delay
},
allow_deposits: false, // No external deposits
frozen: false,
@ -497,21 +506,29 @@ impl TreasuryPool {
}
// Check spending limits
self.config.spending_limit.can_spend(amount, timestamp)
self.config
.spending_limit
.can_spend(amount, timestamp)
.map_err(TreasuryError::SpendingLimitExceeded)?;
// Determine initial state based on requirements
let initial_state = if self.config.approvals.multisig_required && self.multisig_wallet.is_some() {
SpendingRequestState::PendingMultisig
} else if self.config.approvals.dao_threshold.map_or(false, |t| amount >= t) {
SpendingRequestState::PendingDao
} else if self.config.approvals.council_required {
SpendingRequestState::PendingCouncil
} else {
SpendingRequestState::Timelocked {
execute_after: timestamp + self.config.approvals.min_delay,
}
};
let initial_state =
if self.config.approvals.multisig_required && self.multisig_wallet.is_some() {
SpendingRequestState::PendingMultisig
} else if self
.config
.approvals
.dao_threshold
.map_or(false, |t| amount >= t)
{
SpendingRequestState::PendingDao
} else if self.config.approvals.council_required {
SpendingRequestState::PendingCouncil
} else {
SpendingRequestState::Timelocked {
execute_after: timestamp + self.config.approvals.min_delay,
}
};
let request = SpendingRequest {
id,
@ -538,7 +555,9 @@ impl TreasuryPool {
multisig_tx: MultisigTxId,
timestamp: u64,
) -> Result<(), TreasuryError> {
let request = self.pending_requests.get_mut(request_id)
let request = self
.pending_requests
.get_mut(request_id)
.ok_or(TreasuryError::RequestNotFound)?;
if request.state != SpendingRequestState::PendingMultisig {
@ -548,7 +567,10 @@ impl TreasuryPool {
request.multisig_tx = Some(multisig_tx);
// 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);
if needs_dao {
@ -571,7 +593,9 @@ impl TreasuryPool {
proposal_id: ProposalId,
timestamp: u64,
) -> Result<(), TreasuryError> {
let request = self.pending_requests.get_mut(request_id)
let request = self
.pending_requests
.get_mut(request_id)
.ok_or(TreasuryError::RequestNotFound)?;
if request.state != SpendingRequestState::PendingDao {
@ -599,7 +623,9 @@ impl TreasuryPool {
required_approvals: usize,
timestamp: u64,
) -> Result<(), TreasuryError> {
let request = self.pending_requests.get_mut(request_id)
let request = self
.pending_requests
.get_mut(request_id)
.ok_or(TreasuryError::RequestNotFound)?;
if request.state != SpendingRequestState::PendingCouncil {
@ -625,7 +651,9 @@ impl TreasuryPool {
request_id: &Hash256,
timestamp: u64,
) -> Result<SpendingRequest, TreasuryError> {
let request = self.pending_requests.get(request_id)
let request = self
.pending_requests
.get(request_id)
.ok_or(TreasuryError::RequestNotFound)?;
// Check if ready
@ -654,7 +682,9 @@ impl TreasuryPool {
// Execute
self.balance -= 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
let mut executed_request = self.pending_requests.remove(request_id).unwrap();
@ -669,7 +699,9 @@ impl TreasuryPool {
request_id: &Hash256,
canceller: &Address,
) -> Result<(), TreasuryError> {
let request = self.pending_requests.get_mut(request_id)
let request = self
.pending_requests
.get_mut(request_id)
.ok_or(TreasuryError::RequestNotFound)?;
// Only proposer can cancel
@ -710,14 +742,7 @@ impl TreasuryPool {
// Reserve the amount
self.balance -= amount;
let stream = FundingStream::new(
id,
recipient,
amount,
start_time,
duration,
description,
);
let stream = FundingStream::new(id, recipient, amount, start_time, duration, description);
self.streams.insert(id, stream);
Ok(self.streams.get(&id).unwrap())
@ -730,7 +755,9 @@ impl TreasuryPool {
claimer: &Address,
timestamp: u64,
) -> Result<u64, TreasuryError> {
let stream = self.streams.get_mut(stream_id)
let stream = self
.streams
.get_mut(stream_id)
.ok_or(TreasuryError::StreamNotFound)?;
if &stream.recipient != claimer {
@ -745,7 +772,9 @@ impl TreasuryPool {
/// Cancels a funding stream (returns unvested to pool).
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)?;
let remaining = stream.total_amount - stream.claimed_amount;
@ -842,7 +871,9 @@ impl Treasury {
) -> Result<(), TreasuryError> {
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)?;
pool.set_multisig(wallet_id);
@ -985,8 +1016,15 @@ impl std::fmt::Display for SpendingError {
Self::CoolingPeriod { remaining } => {
write!(f, "Cooling period: {} seconds remaining", remaining)
}
Self::ExceedsPeriodLimit { requested, remaining } => {
write!(f, "Requested {} exceeds remaining period allowance of {}", requested, remaining)
Self::ExceedsPeriodLimit {
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::PoolFrozen => write!(f, "Treasury pool is frozen"),
Self::DepositsNotAllowed => write!(f, "Deposits not allowed for this pool"),
Self::InsufficientBalance { requested, available } => {
write!(f, "Insufficient balance: {} requested, {} available", requested, available)
Self::InsufficientBalance {
requested,
available,
} => {
write!(
f,
"Insufficient balance: {} requested, {} available",
requested, available
)
}
Self::SpendingLimitExceeded(e) => write!(f, "Spending limit exceeded: {}", e),
Self::RequestNotFound => write!(f, "Spending request not found"),
@ -1049,7 +1094,11 @@ mod tests {
fn test_address(n: u8) -> Address {
let mut bytes = [0u8; 32];
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 {
@ -1061,9 +1110,9 @@ mod tests {
#[test]
fn test_spending_limit() {
let mut limit = SpendingLimit::new(
1000, // 1000 per tx
5000, // 5000 per period
3600, // 1 hour period
1000, // 1000 per tx
5000, // 5000 per period
3600, // 1 hour period
);
// Should allow first spend
@ -1088,13 +1137,7 @@ mod tests {
fn test_treasury_pool() {
let pool_id = TreasuryPoolId::new([1u8; 32]);
let config = TreasuryConfig::ecosystem(1_000_000);
let mut pool = TreasuryPool::new(
pool_id,
"Test Pool".to_string(),
config,
1_000_000,
0,
);
let mut pool = TreasuryPool::new(pool_id, "Test Pool".to_string(), config, 1_000_000, 0);
// Create spending request
let request_id = test_hash(1);
@ -1108,7 +1151,8 @@ mod tests {
"Test payment".to_string(),
proposer,
100,
).unwrap();
)
.unwrap();
assert!(pool.pending_requests.contains_key(&request_id));
}

View file

@ -105,7 +105,8 @@ impl VestingSchedule {
// Linear vesting between cliff and end
// 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
}
@ -131,7 +132,9 @@ impl VestingSchedule {
}
/// 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 {
/// Vesting is active.
Active,
@ -302,7 +305,9 @@ impl VestingContract {
/// Returns the unvested amount.
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.
@ -429,7 +434,8 @@ impl VestingManager {
schedule: VestingSchedule,
description: String,
) -> 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;
// Index by beneficiary
@ -462,11 +468,7 @@ impl VestingManager {
pub fn get_by_beneficiary(&self, beneficiary: &Address) -> Vec<&VestingContract> {
self.by_beneficiary
.get(beneficiary)
.map(|ids| {
ids.iter()
.filter_map(|id| self.contracts.get(id))
.collect()
})
.map(|ids| ids.iter().filter_map(|id| self.contracts.get(id)).collect())
.unwrap_or_default()
}
@ -474,22 +476,28 @@ impl VestingManager {
pub fn get_by_grantor(&self, grantor: &Address) -> Vec<&VestingContract> {
self.by_grantor
.get(grantor)
.map(|ids| {
ids.iter()
.filter_map(|id| self.contracts.get(id))
.collect()
})
.map(|ids| ids.iter().filter_map(|id| self.contracts.get(id)).collect())
.unwrap_or_default()
}
/// 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)?;
contract.claim(caller, timestamp)
}
/// 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)?;
contract.revoke(caller, timestamp)
}
@ -523,9 +531,18 @@ impl VestingManager {
let contracts: Vec<_> = self.contracts.values().collect();
VestingStats {
total_contracts: contracts.len(),
active_contracts: contracts.iter().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(),
active_contracts: contracts
.iter()
.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_claimed: contracts.iter().map(|c| c.claimed_amount).sum(),
}
@ -556,7 +573,11 @@ mod tests {
fn test_address(n: u8) -> Address {
let mut bytes = [0u8; 32];
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]
@ -591,7 +612,8 @@ mod tests {
grantor,
VestingSchedule::new(1_000_000, 0, 12, start_time, true), // 1 year, no cliff
"Test vesting".to_string(),
).unwrap();
)
.unwrap();
// After 6 months, should have ~50% vested
let six_months = 6 * 30 * 24 * 60 * 60;
@ -617,7 +639,8 @@ mod tests {
grantor.clone(),
VestingSchedule::new(1_000_000, 0, 12, 0, true),
"Revocable".to_string(),
).unwrap();
)
.unwrap();
// Revoke at 6 months
let six_months = 6 * 30 * 24 * 60 * 60;
@ -637,7 +660,8 @@ mod tests {
grantor.clone(),
VestingSchedule::new(1_000_000, 0, 12, 0, false), // non-revocable
"Non-revocable".to_string(),
).unwrap();
)
.unwrap();
let result = contract.revoke(&grantor, 0);
assert!(matches!(result, Err(VestingError::NotRevocable)));
@ -649,12 +673,14 @@ mod tests {
let beneficiary = test_address(1);
let grantor = test_address(2);
let id = manager.create_vesting(
beneficiary.clone(),
grantor,
VestingSchedule::new(1_000_000, 0, 12, 0, true),
"Test".to_string(),
).unwrap();
let id = manager
.create_vesting(
beneficiary.clone(),
grantor,
VestingSchedule::new(1_000_000, 0, 12, 0, true),
"Test".to_string(),
)
.unwrap();
assert!(manager.get(&id).is_some());
assert_eq!(manager.get_by_beneficiary(&beneficiary).len(), 1);

View file

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

View file

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

View file

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

View file

@ -43,9 +43,13 @@ pub mod template;
pub use kheavyhash::{KHeavyHash, PowHash};
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 template::{BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction};
pub use template::{
BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction,
};
use synor_types::{Address, Hash256};
@ -297,27 +301,24 @@ mod tests {
#[test]
fn test_target_comparison() {
let target = Target::from_bytes([
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,
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,
]);
// Hash with more leading zeros should pass
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, 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, 0x01,
]);
assert!(target.is_met_by(&easy_hash));
// Hash with fewer leading zeros should fail
let hard_hash = Hash256::from_bytes([
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, 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,
]);
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).
//! Matrix multiplication is performed using Galois field arithmetic.
/// The irreducible polynomial for GF(2^8): x^8 + x^4 + x^3 + x + 1
const GF_POLY: u16 = 0x11B;

View file

@ -301,7 +301,8 @@ impl StratumServer {
};
// 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(&extra_nonce1);
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 method = request
.get("method")
.and_then(|v| v.as_str())
.unwrap_or("");
let method = request.get("method").and_then(|v| v.as_str()).unwrap_or("");
let response = match method {
"mining.subscribe" => {
let extra_nonce = self.allocate_extra_nonce();
let result = serde_json::json!([
[["mining.notify", "1"]],
extra_nonce,
8
]);
let result =
serde_json::json!([[["mining.notify", "1"]], extra_nonce, 8]);
StratumResponse {
id,
result: Some(result),
@ -463,7 +458,8 @@ impl StratumServer {
}
"mining.authorize" => {
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());
authorized = true;
self.register_worker(worker.to_string(), self.allocate_extra_nonce());
@ -485,10 +481,26 @@ impl StratumServer {
// Parse submission
let params = request.get("params").cloned().unwrap_or_default();
let submission = ShareSubmission {
worker: params.get(0).and_then(|v| v.as_str()).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(),
worker: params
.get(0)
.and_then(|v| v.as_str())
.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(),
};
@ -680,7 +692,10 @@ mod tests {
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]
@ -699,7 +714,7 @@ mod tests {
// Create a job with easy targets
let job = StratumJob {
job_id: "1".to_string(),
header_hash: "00".repeat(32), // 32 zero bytes
header_hash: "00".repeat(32), // 32 zero bytes
share_target: "ff".repeat(32), // All 0xff = easy target
block_target: "00".repeat(32), // All 0x00 = impossible target
timestamp: 1234567890,
@ -745,7 +760,11 @@ mod tests {
timestamp: 1234567890,
};
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
let duplicate_submission = ShareSubmission {

View file

@ -239,13 +239,17 @@ impl BlockTemplateBuilder {
/// Builds the block template.
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()))?;
// Build header data first before taking coinbase
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()))?;
// Calculate fees
@ -321,7 +325,11 @@ impl BlockTemplateBuilder {
}
// 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
merkle_root(&txids)

View file

@ -7,8 +7,7 @@ use libp2p::{
gossipsub::{self, IdentTopic, MessageAuthenticity, MessageId, ValidationMode},
identify,
kad::{self, store::MemoryStore},
mdns,
ping,
mdns, ping,
request_response::{self, ProtocolSupport},
swarm::NetworkBehaviour,
PeerId, StreamProtocol,
@ -35,7 +34,10 @@ pub struct SynorBehaviour {
impl SynorBehaviour {
/// 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();
// GossipSub configuration
@ -68,21 +70,15 @@ impl SynorBehaviour {
// Request-response configuration
let request_response = request_response::Behaviour::new(
[(
StreamProtocol::new(SYNOR_PROTOCOL),
ProtocolSupport::Full,
)],
request_response::Config::default()
.with_request_timeout(config.sync.request_timeout),
[(StreamProtocol::new(SYNOR_PROTOCOL), ProtocolSupport::Full)],
request_response::Config::default().with_request_timeout(config.sync.request_timeout),
);
// Identify configuration
let identify_config = identify::Config::new(
"/synor/id/1.0.0".to_string(),
keypair.public(),
)
.with_agent_version(crate::user_agent())
.with_push_listen_addr_updates(true);
let identify_config =
identify::Config::new("/synor/id/1.0.0".to_string(), keypair.public())
.with_agent_version(crate::user_agent())
.with_push_listen_addr_updates(true);
let identify = identify::Behaviour::new(identify_config);
@ -94,10 +90,7 @@ impl SynorBehaviour {
);
// mDNS configuration
let mdns = mdns::tokio::Behaviour::new(
mdns::Config::default(),
local_peer_id,
)?;
let mdns = mdns::tokio::Behaviour::new(mdns::Config::default(), local_peer_id)?;
Ok(SynorBehaviour {
gossipsub,
@ -135,22 +128,35 @@ impl SynorBehaviour {
}
/// 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)
}
/// 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)
}
/// 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)
}
/// 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)
}
@ -191,10 +197,7 @@ impl SynorBehaviour {
/// Returns peers subscribed to a topic.
pub fn topic_peers(&self, topic: &str) -> Vec<PeerId> {
let topic = IdentTopic::new(topic);
self.gossipsub
.mesh_peers(&topic.hash())
.cloned()
.collect()
self.gossipsub.mesh_peers(&topic.hash()).cloned().collect()
}
}

View file

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

View file

@ -41,8 +41,8 @@ impl Default for EclipseConfig {
min_outbound: 8,
anchor_count: 4,
rotation_interval: Duration::from_secs(3600), // 1 hour
rotation_percentage: 0.1, // 10%
trial_period: Duration::from_secs(300), // 5 minutes
rotation_percentage: 0.1, // 10%
trial_period: Duration::from_secs(300), // 5 minutes
max_trial_peers: 5,
}
}
@ -414,7 +414,9 @@ mod tests {
// Different subnet should be allowed
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]

View file

@ -41,7 +41,9 @@ pub use behaviour::SynorBehaviour;
pub use config::NetworkConfig;
pub use eclipse::{EclipseConfig, EclipseProtection, EclipseStats, PeerType};
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 protocol::{SynorProtocol, SynorRequest, SynorResponse};
pub use rate_limit::{

View file

@ -278,7 +278,9 @@ impl Headers {
/// Creates an empty headers response.
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 },
/// Block production rate significantly lower than expected.
/// 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.
/// `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.
/// `age_secs` and `max_age_secs` are in seconds.
StaleTip { age_secs: u64, max_age_secs: u64 },
/// 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.
BehindNetwork { our_score: u64, network_score: u64 },
/// All connections are inbound (potential eclipse attack).
@ -141,40 +150,59 @@ impl PartitionReason {
pub fn description(&self) -> String {
match self {
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 } => {
format!("Only {} outbound peers, need at least {}", current, required)
format!(
"Only {} outbound peers, need at least {}",
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 } => {
format!(
"Subnet {:X} has {}% of peers, max allowed 50%",
subnet,
percentage
subnet, percentage
)
}
PartitionReason::BlockProductionStalled { 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!(
"Block rate {:.2}/s, expected {:.2}/s",
*current_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!(
"Only {}% of peers agree on tips, need {}%",
matching_peers_pct,
threshold_pct
matching_peers_pct, threshold_pct
)
}
PartitionReason::StaleTip { age_secs, max_age_secs } => {
format!("Best tip is {} seconds old, max allowed {} seconds", age_secs, max_age_secs)
PartitionReason::StaleTip {
age_secs,
max_age_secs,
} => {
format!(
"Best tip is {} seconds old, max allowed {} seconds",
age_secs, max_age_secs
)
}
PartitionReason::ProtocolVersionSkew {
our_version,
@ -404,12 +432,7 @@ impl PartitionDetector {
}
/// Records a peer connection.
pub fn record_peer_connected(
&self,
peer_id: PeerId,
ip: Option<IpAddr>,
is_outbound: bool,
) {
pub fn record_peer_connected(&self, peer_id: PeerId, ip: Option<IpAddr>, is_outbound: bool) {
let info = PeerPartitionInfo::new(peer_id, ip, is_outbound);
self.peers.write().insert(peer_id, info);
@ -743,7 +766,9 @@ impl PartitionDetector {
// Any critical reason means we're partitioned
let mut all_reasons = critical_reasons;
all_reasons.extend(warning_reasons);
PartitionStatus::Partitioned { reasons: all_reasons }
PartitionStatus::Partitioned {
reasons: all_reasons,
}
} else if !warning_reasons.is_empty() {
PartitionStatus::Degraded {
reasons: warning_reasons,
@ -801,11 +826,7 @@ impl PartitionDetector {
let (status_str, warning_count, critical_count) = match &status {
PartitionStatus::Connected => ("Connected".to_string(), 0, 0),
PartitionStatus::Degraded { reasons } => (
"Degraded".to_string(),
reasons.len(),
0,
),
PartitionStatus::Degraded { reasons } => ("Degraded".to_string(), reasons.len(), 0),
PartitionStatus::Partitioned { reasons } => {
let critical = reasons.iter().filter(|r| r.is_critical()).count();
(
@ -966,10 +987,9 @@ mod tests {
match status {
PartitionStatus::Degraded { reasons } => {
assert!(reasons.iter().any(|r| matches!(
r,
PartitionReason::InsufficientPeers { .. }
)));
assert!(reasons
.iter()
.any(|r| matches!(r, PartitionReason::InsufficientPeers { .. })));
}
_ => panic!("Expected degraded status"),
}
@ -993,10 +1013,9 @@ mod tests {
match status {
PartitionStatus::Partitioned { reasons } => {
assert!(reasons.iter().any(|r| matches!(
r,
PartitionReason::NoOutboundConnections
)));
assert!(reasons
.iter()
.any(|r| matches!(r, PartitionReason::NoOutboundConnections)));
}
_ => panic!("Expected partitioned status"),
}
@ -1098,9 +1117,7 @@ mod tests {
required: 3,
},
PartitionReason::NoOutboundConnections,
PartitionReason::BlockProductionStalled {
duration_secs: 60,
},
PartitionReason::BlockProductionStalled { duration_secs: 60 },
];
for reason in reasons {

View file

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

View file

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

View file

@ -371,7 +371,9 @@ impl PerPeerLimiter {
pub fn cleanup(&self, connected_peers: &[PeerId]) {
let connected: std::collections::HashSet<_> = connected_peers.iter().collect();
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.
@ -522,7 +524,7 @@ mod tests {
};
let limiter = PerPeerLimiter::with_violation_config(
config,
3, // max_violations
3, // max_violations
Duration::from_millis(100), // short cooldown for test
);

View file

@ -224,7 +224,10 @@ impl RateLimiter {
/// Returns the number of requests made by a peer in the current window.
pub fn request_count(&self, peer_id: &PeerId) -> u32 {
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.

View file

@ -178,7 +178,10 @@ impl PeerReputation {
pub fn record_success(&mut self) {
self.successes += 1;
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.
@ -602,7 +605,10 @@ mod tests {
rep.record_success();
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]
@ -690,7 +696,10 @@ mod tests {
manager.record_success(&peer_id);
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]

View file

@ -3,7 +3,7 @@
use crate::behaviour::SynorBehaviour;
use crate::config::NetworkConfig;
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::rate_limit::{PerPeerLimiter, RateLimitConfig as TokenBucketConfig};
use crate::ratelimit::{RateLimitResult, RateLimiters};
@ -12,14 +12,8 @@ use crate::sync::{SyncManager, SyncStatus};
use crate::topics;
use futures::StreamExt;
use libp2p::{
gossipsub,
identify,
kad,
mdns,
ping,
request_response,
swarm::SwarmEvent,
Multiaddr, PeerId, Swarm, SwarmBuilder,
gossipsub, identify, kad, mdns, ping, request_response, swarm::SwarmEvent, Multiaddr, PeerId,
Swarm, SwarmBuilder,
};
use parking_lot::RwLock;
use std::sync::Arc;
@ -125,7 +119,10 @@ impl NetworkHandle {
}
/// 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
.send(NetworkCommand::BroadcastBlock(announcement))
.await
@ -133,7 +130,10 @@ impl NetworkHandle {
}
/// 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
.send(NetworkCommand::BroadcastTransaction(announcement))
.await
@ -278,10 +278,8 @@ pub struct NetworkService {
/// Event sender.
event_tx: broadcast::Sender<NetworkEvent>,
/// Pending requests.
pending_requests: RwLock<std::collections::HashMap<
request_response::OutboundRequestId,
PendingRequest,
>>,
pending_requests:
RwLock<std::collections::HashMap<request_response::OutboundRequestId, PendingRequest>>,
}
struct PendingRequest {
@ -290,7 +288,9 @@ struct PendingRequest {
impl NetworkService {
/// 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
let local_keypair = libp2p::identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_keypair.public());
@ -310,9 +310,7 @@ impl NetworkService {
.with_behaviour(|key| {
SynorBehaviour::new(key, &config).expect("Behaviour creation failed")
})?
.with_swarm_config(|cfg| {
cfg.with_idle_connection_timeout(config.idle_timeout)
})
.with_swarm_config(|cfg| cfg.with_idle_connection_timeout(config.idle_timeout))
.build();
// Create peer, sync, and reputation managers
@ -397,13 +395,18 @@ impl NetworkService {
}
/// 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 {
SwarmEvent::NewListenAddr { address, .. } => {
info!("Listening on {}", address);
}
SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } => {
SwarmEvent::ConnectionEstablished {
peer_id, endpoint, ..
} => {
// Check if peer is banned via reputation system
if self.reputation_manager.is_banned(&peer_id) {
warn!("Rejecting connection from banned peer: {}", peer_id);
@ -458,22 +461,26 @@ impl NetworkService {
message_id: _,
message,
}) => {
self.handle_gossip_message(propagation_source, message).await;
self.handle_gossip_message(propagation_source, message)
.await;
}
SynorBehaviourEvent::RequestResponse(request_response::Event::Message {
peer,
message,
}) => {
match message {
request_response::Message::Request { request, channel, .. } => {
self.handle_request(peer, request, channel).await;
}
request_response::Message::Response { request_id, response } => {
self.handle_response(peer, request_id, response).await;
}
}) => match message {
request_response::Message::Request {
request, channel, ..
} => {
self.handle_request(peer, request, channel).await;
}
}
request_response::Message::Response {
request_id,
response,
} => {
self.handle_response(peer, request_id, response).await;
}
},
SynorBehaviourEvent::RequestResponse(request_response::Event::OutboundFailure {
peer,
@ -484,7 +491,9 @@ impl NetworkService {
self.sync_manager.on_request_failed(request_id);
self.peer_manager.update_peer(&peer, |p| p.record_failure());
// 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 {
warn!("Auto-banned peer {} for repeated failures", peer);
let _ = self.swarm.disconnect_peer_id(peer);
@ -508,25 +517,25 @@ impl NetworkService {
}
}
SynorBehaviourEvent::Ping(ping::Event { peer, result, .. }) => {
match result {
Ok(rtt) => {
trace!("Ping to {} succeeded: {:?}", peer, rtt);
self.peer_manager.update_peer(&peer, |p| {
p.latency = Some(rtt);
});
}
Err(e) => {
debug!("Ping to {} failed: {}", peer, e);
}
SynorBehaviourEvent::Ping(ping::Event { peer, result, .. }) => match result {
Ok(rtt) => {
trace!("Ping to {} succeeded: {:?}", peer, rtt);
self.peer_manager.update_peer(&peer, |p| {
p.latency = Some(rtt);
});
}
}
Err(e) => {
debug!("Ping to {} failed: {}", peer, e);
}
},
SynorBehaviourEvent::Mdns(mdns::Event::Discovered(peers)) => {
for (peer_id, addr) in peers {
debug!("mDNS discovered peer: {} at {}", peer_id, addr);
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) {
debug!("Failed to dial discovered peer: {}", e);
}
@ -540,19 +549,19 @@ impl NetworkService {
}
}
SynorBehaviourEvent::Kademlia(kad::Event::OutboundQueryProgressed { result, .. }) => {
match result {
kad::QueryResult::GetClosestPeers(Ok(ok)) => {
for peer in ok.peers {
debug!("Kademlia found peer: {:?}", peer);
}
SynorBehaviourEvent::Kademlia(kad::Event::OutboundQueryProgressed {
result, ..
}) => match result {
kad::QueryResult::GetClosestPeers(Ok(ok)) => {
for peer in ok.peers {
debug!("Kademlia found peer: {:?}", peer);
}
kad::QueryResult::Bootstrap(Ok(_)) => {
info!("Kademlia bootstrap complete");
}
_ => {}
}
}
kad::QueryResult::Bootstrap(Ok(_)) => {
info!("Kademlia bootstrap complete");
}
_ => {}
},
_ => {}
}
@ -565,14 +574,24 @@ impl NetworkService {
match topic {
t if t == topics::BLOCKS => {
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));
}
}
t if t == topics::TRANSACTIONS => {
if let Ok(announcement) = borsh::from_slice::<TransactionAnnouncement>(&message.data) {
debug!("Received tx announcement from {}: {}", source, announcement.txid);
let _ = self.event_tx.send(NetworkEvent::NewTransaction(announcement));
if let Ok(announcement) =
borsh::from_slice::<TransactionAnnouncement>(&message.data)
{
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);
});
// 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 {
warn!("Auto-banned peer {} for repeated spam violations", peer);
let _ = self.swarm.disconnect_peer_id(peer);
@ -640,14 +661,15 @@ impl NetworkService {
p.add_reputation(reputation::TIMEOUT);
});
// 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 {
warn!("Auto-banned peer {} for spam violations", peer);
let _ = self.swarm.disconnect_peer_id(peer);
}
let response = SynorResponse::Error(
"Rate limited. Too many requests per second.".to_string()
);
let response =
SynorResponse::Error("Rate limited. Too many requests per second.".to_string());
if let Err(e) = self.swarm.behaviour_mut().send_response(channel, response) {
warn!("Failed to send rate limit response to {}: {:?}", peer, e);
}
@ -688,7 +710,8 @@ impl NetworkService {
p.add_reputation(reputation::TIMEOUT);
});
// 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!(
"Rate limited. Retry after {} seconds.",
retry_after.as_secs()
@ -710,9 +733,14 @@ impl NetworkService {
p.add_reputation(reputation::INVALID_DATA);
});
// 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 {
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 response = SynorResponse::Error(format!(
@ -794,7 +822,8 @@ impl NetworkService {
let _ = self.event_tx.send(NetworkEvent::HeadersReceived(headers));
}
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));
}
_ => {}
@ -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_id = self.swarm.behaviour_mut().send_request(&peer, request);
// 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_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) => {

View file

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

View file

@ -174,7 +174,8 @@ pub trait UtxoApi {
/// Gets balances by addresses.
#[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.
#[method(name = "getCoinSupply")]
@ -194,7 +195,10 @@ pub trait MiningApi {
/// Submits a mined block.
#[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.
#[method(name = "getNetworkHashrate")]

View file

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

View file

@ -102,7 +102,9 @@ impl PoolStats {
PoolStatsSnapshot {
connections_created: self.connections_created.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)),
requests_total: self.requests_total.load(Ordering::Relaxed),
requests_failed: self.requests_failed.load(Ordering::Relaxed),
@ -289,7 +291,9 @@ impl ConnectionPool {
// Try to get an existing client
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();
return Ok(client);
}
@ -332,7 +336,9 @@ impl ConnectionPool {
// Try to get an existing client
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();
return Ok(client);
}
@ -355,7 +361,9 @@ impl ConnectionPool {
{
entry_lock.http_clients.push(client);
} 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);
} 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;
if entry_lock.consecutive_failures >= self.config.max_retries {
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,
}));
endpoints.insert(endpoint.to_string(), Mutex::new(EndpointEntry {
http_clients: Vec::new(),
ws_clients: Vec::new(),
semaphore: Arc::new(Semaphore::new(self.config.max_connections_per_endpoint)),
last_health_check: Instant::now(),
healthy: true,
consecutive_failures: 0,
}));
endpoints.insert(
endpoint.to_string(),
Mutex::new(EndpointEntry {
http_clients: Vec::new(),
ws_clients: Vec::new(),
semaphore: Arc::new(Semaphore::new(self.config.max_connections_per_endpoint)),
last_health_check: Instant::now(),
healthy: true,
consecutive_failures: 0,
}),
);
entry
}
@ -533,7 +548,9 @@ impl ConnectionPool {
match self.try_create_http_client(endpoint).await {
Ok(client) => {
self.stats.connections_created.fetch_add(1, Ordering::Relaxed);
self.stats
.connections_created
.fetch_add(1, Ordering::Relaxed);
self.mark_healthy(endpoint);
return Ok(client);
}
@ -545,9 +562,7 @@ impl ConnectionPool {
}
self.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
Err(last_error.unwrap_or(PoolError::ConnectionFailed(
"Unknown error".to_string(),
)))
Err(last_error.unwrap_or(PoolError::ConnectionFailed("Unknown error".to_string())))
}
/// Creates a new WebSocket client with retries.
@ -562,7 +577,9 @@ impl ConnectionPool {
match self.try_create_ws_client(endpoint).await {
Ok(client) => {
self.stats.connections_created.fetch_add(1, Ordering::Relaxed);
self.stats
.connections_created
.fetch_add(1, Ordering::Relaxed);
self.mark_healthy(endpoint);
return Ok(client);
}
@ -574,9 +591,7 @@ impl ConnectionPool {
}
self.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
Err(last_error.unwrap_or(PoolError::ConnectionFailed(
"Unknown error".to_string(),
)))
Err(last_error.unwrap_or(PoolError::ConnectionFailed("Unknown error".to_string())))
}
/// Attempts to create an HTTP client.
@ -599,10 +614,7 @@ impl ConnectionPool {
}
/// Attempts to create a WebSocket client.
async fn try_create_ws_client(
&self,
endpoint: &str,
) -> Result<Arc<PooledWsClient>, PoolError> {
async fn try_create_ws_client(&self, endpoint: &str) -> Result<Arc<PooledWsClient>, PoolError> {
let client = WsClientBuilder::default()
.request_timeout(self.config.request_timeout)
.connection_timeout(self.config.connect_timeout)
@ -674,7 +686,10 @@ impl<'a> PooledHttpClientGuard<'a> {
pub fn success(&self) {
if let Some(ref client) = self.client {
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) {
if let Some(ref client) = self.client {
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).
pub fn discard(mut self) {
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);
}
}
@ -727,7 +748,10 @@ impl<'a> PooledWsClientGuard<'a> {
pub fn success(&self) {
if let Some(ref client) = self.client {
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) {
if let Some(ref client) = self.client {
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).
pub fn discard(mut self) {
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);
}
}
@ -760,7 +790,10 @@ impl<'a> Drop for PooledWsClientGuard<'a> {
#[async_trait::async_trait]
pub trait ConnectionPoolExt {
/// 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.
async fn acquire_ws_guard(&self, endpoint: &str) -> Result<PooledWsClientGuard<'_>, PoolError>;
@ -768,7 +801,10 @@ pub trait ConnectionPoolExt {
#[async_trait::async_trait]
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?;
Ok(PooledHttpClientGuard::new(self, client))
}
@ -806,7 +842,10 @@ mod tests {
#[test]
fn test_pool_config_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]

View file

@ -131,7 +131,9 @@ impl RpcServer {
.await
.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);
self.http_handle = Some(server.start(module));
@ -150,7 +152,9 @@ impl RpcServer {
.await
.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);
self.ws_handle = Some(server.start(module));
@ -235,9 +239,9 @@ impl RpcModuleBuilder {
F: Fn(jsonrpsee::types::Params<'_>) -> R + Send + Sync + Clone + 'static,
R: jsonrpsee::IntoResponse + Send + 'static,
{
let _ = self.module.register_method(name, move |params, _| {
callback(params)
});
let _ = self
.module
.register_method(name, move |params, _| callback(params));
self
}

View file

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

View file

@ -61,7 +61,13 @@ pub fn storage_read(key: &[u8; 32], out: &mut [u8]) -> i32 {
/// Writes to storage.
#[cfg(target_arch = "wasm32")]
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.
@ -152,7 +158,13 @@ pub fn get_chain_id() -> u64 {
#[cfg(target_arch = "wasm32")]
pub fn sha3(data: &[u8]) -> [u8; 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
}
@ -160,7 +172,13 @@ pub fn sha3(data: &[u8]) -> [u8; 32] {
#[cfg(target_arch = "wasm32")]
pub fn blake3(data: &[u8]) -> [u8; 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
}

View file

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

View file

@ -9,17 +9,16 @@
//!
//! Run with: cargo bench -p synor-storage --bench storage_bench
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 synor_storage::{
cache::{CacheConfig, LruCache, StorageCache},
cf, Database, DatabaseConfig,
cf,
stores::{
ChainState, GhostdagStore, HeaderStore, MetadataStore, RelationsStore,
StoredGhostdagData, StoredRelations, StoredUtxo, UtxoStore,
ChainState, GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredGhostdagData,
StoredRelations, StoredUtxo, UtxoStore,
},
Database, DatabaseConfig,
};
use synor_types::{BlockHeader, BlockId, BlueScore, Hash256, Timestamp, TransactionId};
use tempfile::TempDir;
@ -83,16 +82,22 @@ fn make_ghostdag_data(n: u64) -> StoredGhostdagData {
StoredGhostdagData {
blue_score: n * 10,
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(),
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.
fn make_relations(n: u64) -> 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(),
}
}
@ -157,25 +162,21 @@ fn header_write_batch(c: &mut Criterion) {
let headers: Vec<BlockHeader> = (0..count).map(|i| make_header(i as u64)).collect();
group.throughput(Throughput::Elements(count as u64));
group.bench_with_input(
BenchmarkId::from_parameter(count),
&headers,
|b, hdrs| {
b.iter_batched(
|| {
let (dir, db) = setup_db();
let store = HeaderStore::new(db);
(dir, store)
},
|(_dir, store)| {
for header in hdrs {
store.put(header).unwrap();
}
},
criterion::BatchSize::SmallInput,
)
},
);
group.bench_with_input(BenchmarkId::from_parameter(count), &headers, |b, hdrs| {
b.iter_batched(
|| {
let (dir, db) = setup_db();
let store = HeaderStore::new(db);
(dir, store)
},
|(_dir, store)| {
for header in hdrs {
store.put(header).unwrap();
}
},
criterion::BatchSize::SmallInput,
)
});
}
group.finish();
@ -189,9 +190,7 @@ fn header_read_single(c: &mut Criterion) {
let store = populate_headers(&db, 1000);
let target_hash = make_header(500).block_id();
group.bench_function("single", |b| {
b.iter(|| black_box(store.get(&target_hash)))
});
group.bench_function("single", |b| b.iter(|| black_box(store.get(&target_hash))));
drop(dir);
group.finish();
@ -208,9 +207,7 @@ fn header_read_varying_db_sizes(c: &mut Criterion) {
group.bench_with_input(
BenchmarkId::from_parameter(db_size),
&target_hash,
|b, hash| {
b.iter(|| black_box(store.get(hash)))
},
|b, hash| b.iter(|| black_box(store.get(hash))),
);
drop(dir);
@ -231,13 +228,9 @@ fn header_multi_get(c: &mut Criterion) {
.collect();
group.throughput(Throughput::Elements(count as u64));
group.bench_with_input(
BenchmarkId::from_parameter(count),
&hashes,
|b, h| {
b.iter(|| black_box(store.multi_get(h)))
},
);
group.bench_with_input(BenchmarkId::from_parameter(count), &hashes, |b, h| {
b.iter(|| black_box(store.multi_get(h)))
});
}
drop(dir);
@ -304,9 +297,7 @@ fn utxo_lookup(c: &mut Criterion) {
group.bench_with_input(
BenchmarkId::new("size", db_size),
&target_txid,
|b, txid| {
b.iter(|| black_box(store.get(txid, 0)))
},
|b, txid| b.iter(|| black_box(store.get(txid, 0))),
);
drop(dir);
@ -320,26 +311,22 @@ fn utxo_delete(c: &mut Criterion) {
group.throughput(Throughput::Elements(1));
for db_size in [1000, 10000] {
group.bench_with_input(
BenchmarkId::new("size", db_size),
&db_size,
|b, &size| {
b.iter_batched(
|| {
let (dir, db) = setup_db();
let store = populate_utxos(&db, size);
let target_txid = make_txid((size / 2) as u64);
(dir, store, target_txid)
},
|(dir, store, txid)| {
let result = store.delete(&txid, 0);
drop(dir);
black_box(result)
},
criterion::BatchSize::SmallInput,
)
},
);
group.bench_with_input(BenchmarkId::new("size", db_size), &db_size, |b, &size| {
b.iter_batched(
|| {
let (dir, db) = setup_db();
let store = populate_utxos(&db, size);
let target_txid = make_txid((size / 2) as u64);
(dir, store, target_txid)
},
|(dir, store, txid)| {
let result = store.delete(&txid, 0);
drop(dir);
black_box(result)
},
criterion::BatchSize::SmallInput,
)
});
}
group.finish();
@ -474,19 +461,15 @@ fn iterator_full_scan(c: &mut Criterion) {
let _ = populate_headers(&db, db_size);
group.throughput(Throughput::Elements(db_size as u64));
group.bench_with_input(
BenchmarkId::from_parameter(db_size),
&db,
|b, database| {
b.iter(|| {
let mut count = 0;
for _ in database.iter(cf::HEADERS).unwrap() {
count += 1;
}
black_box(count)
})
},
);
group.bench_with_input(BenchmarkId::from_parameter(db_size), &db, |b, database| {
b.iter(|| {
let mut count = 0;
for _ in database.iter(cf::HEADERS).unwrap() {
count += 1;
}
black_box(count)
})
});
drop(dir);
}
@ -502,7 +485,13 @@ fn iterator_prefix_scan(c: &mut Criterion) {
for tx_num in 0..100 {
let txid = make_txid(tx_num);
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
let target = make_block_id(500);
group.bench_function("get", |b| {
b.iter(|| black_box(store.get(&target)))
});
group.bench_function("get", |b| b.iter(|| black_box(store.get(&target))));
group.bench_function("get_blue_score", |b| {
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);
group.bench_function("get", |b| {
b.iter(|| black_box(store.get(&target)))
});
group.bench_function("get", |b| b.iter(|| black_box(store.get(&target))));
group.bench_function("get_parents", |b| {
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");
group.bench_function("get_tips", |b| {
b.iter(|| black_box(store.get_tips()))
});
group.bench_function("get_tips", |b| b.iter(|| black_box(store.get_tips())));
group.bench_function("set_tips", |b| {
let new_tips = vec![make_block_id(200), make_block_id(201)];
b.iter(|| black_box(store.set_tips(&new_tips)))
});
group.bench_function("get_genesis", |b| {
b.iter(|| black_box(store.get_genesis()))
});
group.bench_function("get_genesis", |b| b.iter(|| black_box(store.get_genesis())));
group.bench_function("get_chain_state", |b| {
b.iter(|| black_box(store.get_chain_state()))
@ -747,9 +728,7 @@ fn storage_cache_operations(c: &mut Criterion) {
});
// Stats access
group.bench_function("stats", |b| {
b.iter(|| black_box(cache.stats()))
});
group.bench_function("stats", |b| b.iter(|| black_box(cache.stats())));
// Total entries
group.bench_function("total_entries", |b| {
@ -812,17 +791,9 @@ criterion_group!(
utxo_get_by_tx,
);
criterion_group!(
batch_benches,
batch_write_mixed,
batch_headers_and_utxos,
);
criterion_group!(batch_benches, batch_write_mixed, batch_headers_and_utxos,);
criterion_group!(
iterator_benches,
iterator_full_scan,
iterator_prefix_scan,
);
criterion_group!(iterator_benches, iterator_full_scan, iterator_prefix_scan,);
criterion_group!(
store_benches,
@ -837,10 +808,7 @@ criterion_group!(
storage_cache_operations,
);
criterion_group!(
db_benches,
database_creation,
);
criterion_group!(db_benches, database_creation,);
criterion_main!(
header_benches,

View file

@ -3,9 +3,7 @@
//! Provides a typed interface to the underlying RocksDB instance.
use crate::cf;
use rocksdb::{
ColumnFamily, ColumnFamilyDescriptor, DBCompressionType, Options, WriteBatch, DB,
};
use rocksdb::{ColumnFamily, ColumnFamilyDescriptor, DBCompressionType, Options, WriteBatch, DB};
use std::path::Path;
use std::sync::Arc;
use thiserror::Error;
@ -182,7 +180,10 @@ impl Database {
}
/// 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 opts = config.to_options();
@ -241,7 +242,10 @@ impl Database {
}
/// 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 iter = self.db.iterator_cf(cf, rocksdb::IteratorMode::Start);
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 stores::{
BatchStore, BlockBody, BlockStore, ChainState, ContractStateStore, ContractStore,
GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredContract,
StoredGhostdagData, StoredRelations, StoredUtxo, TransactionStore, UtxoStore,
GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredContract, StoredGhostdagData,
StoredRelations, StoredUtxo, TransactionStore, UtxoStore,
};
/// Column family names.

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,9 @@ pub use hash::{Hash256, HashError};
pub use transaction::{Transaction, TransactionId, TxInput, TxOutput};
/// 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 {
/// Main production network
Mainnet,

View file

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

View file

@ -10,11 +10,9 @@ use std::sync::Arc;
use parking_lot::RwLock;
use wasmtime::{
Config, Engine, Instance, Linker, Memory, Module, Store, StoreLimits,
StoreLimitsBuilder,
Config, Engine, Instance, Linker, Memory, Module, Store, StoreLimits, StoreLimitsBuilder,
};
use crate::context::ExecutionContext;
use crate::gas::GasMeter;
use crate::storage::{ContractStorage, MemoryStorage};
@ -243,7 +241,11 @@ impl VmEngine {
// Read from storage
let data = caller.data_mut();
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) => {
let len = value.0.len();
// Write to output buffer

View file

@ -100,7 +100,10 @@ impl GasEstimator {
);
// 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),
Err(e) => {
// Try to extract gas used from error
@ -141,7 +144,10 @@ impl GasEstimator {
);
// 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),
Err(e) => {
let gas = match &e {
@ -167,7 +173,14 @@ impl GasEstimator {
storage: MemoryStorage,
) -> GasEstimate {
// 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 {
return initial;
@ -197,7 +210,10 @@ impl GasEstimator {
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,
Err(_) => low = mid + 1,
}
@ -334,7 +350,10 @@ mod tests {
fn test_calldata_cost() {
let data = [0u8, 1, 0, 2, 0, 3]; // 3 zeros, 3 non-zeros
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]

View file

@ -83,7 +83,10 @@ impl HostFunctions {
// Add refund for deletion
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)
}

View file

@ -13,7 +13,9 @@ use synor_types::Hash256;
use crate::ContractId;
/// 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]);
impl StorageKey {
@ -209,10 +211,7 @@ impl MemoryStorage {
for (contract, pending_changes) in self.pending.drain() {
for (key, new_value) in pending_changes {
// Get old value from committed data
let old_value = self.data
.get(&contract)
.and_then(|m| m.get(&key))
.cloned();
let old_value = self.data.get(&contract).and_then(|m| m.get(&key)).cloned();
changes.push(crate::StorageChange {
contract,
@ -233,10 +232,7 @@ impl MemoryStorage {
for (contract, pending_changes) in &self.pending {
for (key, new_value) in pending_changes {
// Get old value from committed data
let old_value = self.data
.get(contract)
.and_then(|m| m.get(key))
.cloned();
let old_value = self.data.get(contract).and_then(|m| m.get(key)).cloned();
changes.push(crate::StorageChange {
contract: *contract,
@ -261,10 +257,7 @@ impl ContractStorage for MemoryStorage {
}
// Then check committed
self.data
.get(contract)
.and_then(|m| m.get(key))
.cloned()
self.data.get(contract).and_then(|m| m.get(key)).cloned()
}
fn set(&mut self, contract: &ContractId, key: StorageKey, value: StorageValue) {
@ -514,13 +507,21 @@ mod tests {
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();
let root2 = storage.root(&contract);
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();
let root3 = storage.root(&contract);