style: format all Rust code with cargo fmt
This commit is contained in:
parent
ae0a9d7cfa
commit
d917f1ed22
97 changed files with 2259 additions and 1543 deletions
|
|
@ -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!({
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
3
apps/explorer-web/.vite/deps_temp_a70ee4eb/package.json
Normal file
3
apps/explorer-web/.vite/deps_temp_a70ee4eb/package.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"type": "module"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(¶ms.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(¶ms.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,
|
||||
¶ms.deployer,
|
||||
params.gas_limit,
|
||||
block_height,
|
||||
timestamp,
|
||||
).await {
|
||||
match ctx
|
||||
.contract
|
||||
.deploy(
|
||||
bytecode,
|
||||
init_args,
|
||||
¶ms.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(¶ms.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(¶ms.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,
|
||||
¶ms.method,
|
||||
args,
|
||||
¶ms.caller,
|
||||
params.value,
|
||||
params.gas_limit,
|
||||
block_height,
|
||||
timestamp,
|
||||
).await {
|
||||
match ctx
|
||||
.contract
|
||||
.call(
|
||||
&contract_id,
|
||||
¶ms.method,
|
||||
args,
|
||||
¶ms.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(¶ms.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(¶ms.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,
|
||||
¶ms.method,
|
||||
args,
|
||||
¶ms.caller,
|
||||
params.value,
|
||||
block_height,
|
||||
timestamp,
|
||||
).await {
|
||||
match ctx
|
||||
.contract
|
||||
.estimate_gas(
|
||||
&contract_id,
|
||||
¶ms.method,
|
||||
args,
|
||||
¶ms.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(¶ms.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(¶ms.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(¶ms.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(¶ms.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,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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]")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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::{
|
||||
|
|
|
|||
|
|
@ -278,7 +278,9 @@ impl Headers {
|
|||
|
||||
/// Creates an empty headers response.
|
||||
pub fn empty() -> Self {
|
||||
Headers { headers: Vec::new() }
|
||||
Headers {
|
||||
headers: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {}",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue