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
|
"params": params
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self
|
let response = self.client.post(&self.url).json(&request).send().await?;
|
||||||
.client
|
|
||||||
.post(&self.url)
|
|
||||||
.json(&request)
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let rpc_response: RpcResponse<T> = response.json().await?;
|
let rpc_response: RpcResponse<T> = response.json().await?;
|
||||||
|
|
||||||
|
|
@ -68,7 +63,8 @@ impl RpcClient {
|
||||||
|
|
||||||
/// Gets a block by hash.
|
/// Gets a block by hash.
|
||||||
pub async fn get_block(&self, hash: &str, include_txs: bool) -> Result<Block> {
|
pub async fn get_block(&self, hash: &str, include_txs: bool) -> Result<Block> {
|
||||||
self.call("synor_getBlock", json!([hash, include_txs])).await
|
self.call("synor_getBlock", json!([hash, include_txs]))
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets block header.
|
/// Gets block header.
|
||||||
|
|
@ -235,7 +231,11 @@ impl RpcClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets contract storage value.
|
/// Gets contract storage value.
|
||||||
pub async fn get_contract_storage(&self, contract_id: &str, key: &str) -> Result<GetStorageResult> {
|
pub async fn get_contract_storage(
|
||||||
|
&self,
|
||||||
|
contract_id: &str,
|
||||||
|
key: &str,
|
||||||
|
) -> Result<GetStorageResult> {
|
||||||
self.call(
|
self.call(
|
||||||
"synor_getStorageAt",
|
"synor_getStorageAt",
|
||||||
json!({
|
json!({
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,20 @@ pub async fn validate(address: &str, format: OutputFormat) -> Result<()> {
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
if validation.is_valid {
|
if validation.is_valid {
|
||||||
output::print_success("Address is valid");
|
output::print_success("Address is valid");
|
||||||
output::print_kv("Network", validation.network.as_deref().unwrap_or("unknown"));
|
output::print_kv(
|
||||||
output::print_kv("Type", validation.address_type.as_deref().unwrap_or("unknown"));
|
"Network",
|
||||||
|
validation.network.as_deref().unwrap_or("unknown"),
|
||||||
|
);
|
||||||
|
output::print_kv(
|
||||||
|
"Type",
|
||||||
|
validation.address_type.as_deref().unwrap_or("unknown"),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
output::print_error(&format!(
|
output::print_error(&format!(
|
||||||
"Invalid address: {}",
|
"Invalid address: {}",
|
||||||
validation.error.unwrap_or_else(|| "unknown error".to_string())
|
validation
|
||||||
|
.error
|
||||||
|
.unwrap_or_else(|| "unknown error".to_string())
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,10 @@ pub async fn get_block(client: &RpcClient, id: &str, format: OutputFormat) -> Re
|
||||||
output::print_header("Block");
|
output::print_header("Block");
|
||||||
output::print_kv("Hash", &block.hash);
|
output::print_kv("Hash", &block.hash);
|
||||||
output::print_kv("Version", &block.header.version.to_string());
|
output::print_kv("Version", &block.header.version.to_string());
|
||||||
output::print_kv("Timestamp", &output::format_timestamp(block.header.timestamp));
|
output::print_kv(
|
||||||
|
"Timestamp",
|
||||||
|
&output::format_timestamp(block.header.timestamp),
|
||||||
|
);
|
||||||
output::print_kv("Blue Score", &block.header.blue_score.to_string());
|
output::print_kv("Blue Score", &block.header.blue_score.to_string());
|
||||||
output::print_kv("Bits", &format!("0x{:08x}", block.header.bits));
|
output::print_kv("Bits", &format!("0x{:08x}", block.header.bits));
|
||||||
output::print_kv("Nonce", &block.header.nonce.to_string());
|
output::print_kv("Nonce", &block.header.nonce.to_string());
|
||||||
|
|
|
||||||
|
|
@ -18,24 +18,55 @@ pub async fn handle(
|
||||||
format: OutputFormat,
|
format: OutputFormat,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match cmd {
|
match cmd {
|
||||||
ContractCommands::Deploy { wasm, deployer, args, gas } => {
|
ContractCommands::Deploy {
|
||||||
deploy(client, wasm, &deployer, args.as_deref(), Some(gas), format).await
|
wasm,
|
||||||
}
|
deployer,
|
||||||
ContractCommands::Call { contract_id, method, caller, args, value, gas } => {
|
args,
|
||||||
call(client, &contract_id, &method, &caller, args.as_deref(), value, Some(gas), format).await
|
gas,
|
||||||
}
|
} => deploy(client, wasm, &deployer, args.as_deref(), Some(gas), format).await,
|
||||||
ContractCommands::Code { contract_id } => {
|
ContractCommands::Call {
|
||||||
code(client, &contract_id, format).await
|
contract_id,
|
||||||
|
method,
|
||||||
|
caller,
|
||||||
|
args,
|
||||||
|
value,
|
||||||
|
gas,
|
||||||
|
} => {
|
||||||
|
call(
|
||||||
|
client,
|
||||||
|
&contract_id,
|
||||||
|
&method,
|
||||||
|
&caller,
|
||||||
|
args.as_deref(),
|
||||||
|
value,
|
||||||
|
Some(gas),
|
||||||
|
format,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
ContractCommands::Code { contract_id } => code(client, &contract_id, format).await,
|
||||||
ContractCommands::Storage { contract_id, key } => {
|
ContractCommands::Storage { contract_id, key } => {
|
||||||
storage(client, &contract_id, &key, format).await
|
storage(client, &contract_id, &key, format).await
|
||||||
}
|
}
|
||||||
ContractCommands::EstimateGas { contract_id, method, caller, args, value } => {
|
ContractCommands::EstimateGas {
|
||||||
estimate_gas(client, &contract_id, &method, &caller, args.as_deref(), value, format).await
|
contract_id,
|
||||||
}
|
method,
|
||||||
ContractCommands::Info { contract_id } => {
|
caller,
|
||||||
info(client, &contract_id, format).await
|
args,
|
||||||
|
value,
|
||||||
|
} => {
|
||||||
|
estimate_gas(
|
||||||
|
client,
|
||||||
|
&contract_id,
|
||||||
|
&method,
|
||||||
|
&caller,
|
||||||
|
args.as_deref(),
|
||||||
|
value,
|
||||||
|
format,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
ContractCommands::Info { contract_id } => info(client, &contract_id, format).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,23 +83,31 @@ async fn deploy(
|
||||||
let wasm_bytes = fs::read(&wasm_path)?;
|
let wasm_bytes = fs::read(&wasm_path)?;
|
||||||
let wasm_hex = hex::encode(&wasm_bytes);
|
let wasm_hex = hex::encode(&wasm_bytes);
|
||||||
|
|
||||||
output::print_info(&format!("Deploying contract ({} bytes)...", wasm_bytes.len()));
|
output::print_info(&format!(
|
||||||
|
"Deploying contract ({} bytes)...",
|
||||||
|
wasm_bytes.len()
|
||||||
|
));
|
||||||
|
|
||||||
let args_hex = args.unwrap_or("");
|
let args_hex = args.unwrap_or("");
|
||||||
|
|
||||||
let spinner = output::create_spinner("Deploying contract...");
|
let spinner = output::create_spinner("Deploying contract...");
|
||||||
|
|
||||||
let result = client.deploy_contract(&wasm_hex, args_hex, deployer, gas_limit).await?;
|
let result = client
|
||||||
|
.deploy_contract(&wasm_hex, args_hex, deployer, gas_limit)
|
||||||
|
.await?;
|
||||||
|
|
||||||
spinner.finish_and_clear();
|
spinner.finish_and_clear();
|
||||||
|
|
||||||
if let Some(error) = &result.error {
|
if let Some(error) = &result.error {
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Json => {
|
OutputFormat::Json => {
|
||||||
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
|
println!(
|
||||||
"success": false,
|
"{}",
|
||||||
"error": error
|
serde_json::to_string_pretty(&serde_json::json!({
|
||||||
}))?);
|
"success": false,
|
||||||
|
"error": error
|
||||||
|
}))?
|
||||||
|
);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_error(&format!("Deployment failed: {}", error));
|
output::print_error(&format!("Deployment failed: {}", error));
|
||||||
|
|
@ -79,12 +118,15 @@ async fn deploy(
|
||||||
|
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Json => {
|
OutputFormat::Json => {
|
||||||
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
|
println!(
|
||||||
"success": true,
|
"{}",
|
||||||
"contractId": result.contract_id,
|
serde_json::to_string_pretty(&serde_json::json!({
|
||||||
"address": result.address,
|
"success": true,
|
||||||
"gasUsed": result.gas_used,
|
"contractId": result.contract_id,
|
||||||
}))?);
|
"address": result.address,
|
||||||
|
"gasUsed": result.gas_used,
|
||||||
|
}))?
|
||||||
|
);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_success("Contract deployed!");
|
output::print_success("Contract deployed!");
|
||||||
|
|
@ -110,15 +152,20 @@ async fn call(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let args_hex = args.unwrap_or("");
|
let args_hex = args.unwrap_or("");
|
||||||
|
|
||||||
let result = client.call_contract(contract_id, method, args_hex, caller, value, gas_limit).await?;
|
let result = client
|
||||||
|
.call_contract(contract_id, method, args_hex, caller, value, gas_limit)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(error) = &result.error {
|
if let Some(error) = &result.error {
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Json => {
|
OutputFormat::Json => {
|
||||||
output::print_value(&serde_json::json!({
|
output::print_value(
|
||||||
"success": false,
|
&serde_json::json!({
|
||||||
"error": error
|
"success": false,
|
||||||
}), format);
|
"error": error
|
||||||
|
}),
|
||||||
|
format,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_error(&format!("Contract call failed: {}", error));
|
output::print_error(&format!("Contract call failed: {}", error));
|
||||||
|
|
@ -129,12 +176,15 @@ async fn call(
|
||||||
|
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Json => {
|
OutputFormat::Json => {
|
||||||
output::print_value(&serde_json::json!({
|
output::print_value(
|
||||||
"success": result.success,
|
&serde_json::json!({
|
||||||
"data": result.data,
|
"success": result.success,
|
||||||
"gasUsed": result.gas_used,
|
"data": result.data,
|
||||||
"logs": result.logs
|
"gasUsed": result.gas_used,
|
||||||
}), format);
|
"logs": result.logs
|
||||||
|
}),
|
||||||
|
format,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_header("Contract Call Result");
|
output::print_header("Contract Call Result");
|
||||||
|
|
@ -170,9 +220,12 @@ async fn code(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Re
|
||||||
if let Some(error) = &result.error {
|
if let Some(error) = &result.error {
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Json => {
|
OutputFormat::Json => {
|
||||||
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
|
println!(
|
||||||
"error": error
|
"{}",
|
||||||
}))?);
|
serde_json::to_string_pretty(&serde_json::json!({
|
||||||
|
"error": error
|
||||||
|
}))?
|
||||||
|
);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_error(&format!("Failed to get code: {}", error));
|
output::print_error(&format!("Failed to get code: {}", error));
|
||||||
|
|
@ -208,15 +261,23 @@ async fn code(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Re
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get contract storage.
|
/// Get contract storage.
|
||||||
async fn storage(client: &RpcClient, contract_id: &str, key: &str, format: OutputFormat) -> Result<()> {
|
async fn storage(
|
||||||
|
client: &RpcClient,
|
||||||
|
contract_id: &str,
|
||||||
|
key: &str,
|
||||||
|
format: OutputFormat,
|
||||||
|
) -> Result<()> {
|
||||||
let result = client.get_contract_storage(contract_id, key).await?;
|
let result = client.get_contract_storage(contract_id, key).await?;
|
||||||
|
|
||||||
if let Some(error) = &result.error {
|
if let Some(error) = &result.error {
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Json => {
|
OutputFormat::Json => {
|
||||||
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
|
println!(
|
||||||
"error": error
|
"{}",
|
||||||
}))?);
|
serde_json::to_string_pretty(&serde_json::json!({
|
||||||
|
"error": error
|
||||||
|
}))?
|
||||||
|
);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_error(&format!("Failed to get storage: {}", error));
|
output::print_error(&format!("Failed to get storage: {}", error));
|
||||||
|
|
@ -258,14 +319,19 @@ async fn estimate_gas(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let args_hex = args.unwrap_or("");
|
let args_hex = args.unwrap_or("");
|
||||||
|
|
||||||
let result = client.estimate_gas(contract_id, method, args_hex, caller, value).await?;
|
let result = client
|
||||||
|
.estimate_gas(contract_id, method, args_hex, caller, value)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(error) = &result.error {
|
if let Some(error) = &result.error {
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Json => {
|
OutputFormat::Json => {
|
||||||
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
|
println!(
|
||||||
"error": error
|
"{}",
|
||||||
}))?);
|
serde_json::to_string_pretty(&serde_json::json!({
|
||||||
|
"error": error
|
||||||
|
}))?
|
||||||
|
);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_error(&format!("Failed to estimate gas: {}", error));
|
output::print_error(&format!("Failed to estimate gas: {}", error));
|
||||||
|
|
@ -300,9 +366,12 @@ async fn info(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Re
|
||||||
if let Some(error) = &result.error {
|
if let Some(error) = &result.error {
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Json => {
|
OutputFormat::Json => {
|
||||||
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
|
println!(
|
||||||
"error": error
|
"{}",
|
||||||
}))?);
|
serde_json::to_string_pretty(&serde_json::json!({
|
||||||
|
"error": error
|
||||||
|
}))?
|
||||||
|
);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_error(&format!("Failed to get contract info: {}", error));
|
output::print_error(&format!("Failed to get contract info: {}", error));
|
||||||
|
|
@ -313,13 +382,16 @@ async fn info(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Re
|
||||||
|
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Json => {
|
OutputFormat::Json => {
|
||||||
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
|
println!(
|
||||||
"contractId": contract_id,
|
"{}",
|
||||||
"codeHash": result.code_hash,
|
serde_json::to_string_pretty(&serde_json::json!({
|
||||||
"deployer": result.deployer,
|
"contractId": contract_id,
|
||||||
"deployedAt": result.deployed_at,
|
"codeHash": result.code_hash,
|
||||||
"deployedHeight": result.deployed_height,
|
"deployer": result.deployer,
|
||||||
}))?);
|
"deployedAt": result.deployed_at,
|
||||||
|
"deployedHeight": result.deployed_height,
|
||||||
|
}))?
|
||||||
|
);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_header("Contract Info");
|
output::print_header("Contract Info");
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,17 @@ pub async fn handle(
|
||||||
voter,
|
voter,
|
||||||
choice,
|
choice,
|
||||||
reason,
|
reason,
|
||||||
} => vote(client, &proposal_id, &voter, &choice, reason.as_deref(), format).await,
|
} => {
|
||||||
|
vote(
|
||||||
|
client,
|
||||||
|
&proposal_id,
|
||||||
|
&voter,
|
||||||
|
&choice,
|
||||||
|
reason.as_deref(),
|
||||||
|
format,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
GovernanceCommands::Execute {
|
GovernanceCommands::Execute {
|
||||||
proposal_id,
|
proposal_id,
|
||||||
executor,
|
executor,
|
||||||
|
|
@ -75,15 +85,9 @@ async fn info(client: &RpcClient, format: OutputFormat) -> Result<()> {
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_header("Governance Info");
|
output::print_header("Governance Info");
|
||||||
output::print_kv(
|
output::print_kv("Proposal Threshold", &format_synor(info.proposal_threshold));
|
||||||
"Proposal Threshold",
|
|
||||||
&format_synor(info.proposal_threshold),
|
|
||||||
);
|
|
||||||
output::print_kv("Quorum", &format!("{}%", info.quorum_bps as f64 / 100.0));
|
output::print_kv("Quorum", &format!("{}%", info.quorum_bps as f64 / 100.0));
|
||||||
output::print_kv(
|
output::print_kv("Voting Period", &format_blocks(info.voting_period_blocks));
|
||||||
"Voting Period",
|
|
||||||
&format_blocks(info.voting_period_blocks),
|
|
||||||
);
|
|
||||||
output::print_kv(
|
output::print_kv(
|
||||||
"Execution Delay",
|
"Execution Delay",
|
||||||
&format_blocks(info.execution_delay_blocks),
|
&format_blocks(info.execution_delay_blocks),
|
||||||
|
|
@ -125,11 +129,7 @@ async fn stats(client: &RpcClient, format: OutputFormat) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List proposals.
|
/// List proposals.
|
||||||
async fn proposals(
|
async fn proposals(client: &RpcClient, state: Option<&str>, format: OutputFormat) -> Result<()> {
|
||||||
client: &RpcClient,
|
|
||||||
state: Option<&str>,
|
|
||||||
format: OutputFormat,
|
|
||||||
) -> Result<()> {
|
|
||||||
let proposals = match state {
|
let proposals = match state {
|
||||||
Some(s) => client.get_proposals_by_state(s).await?,
|
Some(s) => client.get_proposals_by_state(s).await?,
|
||||||
None => client.get_active_proposals().await?,
|
None => client.get_active_proposals().await?,
|
||||||
|
|
@ -171,7 +171,11 @@ async fn proposals(
|
||||||
println!(
|
println!(
|
||||||
" Participation: {:.2}% | Quorum: {}",
|
" Participation: {:.2}% | Quorum: {}",
|
||||||
proposal.participation_rate,
|
proposal.participation_rate,
|
||||||
if proposal.has_quorum { "Reached" } else { "Not reached" }
|
if proposal.has_quorum {
|
||||||
|
"Reached"
|
||||||
|
} else {
|
||||||
|
"Not reached"
|
||||||
|
}
|
||||||
);
|
);
|
||||||
if let Some(remaining) = proposal.time_remaining_blocks {
|
if let Some(remaining) = proposal.time_remaining_blocks {
|
||||||
println!(" Time Remaining: {}", format_blocks(remaining));
|
println!(" Time Remaining: {}", format_blocks(remaining));
|
||||||
|
|
@ -197,10 +201,16 @@ async fn proposal(client: &RpcClient, id: &str, format: OutputFormat) -> Result<
|
||||||
println!("{}", serde_json::to_string_pretty(&proposal)?);
|
println!("{}", serde_json::to_string_pretty(&proposal)?);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_header(&format!("Proposal #{}: {}", proposal.number, proposal.title));
|
output::print_header(&format!(
|
||||||
|
"Proposal #{}: {}",
|
||||||
|
proposal.number, proposal.title
|
||||||
|
));
|
||||||
println!();
|
println!();
|
||||||
output::print_kv("ID", &proposal.id);
|
output::print_kv("ID", &proposal.id);
|
||||||
output::print_kv("State", &format!("{} {}", state_emoji(&proposal.state), proposal.state));
|
output::print_kv(
|
||||||
|
"State",
|
||||||
|
&format!("{} {}", state_emoji(&proposal.state), proposal.state),
|
||||||
|
);
|
||||||
output::print_kv("Type", &proposal.proposal_type);
|
output::print_kv("Type", &proposal.proposal_type);
|
||||||
output::print_kv("Proposer", &proposal.proposer);
|
output::print_kv("Proposer", &proposal.proposer);
|
||||||
println!();
|
println!();
|
||||||
|
|
@ -212,8 +222,14 @@ async fn proposal(client: &RpcClient, id: &str, format: OutputFormat) -> Result<
|
||||||
println!();
|
println!();
|
||||||
println!("Timeline:");
|
println!("Timeline:");
|
||||||
output::print_kv(" Created", &format!("Block {}", proposal.created_at_block));
|
output::print_kv(" Created", &format!("Block {}", proposal.created_at_block));
|
||||||
output::print_kv(" Voting Starts", &format!("Block {}", proposal.voting_starts_block));
|
output::print_kv(
|
||||||
output::print_kv(" Voting Ends", &format!("Block {}", proposal.voting_ends_block));
|
" Voting Starts",
|
||||||
|
&format!("Block {}", proposal.voting_starts_block),
|
||||||
|
);
|
||||||
|
output::print_kv(
|
||||||
|
" Voting Ends",
|
||||||
|
&format!("Block {}", proposal.voting_ends_block),
|
||||||
|
);
|
||||||
output::print_kv(
|
output::print_kv(
|
||||||
" Execution Allowed",
|
" Execution Allowed",
|
||||||
&format!("Block {}", proposal.execution_allowed_block),
|
&format!("Block {}", proposal.execution_allowed_block),
|
||||||
|
|
@ -226,7 +242,10 @@ async fn proposal(client: &RpcClient, id: &str, format: OutputFormat) -> Result<
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
output::print_kv(" Yes", &format!("{} ({:.1}%)", format_synor(proposal.yes_votes), yes_pct));
|
output::print_kv(
|
||||||
|
" Yes",
|
||||||
|
&format!("{} ({:.1}%)", format_synor(proposal.yes_votes), yes_pct),
|
||||||
|
);
|
||||||
output::print_kv(" No", &format_synor(proposal.no_votes));
|
output::print_kv(" No", &format_synor(proposal.no_votes));
|
||||||
output::print_kv(" Abstain", &format_synor(proposal.abstain_votes));
|
output::print_kv(" Abstain", &format_synor(proposal.abstain_votes));
|
||||||
output::print_kv(" Total Voters", &proposal.votes.len().to_string());
|
output::print_kv(" Total Voters", &proposal.votes.len().to_string());
|
||||||
|
|
@ -276,8 +295,10 @@ async fn create_proposal(
|
||||||
// Build proposal params based on type
|
// Build proposal params based on type
|
||||||
let params = match proposal_type {
|
let params = match proposal_type {
|
||||||
"treasury_spend" | "ecosystem_grant" => {
|
"treasury_spend" | "ecosystem_grant" => {
|
||||||
let recipient = recipient.ok_or_else(|| anyhow::anyhow!("--recipient required for treasury proposals"))?;
|
let recipient = recipient
|
||||||
let amount = amount.ok_or_else(|| anyhow::anyhow!("--amount required for treasury proposals"))?;
|
.ok_or_else(|| anyhow::anyhow!("--recipient required for treasury proposals"))?;
|
||||||
|
let amount = amount
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("--amount required for treasury proposals"))?;
|
||||||
json!({
|
json!({
|
||||||
"recipient": recipient,
|
"recipient": recipient,
|
||||||
"amount": amount
|
"amount": amount
|
||||||
|
|
@ -423,11 +444,18 @@ async fn treasury(client: &RpcClient, format: OutputFormat) -> Result<()> {
|
||||||
println!("Pools:");
|
println!("Pools:");
|
||||||
for pool in &pools {
|
for pool in &pools {
|
||||||
println!();
|
println!();
|
||||||
let status = if pool.frozen { "🔒 FROZEN" } else { "✅ Active" };
|
let status = if pool.frozen {
|
||||||
|
"🔒 FROZEN"
|
||||||
|
} else {
|
||||||
|
"✅ Active"
|
||||||
|
};
|
||||||
println!(" {} [{}]", pool.name, status);
|
println!(" {} [{}]", pool.name, status);
|
||||||
println!(" ID: {}", pool.id);
|
println!(" ID: {}", pool.id);
|
||||||
println!(" Balance: {}", format_synor(pool.balance));
|
println!(" Balance: {}", format_synor(pool.balance));
|
||||||
println!(" Total Deposited: {}", format_synor(pool.total_deposited));
|
println!(
|
||||||
|
" Total Deposited: {}",
|
||||||
|
format_synor(pool.total_deposited)
|
||||||
|
);
|
||||||
println!(" Total Spent: {}", format_synor(pool.total_spent));
|
println!(" Total Spent: {}", format_synor(pool.total_spent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -445,7 +473,11 @@ async fn treasury_pool(client: &RpcClient, id: &str, format: OutputFormat) -> Re
|
||||||
println!("{}", serde_json::to_string_pretty(&pool)?);
|
println!("{}", serde_json::to_string_pretty(&pool)?);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
let status = if pool.frozen { "🔒 FROZEN" } else { "✅ Active" };
|
let status = if pool.frozen {
|
||||||
|
"🔒 FROZEN"
|
||||||
|
} else {
|
||||||
|
"✅ Active"
|
||||||
|
};
|
||||||
output::print_header(&format!("{} [{}]", pool.name, status));
|
output::print_header(&format!("{} [{}]", pool.name, status));
|
||||||
output::print_kv("ID", &pool.id);
|
output::print_kv("ID", &pool.id);
|
||||||
output::print_kv("Balance", &format_synor(pool.balance));
|
output::print_kv("Balance", &format_synor(pool.balance));
|
||||||
|
|
@ -466,7 +498,9 @@ fn format_synor(amount: u64) -> String {
|
||||||
if frac == 0 {
|
if frac == 0 {
|
||||||
format!("{} SYNOR", whole)
|
format!("{} SYNOR", whole)
|
||||||
} else {
|
} else {
|
||||||
format!("{}.{:08} SYNOR", whole, frac).trim_end_matches('0').to_string()
|
format!("{}.{:08} SYNOR", whole, frac)
|
||||||
|
.trim_end_matches('0')
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,7 @@ use crate::output::{self, OutputFormat};
|
||||||
use crate::MiningCommands;
|
use crate::MiningCommands;
|
||||||
|
|
||||||
/// Handle mining commands.
|
/// Handle mining commands.
|
||||||
pub async fn handle(
|
pub async fn handle(client: &RpcClient, cmd: MiningCommands, format: OutputFormat) -> Result<()> {
|
||||||
client: &RpcClient,
|
|
||||||
cmd: MiningCommands,
|
|
||||||
format: OutputFormat,
|
|
||||||
) -> Result<()> {
|
|
||||||
match cmd {
|
match cmd {
|
||||||
MiningCommands::Info => info(client, format).await,
|
MiningCommands::Info => info(client, format).await,
|
||||||
MiningCommands::Template { address } => template(client, &address, format).await,
|
MiningCommands::Template { address } => template(client, &address, format).await,
|
||||||
|
|
@ -32,7 +28,10 @@ async fn info(client: &RpcClient, format: OutputFormat) -> Result<()> {
|
||||||
output::print_header("Mining Information");
|
output::print_header("Mining Information");
|
||||||
output::print_kv("Blocks", &info.blocks.to_string());
|
output::print_kv("Blocks", &info.blocks.to_string());
|
||||||
output::print_kv("Difficulty", &format!("{:.6}", info.difficulty));
|
output::print_kv("Difficulty", &format!("{:.6}", info.difficulty));
|
||||||
output::print_kv("Network Hashrate", &output::format_hashrate(info.network_hashrate));
|
output::print_kv(
|
||||||
|
"Network Hashrate",
|
||||||
|
&output::format_hashrate(info.network_hashrate),
|
||||||
|
);
|
||||||
if let Some(pool_hr) = info.pool_hashrate {
|
if let Some(pool_hr) = info.pool_hashrate {
|
||||||
output::print_kv("Pool Hashrate", &output::format_hashrate(pool_hr));
|
output::print_kv("Pool Hashrate", &output::format_hashrate(pool_hr));
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +53,10 @@ async fn template(client: &RpcClient, address: &str, format: OutputFormat) -> Re
|
||||||
output::print_header("Block Template");
|
output::print_header("Block Template");
|
||||||
output::print_kv("Version", &template.header.version.to_string());
|
output::print_kv("Version", &template.header.version.to_string());
|
||||||
output::print_kv("Parents", &template.header.parents.len().to_string());
|
output::print_kv("Parents", &template.header.parents.len().to_string());
|
||||||
output::print_kv("Timestamp", &output::format_timestamp(template.header.timestamp));
|
output::print_kv(
|
||||||
|
"Timestamp",
|
||||||
|
&output::format_timestamp(template.header.timestamp),
|
||||||
|
);
|
||||||
output::print_kv("Bits", &format!("0x{:08x}", template.header.bits));
|
output::print_kv("Bits", &format!("0x{:08x}", template.header.bits));
|
||||||
output::print_kv("Blue Score", &template.header.blue_score.to_string());
|
output::print_kv("Blue Score", &template.header.blue_score.to_string());
|
||||||
output::print_kv("Target", &output::format_hash(&template.target));
|
output::print_kv("Target", &output::format_hash(&template.target));
|
||||||
|
|
@ -105,7 +107,10 @@ async fn hashrate(client: &RpcClient, format: OutputFormat) -> Result<()> {
|
||||||
println!("{}", serde_json::to_string_pretty(&json)?);
|
println!("{}", serde_json::to_string_pretty(&json)?);
|
||||||
}
|
}
|
||||||
OutputFormat::Text => {
|
OutputFormat::Text => {
|
||||||
output::print_kv("Network Hashrate", &output::format_hashrate(info.network_hashrate));
|
output::print_kv(
|
||||||
|
"Network Hashrate",
|
||||||
|
&output::format_hashrate(info.network_hashrate),
|
||||||
|
);
|
||||||
output::print_kv("Difficulty", &format!("{:.6}", info.difficulty));
|
output::print_kv("Difficulty", &format!("{:.6}", info.difficulty));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use dialoguer::Password;
|
use dialoguer::Password;
|
||||||
use synor_types::{
|
use synor_types::{
|
||||||
Address, Amount, Hash256,
|
|
||||||
transaction::{Outpoint, ScriptPubKey, ScriptType, Transaction, TxInput, TxOutput},
|
transaction::{Outpoint, ScriptPubKey, ScriptType, Transaction, TxInput, TxOutput},
|
||||||
|
Address, Amount, Hash256,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::client::RpcClient;
|
use crate::client::RpcClient;
|
||||||
|
|
@ -122,7 +122,15 @@ pub async fn send(
|
||||||
|
|
||||||
// Build transaction
|
// Build transaction
|
||||||
let change = selected_amount - total_needed;
|
let change = selected_amount - total_needed;
|
||||||
let tx_hex = build_transaction(&wallet, &selected_utxos, to, amount_sompi, &from_addr.address, change, &password)?;
|
let tx_hex = build_transaction(
|
||||||
|
&wallet,
|
||||||
|
&selected_utxos,
|
||||||
|
to,
|
||||||
|
amount_sompi,
|
||||||
|
&from_addr.address,
|
||||||
|
change,
|
||||||
|
&password,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Submit transaction
|
// Submit transaction
|
||||||
let tx_hash = client.submit_transaction(&tx_hex).await?;
|
let tx_hash = client.submit_transaction(&tx_hex).await?;
|
||||||
|
|
@ -219,8 +227,8 @@ fn build_transaction(
|
||||||
password: &str,
|
password: &str,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
// Parse destination address
|
// Parse destination address
|
||||||
let to_address = Address::from_str(to)
|
let to_address =
|
||||||
.map_err(|e| anyhow::anyhow!("Invalid destination address: {}", e))?;
|
Address::from_str(to).map_err(|e| anyhow::anyhow!("Invalid destination address: {}", e))?;
|
||||||
|
|
||||||
// Parse change address
|
// Parse change address
|
||||||
let change_address = Address::from_str(change_addr)
|
let change_address = Address::from_str(change_addr)
|
||||||
|
|
@ -236,10 +244,7 @@ fn build_transaction(
|
||||||
let mut txid_array = [0u8; 32];
|
let mut txid_array = [0u8; 32];
|
||||||
txid_array.copy_from_slice(&txid_bytes);
|
txid_array.copy_from_slice(&txid_bytes);
|
||||||
|
|
||||||
let outpoint = Outpoint::new(
|
let outpoint = Outpoint::new(Hash256::from_bytes(txid_array), utxo.outpoint.index);
|
||||||
Hash256::from_bytes(txid_array),
|
|
||||||
utxo.outpoint.index,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Empty signature script initially - will be filled after signing
|
// Empty signature script initially - will be filled after signing
|
||||||
inputs.push(TxInput::new(outpoint, Vec::new()));
|
inputs.push(TxInput::new(outpoint, Vec::new()));
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,7 @@ use crate::wallet::Wallet;
|
||||||
use crate::WalletCommands;
|
use crate::WalletCommands;
|
||||||
|
|
||||||
/// Handle wallet commands.
|
/// Handle wallet commands.
|
||||||
pub async fn handle(
|
pub async fn handle(config: &CliConfig, cmd: WalletCommands, format: OutputFormat) -> Result<()> {
|
||||||
config: &CliConfig,
|
|
||||||
cmd: WalletCommands,
|
|
||||||
format: OutputFormat,
|
|
||||||
) -> Result<()> {
|
|
||||||
match cmd {
|
match cmd {
|
||||||
WalletCommands::Create { name } => create(config, &name, format).await,
|
WalletCommands::Create { name } => create(config, &name, format).await,
|
||||||
WalletCommands::Import { name } => import(config, &name, format).await,
|
WalletCommands::Import { name } => import(config, &name, format).await,
|
||||||
|
|
@ -72,7 +68,10 @@ async fn create(config: &CliConfig, name: &str, format: OutputFormat) -> Result<
|
||||||
output::print_kv("Network", &wallet.network);
|
output::print_kv("Network", &wallet.network);
|
||||||
output::print_kv(
|
output::print_kv(
|
||||||
"Address",
|
"Address",
|
||||||
wallet.default_address().map(|a| a.address.as_str()).unwrap_or("none"),
|
wallet
|
||||||
|
.default_address()
|
||||||
|
.map(|a| a.address.as_str())
|
||||||
|
.unwrap_or("none"),
|
||||||
);
|
);
|
||||||
output::print_kv("Key Type", "Hybrid (Ed25519 + Dilithium)");
|
output::print_kv("Key Type", "Hybrid (Ed25519 + Dilithium)");
|
||||||
|
|
||||||
|
|
@ -134,7 +133,10 @@ async fn import(config: &CliConfig, name: &str, format: OutputFormat) -> Result<
|
||||||
output::print_kv("Name", &wallet.name);
|
output::print_kv("Name", &wallet.name);
|
||||||
output::print_kv(
|
output::print_kv(
|
||||||
"Address",
|
"Address",
|
||||||
wallet.default_address().map(|a| a.address.as_str()).unwrap_or("none"),
|
wallet
|
||||||
|
.default_address()
|
||||||
|
.map(|a| a.address.as_str())
|
||||||
|
.unwrap_or("none"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,26 +164,26 @@ async fn export(config: &CliConfig, name: &str, format: OutputFormat) -> Result<
|
||||||
// Note: We can't export the mnemonic from the derived seed
|
// Note: We can't export the mnemonic from the derived seed
|
||||||
// The user should have written down the mnemonic during creation
|
// The user should have written down the mnemonic during creation
|
||||||
match wallet.export_seed_phrase(&password) {
|
match wallet.export_seed_phrase(&password) {
|
||||||
Ok(seed_phrase) => {
|
Ok(seed_phrase) => match format {
|
||||||
match format {
|
OutputFormat::Json => {
|
||||||
OutputFormat::Json => {
|
let result = serde_json::json!({
|
||||||
let result = serde_json::json!({
|
"name": wallet.name,
|
||||||
"name": wallet.name,
|
"seed_phrase": seed_phrase,
|
||||||
"seed_phrase": seed_phrase,
|
});
|
||||||
});
|
println!("{}", serde_json::to_string_pretty(&result)?);
|
||||||
println!("{}", serde_json::to_string_pretty(&result)?);
|
|
||||||
}
|
|
||||||
OutputFormat::Text => {
|
|
||||||
output::print_warning("Keep this seed phrase secret and safe!");
|
|
||||||
println!();
|
|
||||||
println!(" {}", seed_phrase);
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
OutputFormat::Text => {
|
||||||
|
output::print_warning("Keep this seed phrase secret and safe!");
|
||||||
|
println!();
|
||||||
|
println!(" {}", seed_phrase);
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
output::print_warning(&format!("{}", e));
|
output::print_warning(&format!("{}", e));
|
||||||
output::print_info("Please use the mnemonic phrase you wrote down during wallet creation.");
|
output::print_info(
|
||||||
|
"Please use the mnemonic phrase you wrote down during wallet creation.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,7 +237,10 @@ async fn info(config: &CliConfig, name: &str, format: OutputFormat) -> Result<()
|
||||||
output::print_kv("Network", &wallet.network);
|
output::print_kv("Network", &wallet.network);
|
||||||
output::print_kv("Key Type", "Hybrid (Ed25519 + Dilithium)");
|
output::print_kv("Key Type", "Hybrid (Ed25519 + Dilithium)");
|
||||||
output::print_kv("Addresses", &wallet.addresses.len().to_string());
|
output::print_kv("Addresses", &wallet.addresses.len().to_string());
|
||||||
output::print_kv("Created", &output::format_timestamp(wallet.created_at * 1000));
|
output::print_kv(
|
||||||
|
"Created",
|
||||||
|
&output::format_timestamp(wallet.created_at * 1000),
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(default) = wallet.default_address() {
|
if let Some(default) = wallet.default_address() {
|
||||||
output::print_kv("Default Address", &default.address);
|
output::print_kv("Default Address", &default.address);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,12 @@ use crate::config::CliConfig;
|
||||||
#[command(version, about = "Synor blockchain CLI", long_about = None)]
|
#[command(version, about = "Synor blockchain CLI", long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
/// RPC server URL
|
/// RPC server URL
|
||||||
#[arg(short, long, env = "SYNOR_RPC_URL", default_value = "http://127.0.0.1:16110")]
|
#[arg(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
env = "SYNOR_RPC_URL",
|
||||||
|
default_value = "http://127.0.0.1:16110"
|
||||||
|
)]
|
||||||
rpc: String,
|
rpc: String,
|
||||||
|
|
||||||
/// Configuration file path
|
/// Configuration file path
|
||||||
|
|
@ -472,39 +477,29 @@ async fn main() {
|
||||||
Commands::Balance { address } => {
|
Commands::Balance { address } => {
|
||||||
commands::wallet::balance(&client, &config, address.as_deref(), output).await
|
commands::wallet::balance(&client, &config, address.as_deref(), output).await
|
||||||
}
|
}
|
||||||
Commands::Utxos { address } => {
|
Commands::Utxos { address } => commands::wallet::utxos(&client, &address, output).await,
|
||||||
commands::wallet::utxos(&client, &address, output).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address commands
|
// Address commands
|
||||||
Commands::ValidateAddress { address } => {
|
Commands::ValidateAddress { address } => {
|
||||||
commands::address::validate(&address, output).await
|
commands::address::validate(&address, output).await
|
||||||
}
|
}
|
||||||
Commands::DecodeAddress { address } => {
|
Commands::DecodeAddress { address } => commands::address::decode(&address, output).await,
|
||||||
commands::address::decode(&address, output).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mining commands
|
// Mining commands
|
||||||
Commands::Mining(cmd) => commands::mining::handle(&client, cmd, output).await,
|
Commands::Mining(cmd) => commands::mining::handle(&client, cmd, output).await,
|
||||||
|
|
||||||
// Contract commands
|
// Contract commands
|
||||||
Commands::Contract(cmd) => {
|
Commands::Contract(cmd) => commands::contract::handle(&client, &config, cmd, output).await,
|
||||||
commands::contract::handle(&client, &config, cmd, output).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Governance commands
|
// Governance commands
|
||||||
Commands::Governance(cmd) => {
|
Commands::Governance(cmd) => commands::governance::handle(&client, cmd, output).await,
|
||||||
commands::governance::handle(&client, cmd, output).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Network commands
|
// Network commands
|
||||||
Commands::AddPeer { address } => {
|
Commands::AddPeer { address } => {
|
||||||
commands::network::add_peer(&client, &address, output).await
|
commands::network::add_peer(&client, &address, output).await
|
||||||
}
|
}
|
||||||
Commands::BanPeer { peer } => commands::network::ban_peer(&client, &peer, output).await,
|
Commands::BanPeer { peer } => commands::network::ban_peer(&client, &peer, output).await,
|
||||||
Commands::UnbanPeer { peer } => {
|
Commands::UnbanPeer { peer } => commands::network::unban_peer(&client, &peer, output).await,
|
||||||
commands::network::unban_peer(&client, &peer, output).await
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,11 @@ impl Wallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a new address.
|
/// Generates a new address.
|
||||||
pub fn new_address(&mut self, label: Option<String>, password: &str) -> anyhow::Result<&WalletAddress> {
|
pub fn new_address(
|
||||||
|
&mut self,
|
||||||
|
label: Option<String>,
|
||||||
|
password: &str,
|
||||||
|
) -> anyhow::Result<&WalletAddress> {
|
||||||
let seed = self
|
let seed = self
|
||||||
.encrypted_seed
|
.encrypted_seed
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
||||||
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::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use axum::http::{HeaderValue, Method};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, Query, State},
|
extract::{Path, Query, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
|
@ -21,10 +22,9 @@ use axum::{
|
||||||
};
|
};
|
||||||
use moka::future::Cache;
|
use moka::future::Cache;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
|
||||||
use axum::http::{HeaderValue, Method};
|
|
||||||
use tower_http::trace::TraceLayer;
|
|
||||||
use tower_http::compression::CompressionLayer;
|
use tower_http::compression::CompressionLayer;
|
||||||
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
// ==================== Configuration ====================
|
// ==================== Configuration ====================
|
||||||
|
|
@ -297,8 +297,12 @@ pub struct PaginationParams {
|
||||||
pub limit: usize,
|
pub limit: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_page() -> usize { 1 }
|
fn default_page() -> usize {
|
||||||
fn default_limit() -> usize { 25 }
|
1
|
||||||
|
}
|
||||||
|
fn default_limit() -> usize {
|
||||||
|
25
|
||||||
|
}
|
||||||
|
|
||||||
/// Paginated response.
|
/// Paginated response.
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
|
@ -435,14 +439,19 @@ async fn health(State(state): State<Arc<ExplorerState>>) -> impl IntoResponse {
|
||||||
StatusCode::SERVICE_UNAVAILABLE
|
StatusCode::SERVICE_UNAVAILABLE
|
||||||
};
|
};
|
||||||
|
|
||||||
(status, Json(Health {
|
(
|
||||||
healthy: rpc_ok,
|
status,
|
||||||
rpc_connected: rpc_ok,
|
Json(Health {
|
||||||
}))
|
healthy: rpc_ok,
|
||||||
|
rpc_connected: rpc_ok,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get network statistics.
|
/// Get network statistics.
|
||||||
async fn get_stats(State(state): State<Arc<ExplorerState>>) -> Result<Json<NetworkStats>, ApiError> {
|
async fn get_stats(
|
||||||
|
State(state): State<Arc<ExplorerState>>,
|
||||||
|
) -> Result<Json<NetworkStats>, ApiError> {
|
||||||
// Check cache first
|
// Check cache first
|
||||||
if let Some(stats) = state.stats_cache.get("network_stats").await {
|
if let Some(stats) = state.stats_cache.get("network_stats").await {
|
||||||
return Ok(Json(stats));
|
return Ok(Json(stats));
|
||||||
|
|
@ -532,7 +541,10 @@ async fn get_stats(State(state): State<Arc<ExplorerState>>) -> Result<Json<Netwo
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cache the result
|
// Cache the result
|
||||||
state.stats_cache.insert("network_stats".to_string(), stats.clone()).await;
|
state
|
||||||
|
.stats_cache
|
||||||
|
.insert("network_stats".to_string(), stats.clone())
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(Json(stats))
|
Ok(Json(stats))
|
||||||
}
|
}
|
||||||
|
|
@ -559,7 +571,13 @@ async fn get_block(
|
||||||
}
|
}
|
||||||
|
|
||||||
let rpc_block: synor_rpc::RpcBlock = state
|
let rpc_block: synor_rpc::RpcBlock = state
|
||||||
.rpc_call("synor_getBlock", GetBlockParams { hash: hash.clone(), include_txs })
|
.rpc_call(
|
||||||
|
"synor_getBlock",
|
||||||
|
GetBlockParams {
|
||||||
|
hash: hash.clone(),
|
||||||
|
include_txs,
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let block = convert_rpc_block(rpc_block);
|
let block = convert_rpc_block(rpc_block);
|
||||||
|
|
@ -634,7 +652,7 @@ async fn get_blocks(
|
||||||
daa_score: h.daa_score,
|
daa_score: h.daa_score,
|
||||||
blue_score: h.blue_score,
|
blue_score: h.blue_score,
|
||||||
blue_work: h.blue_work,
|
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
|
transaction_count: 0, // Unknown without fetching full block
|
||||||
is_chain_block: true, // Assume chain block for headers
|
is_chain_block: true, // Assume chain block for headers
|
||||||
transactions: None,
|
transactions: None,
|
||||||
|
|
@ -710,9 +728,12 @@ async fn get_address(
|
||||||
}
|
}
|
||||||
|
|
||||||
let utxos: Vec<synor_rpc::RpcUtxo> = state
|
let utxos: Vec<synor_rpc::RpcUtxo> = state
|
||||||
.rpc_call("synor_getUtxosByAddresses", GetUtxosParams {
|
.rpc_call(
|
||||||
addresses: vec![address.clone()],
|
"synor_getUtxosByAddresses",
|
||||||
})
|
GetUtxosParams {
|
||||||
|
addresses: vec![address.clone()],
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Get balance
|
// Get balance
|
||||||
|
|
@ -727,9 +748,12 @@ async fn get_address(
|
||||||
}
|
}
|
||||||
|
|
||||||
let balance: BalanceResult = state
|
let balance: BalanceResult = state
|
||||||
.rpc_call("synor_getBalanceByAddress", GetBalanceParams {
|
.rpc_call(
|
||||||
address: address.clone(),
|
"synor_getBalanceByAddress",
|
||||||
})
|
GetBalanceParams {
|
||||||
|
address: address.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let info = AddressInfo {
|
let info = AddressInfo {
|
||||||
|
|
@ -737,8 +761,8 @@ async fn get_address(
|
||||||
balance: balance.balance,
|
balance: balance.balance,
|
||||||
balance_human: format_synor(balance.balance),
|
balance_human: format_synor(balance.balance),
|
||||||
utxo_count: utxos.len(),
|
utxo_count: utxos.len(),
|
||||||
total_received: 0, // Would need historical data
|
total_received: 0, // Would need historical data
|
||||||
total_sent: 0, // Would need historical data
|
total_sent: 0, // Would need historical data
|
||||||
transaction_count: 0, // Would need indexing
|
transaction_count: 0, // Would need indexing
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -756,9 +780,12 @@ async fn get_address_utxos(
|
||||||
}
|
}
|
||||||
|
|
||||||
let utxos: Vec<synor_rpc::RpcUtxo> = state
|
let utxos: Vec<synor_rpc::RpcUtxo> = state
|
||||||
.rpc_call("synor_getUtxosByAddresses", GetUtxosParams {
|
.rpc_call(
|
||||||
addresses: vec![address],
|
"synor_getUtxosByAddresses",
|
||||||
})
|
GetUtxosParams {
|
||||||
|
addresses: vec![address],
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Json(utxos))
|
Ok(Json(utxos))
|
||||||
|
|
@ -820,7 +847,7 @@ async fn get_dag(
|
||||||
hash: header.hash.clone(),
|
hash: header.hash.clone(),
|
||||||
short_hash: header.hash.chars().take(8).collect(),
|
short_hash: header.hash.chars().take(8).collect(),
|
||||||
blue_score: header.blue_score,
|
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
|
is_chain_block: true, // Would need verbose data
|
||||||
timestamp: header.timestamp,
|
timestamp: header.timestamp,
|
||||||
tx_count: 0, // Unknown from header
|
tx_count: 0, // Unknown from header
|
||||||
|
|
@ -852,10 +879,13 @@ async fn get_mempool(
|
||||||
}
|
}
|
||||||
|
|
||||||
let entries: Vec<synor_rpc::RpcMempoolEntry> = state
|
let entries: Vec<synor_rpc::RpcMempoolEntry> = state
|
||||||
.rpc_call("synor_getMempoolEntries", GetMempoolParams {
|
.rpc_call(
|
||||||
include_orphan_pool: false,
|
"synor_getMempoolEntries",
|
||||||
filter_tx_in_addresses: false,
|
GetMempoolParams {
|
||||||
})
|
include_orphan_pool: false,
|
||||||
|
filter_tx_in_addresses: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let total = entries.len();
|
let total = entries.len();
|
||||||
|
|
@ -912,10 +942,13 @@ async fn search(
|
||||||
}
|
}
|
||||||
|
|
||||||
let block_result: Result<synor_rpc::RpcBlock, _> = state
|
let block_result: Result<synor_rpc::RpcBlock, _> = state
|
||||||
.rpc_call("synor_getBlock", GetBlockParams {
|
.rpc_call(
|
||||||
hash: query.to_string(),
|
"synor_getBlock",
|
||||||
include_txs: false,
|
GetBlockParams {
|
||||||
})
|
hash: query.to_string(),
|
||||||
|
include_txs: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if block_result.is_ok() {
|
if block_result.is_ok() {
|
||||||
|
|
@ -933,9 +966,12 @@ async fn search(
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx_result: Result<synor_rpc::RpcTransaction, _> = state
|
let tx_result: Result<synor_rpc::RpcTransaction, _> = state
|
||||||
.rpc_call("synor_getTransaction", GetTxParams {
|
.rpc_call(
|
||||||
tx_id: query.to_string(),
|
"synor_getTransaction",
|
||||||
})
|
GetTxParams {
|
||||||
|
tx_id: query.to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if tx_result.is_ok() {
|
if tx_result.is_ok() {
|
||||||
|
|
@ -984,9 +1020,15 @@ fn convert_rpc_block(rpc: synor_rpc::RpcBlock) -> ExplorerBlock {
|
||||||
.map(convert_rpc_transaction)
|
.map(convert_rpc_transaction)
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
children_hashes: verbose.map(|v| v.children_hashes.clone()).unwrap_or_default(),
|
children_hashes: verbose
|
||||||
merge_set_blues: verbose.map(|v| v.merge_set_blues_hashes.clone()).unwrap_or_default(),
|
.map(|v| v.children_hashes.clone())
|
||||||
merge_set_reds: verbose.map(|v| v.merge_set_reds_hashes.clone()).unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
merge_set_blues: verbose
|
||||||
|
.map(|v| v.merge_set_blues_hashes.clone())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
merge_set_reds: verbose
|
||||||
|
.map(|v| v.merge_set_reds_hashes.clone())
|
||||||
|
.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -995,7 +1037,11 @@ fn convert_rpc_transaction(rpc: synor_rpc::RpcTransaction) -> ExplorerTransactio
|
||||||
let verbose = rpc.verbose_data.as_ref();
|
let verbose = rpc.verbose_data.as_ref();
|
||||||
|
|
||||||
let is_coinbase = rpc.inputs.is_empty()
|
let is_coinbase = rpc.inputs.is_empty()
|
||||||
|| rpc.inputs.first().map(|i| i.previous_outpoint.transaction_id.chars().all(|c| c == '0')).unwrap_or(false);
|
|| rpc
|
||||||
|
.inputs
|
||||||
|
.first()
|
||||||
|
.map(|i| i.previous_outpoint.transaction_id.chars().all(|c| c == '0'))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
let total_output: u64 = rpc.outputs.iter().map(|o| o.value).sum();
|
let total_output: u64 = rpc.outputs.iter().map(|o| o.value).sum();
|
||||||
let total_input: u64 = rpc
|
let total_input: u64 = rpc
|
||||||
|
|
@ -1004,10 +1050,17 @@ fn convert_rpc_transaction(rpc: synor_rpc::RpcTransaction) -> ExplorerTransactio
|
||||||
.filter_map(|i| i.verbose_data.as_ref().map(|v| v.value))
|
.filter_map(|i| i.verbose_data.as_ref().map(|v| v.value))
|
||||||
.sum();
|
.sum();
|
||||||
|
|
||||||
let fee = if is_coinbase { 0 } else { total_input.saturating_sub(total_output) };
|
let fee = if is_coinbase {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
total_input.saturating_sub(total_output)
|
||||||
|
};
|
||||||
|
|
||||||
ExplorerTransaction {
|
ExplorerTransaction {
|
||||||
id: verbose.as_ref().map(|v| v.transaction_id.clone()).unwrap_or_default(),
|
id: verbose
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.transaction_id.clone())
|
||||||
|
.unwrap_or_default(),
|
||||||
hash: verbose.as_ref().map(|v| v.hash.clone()).unwrap_or_default(),
|
hash: verbose.as_ref().map(|v| v.hash.clone()).unwrap_or_default(),
|
||||||
version: rpc.version,
|
version: rpc.version,
|
||||||
inputs: rpc
|
inputs: rpc
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use axum::http::{HeaderValue, Method};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{ConnectInfo, State},
|
extract::{ConnectInfo, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
|
@ -15,11 +16,10 @@ use axum::{
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use governor::{Quota, RateLimiter, state::keyed::DashMapStateStore};
|
use governor::{state::keyed::DashMapStateStore, Quota, RateLimiter};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use axum::http::{HeaderValue, Method};
|
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ impl Default for FaucetConfig {
|
||||||
FaucetConfig {
|
FaucetConfig {
|
||||||
rpc_url: "http://localhost:17110".to_string(),
|
rpc_url: "http://localhost:17110".to_string(),
|
||||||
dispense_amount: 10_00000000, // 10 SYNOR
|
dispense_amount: 10_00000000, // 10 SYNOR
|
||||||
cooldown_seconds: 3600, // 1 hour
|
cooldown_seconds: 3600, // 1 hour
|
||||||
rate_limit_per_minute: 10,
|
rate_limit_per_minute: 10,
|
||||||
listen_addr: "0.0.0.0:8080".parse().unwrap(),
|
listen_addr: "0.0.0.0:8080".parse().unwrap(),
|
||||||
wallet_key: None,
|
wallet_key: None,
|
||||||
|
|
@ -210,7 +210,8 @@ async fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
// Create rate limiter (using NonZeroU32 for quota)
|
// Create rate limiter (using NonZeroU32 for quota)
|
||||||
let quota = Quota::per_minute(
|
let quota = Quota::per_minute(
|
||||||
std::num::NonZeroU32::new(config.rate_limit_per_minute).unwrap_or(std::num::NonZeroU32::new(10).unwrap())
|
std::num::NonZeroU32::new(config.rate_limit_per_minute)
|
||||||
|
.unwrap_or(std::num::NonZeroU32::new(10).unwrap()),
|
||||||
);
|
);
|
||||||
let rate_limiter = RateLimiter::keyed(quota);
|
let rate_limiter = RateLimiter::keyed(quota);
|
||||||
|
|
||||||
|
|
@ -639,10 +640,7 @@ async fn send_tokens(state: &FaucetState, address: &str) -> anyhow::Result<Optio
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
// For testnet demo, simulate success
|
// For testnet demo, simulate success
|
||||||
// In production, this would be a real error
|
// In production, this would be a real error
|
||||||
return Ok(Some(format!(
|
return Ok(Some(format!("0x{}", hex::encode(&rand_bytes()))));
|
||||||
"0x{}",
|
|
||||||
hex::encode(&rand_bytes())
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let rpc_response: RpcResponse = response.json().await?;
|
let rpc_response: RpcResponse = response.json().await?;
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,11 @@ pub fn confirm(prompt: &str) -> bool {
|
||||||
/// Formats a hash for display.
|
/// Formats a hash for display.
|
||||||
pub fn format_hash(hash: &[u8]) -> String {
|
pub fn format_hash(hash: &[u8]) -> String {
|
||||||
if hash.len() >= 8 {
|
if hash.len() >= 8 {
|
||||||
format!("{}...{}", hex::encode(&hash[..4]), hex::encode(&hash[hash.len() - 4..]))
|
format!(
|
||||||
|
"{}...{}",
|
||||||
|
hex::encode(&hash[..4]),
|
||||||
|
hex::encode(&hash[hash.len() - 4..])
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
hex::encode(hash)
|
hex::encode(hash)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,12 +115,7 @@ impl NodeConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets mining configuration.
|
/// Sets mining configuration.
|
||||||
pub fn with_mining(
|
pub fn with_mining(mut self, enabled: bool, coinbase: Option<String>, threads: usize) -> Self {
|
||||||
mut self,
|
|
||||||
enabled: bool,
|
|
||||||
coinbase: Option<String>,
|
|
||||||
threads: usize,
|
|
||||||
) -> Self {
|
|
||||||
if enabled {
|
if enabled {
|
||||||
self.mining.enabled = true;
|
self.mining.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
@ -448,7 +443,7 @@ impl Default for ConsensusConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ConsensusConfig {
|
ConsensusConfig {
|
||||||
ghostdag_k: 18,
|
ghostdag_k: 18,
|
||||||
merge_depth: 3600, // ~1 hour
|
merge_depth: 3600, // ~1 hour
|
||||||
finality_depth: 86400, // ~24 hours
|
finality_depth: 86400, // ~24 hours
|
||||||
target_time_ms: 1000,
|
target_time_ms: 1000,
|
||||||
difficulty_window: 2641,
|
difficulty_window: 2641,
|
||||||
|
|
|
||||||
|
|
@ -210,19 +210,14 @@ async fn main() {
|
||||||
fn init_logging(level: &str, json: bool) {
|
fn init_logging(level: &str, json: bool) {
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
|
|
||||||
let filter = EnvFilter::try_from_default_env()
|
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level));
|
||||||
.unwrap_or_else(|_| EnvFilter::new(level));
|
|
||||||
|
|
||||||
let subscriber = tracing_subscriber::registry().with(filter);
|
let subscriber = tracing_subscriber::registry().with(filter);
|
||||||
|
|
||||||
if json {
|
if json {
|
||||||
subscriber
|
subscriber.with(fmt::layer().json()).init();
|
||||||
.with(fmt::layer().json())
|
|
||||||
.init();
|
|
||||||
} else {
|
} else {
|
||||||
subscriber
|
subscriber.with(fmt::layer().with_target(true)).init();
|
||||||
.with(fmt::layer().with_target(true))
|
|
||||||
.init();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,11 +272,7 @@ async fn run_node(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a new node with genesis block.
|
/// Initialize a new node with genesis block.
|
||||||
async fn init_node(
|
async fn init_node(data_dir: Option<PathBuf>, network: String, force: bool) -> anyhow::Result<()> {
|
||||||
data_dir: Option<PathBuf>,
|
|
||||||
network: String,
|
|
||||||
force: bool,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
use synor_consensus::genesis::ChainConfig;
|
use synor_consensus::genesis::ChainConfig;
|
||||||
use synor_storage::{BlockBody, ChainState};
|
use synor_storage::{BlockBody, ChainState};
|
||||||
use synor_types::{BlockId, Network};
|
use synor_types::{BlockId, Network};
|
||||||
|
|
@ -302,7 +293,10 @@ async fn init_node(
|
||||||
"mainnet" => Network::Mainnet,
|
"mainnet" => Network::Mainnet,
|
||||||
"testnet" => Network::Testnet,
|
"testnet" => Network::Testnet,
|
||||||
"devnet" => Network::Devnet,
|
"devnet" => Network::Devnet,
|
||||||
_ => anyhow::bail!("Unknown network: {}. Use mainnet, testnet, or devnet.", network),
|
_ => anyhow::bail!(
|
||||||
|
"Unknown network: {}. Use mainnet, testnet, or devnet.",
|
||||||
|
network
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(network = %network, "Initializing node...");
|
info!(network = %network, "Initializing node...");
|
||||||
|
|
@ -323,8 +317,7 @@ async fn init_node(
|
||||||
std::fs::create_dir_all(data_dir.join("keys"))?;
|
std::fs::create_dir_all(data_dir.join("keys"))?;
|
||||||
|
|
||||||
// Create and save node config
|
// Create and save node config
|
||||||
let config = NodeConfig::for_network(&network)?
|
let config = NodeConfig::for_network(&network)?.with_data_dir(Some(data_dir.clone()));
|
||||||
.with_data_dir(Some(data_dir.clone()));
|
|
||||||
let config_path = data_dir.join("synord.toml");
|
let config_path = data_dir.join("synord.toml");
|
||||||
config.save(&config_path)?;
|
config.save(&config_path)?;
|
||||||
|
|
||||||
|
|
@ -343,7 +336,10 @@ async fn init_node(
|
||||||
// Store genesis block body
|
// Store genesis block body
|
||||||
let genesis_hash = chain_config.genesis_hash;
|
let genesis_hash = chain_config.genesis_hash;
|
||||||
let body = BlockBody {
|
let body = BlockBody {
|
||||||
transaction_ids: chain_config.genesis.body.transactions
|
transaction_ids: chain_config
|
||||||
|
.genesis
|
||||||
|
.body
|
||||||
|
.transactions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tx| tx.txid())
|
.map(|tx| tx.txid())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
@ -401,10 +397,19 @@ async fn init_node(
|
||||||
println!(" Genesis: {}", hex::encode(genesis_hash.as_bytes()));
|
println!(" Genesis: {}", hex::encode(genesis_hash.as_bytes()));
|
||||||
println!();
|
println!();
|
||||||
println!("Chain parameters:");
|
println!("Chain parameters:");
|
||||||
println!(" Block time: {} ms", chain_config.target_block_time_ms);
|
println!(
|
||||||
|
" Block time: {} ms",
|
||||||
|
chain_config.target_block_time_ms
|
||||||
|
);
|
||||||
println!(" GHOSTDAG K: {}", chain_config.ghostdag_k);
|
println!(" GHOSTDAG K: {}", chain_config.ghostdag_k);
|
||||||
println!(" Initial reward: {} SYNOR", chain_config.initial_reward / 100_000_000);
|
println!(
|
||||||
println!(" Halving interval: {} blocks", chain_config.halving_interval);
|
" Initial reward: {} SYNOR",
|
||||||
|
chain_config.initial_reward / 100_000_000
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Halving interval: {} blocks",
|
||||||
|
chain_config.halving_interval
|
||||||
|
);
|
||||||
println!();
|
println!();
|
||||||
println!("To start the node:");
|
println!("To start the node:");
|
||||||
println!(" synord run --network {}", network);
|
println!(" synord run --network {}", network);
|
||||||
|
|
@ -481,7 +486,10 @@ async fn import_blocks(
|
||||||
|
|
||||||
// Store the block
|
// Store the block
|
||||||
if let Err(e) = storage.put_block(&block_data).await {
|
if let Err(e) = storage.put_block(&block_data).await {
|
||||||
error!(hash = hex::encode(&block_data.hash[..8]), "Failed to store block: {}", e);
|
error!(
|
||||||
|
hash = hex::encode(&block_data.hash[..8]),
|
||||||
|
"Failed to store block: {}", e
|
||||||
|
);
|
||||||
errors += 1;
|
errors += 1;
|
||||||
} else {
|
} else {
|
||||||
imported += 1;
|
imported += 1;
|
||||||
|
|
@ -656,7 +664,9 @@ async fn wait_for_shutdown() {
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
tokio::signal::ctrl_c().await.expect("Failed to listen for Ctrl+C");
|
tokio::signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("Failed to listen for Ctrl+C");
|
||||||
info!("Received Ctrl+C");
|
info!("Received Ctrl+C");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,10 +207,7 @@ impl SynorNode {
|
||||||
// Start miner if enabled
|
// Start miner if enabled
|
||||||
if let Some(ref miner) = self.miner {
|
if let Some(ref miner) = self.miner {
|
||||||
miner.start().await?;
|
miner.start().await?;
|
||||||
info!(
|
info!(threads = self.config.mining.threads, "Miner started");
|
||||||
threads = self.config.mining.threads,
|
|
||||||
"Miner started"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ use tokio::sync::{broadcast, RwLock};
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use synor_consensus::{
|
use synor_consensus::{
|
||||||
BlockValidator, DaaParams, DifficultyManager, RewardCalculator,
|
BlockValidator, DaaParams, DifficultyManager, RewardCalculator, TransactionValidator, UtxoSet,
|
||||||
TransactionValidator, UtxoSet, ValidationError,
|
ValidationError,
|
||||||
};
|
};
|
||||||
use synor_types::{
|
use synor_types::{
|
||||||
block::{Block, BlockHeader},
|
block::{Block, BlockHeader},
|
||||||
|
|
@ -288,10 +288,15 @@ impl ConsensusService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate expected reward
|
// Calculate expected reward
|
||||||
let expected_reward = self.reward_calculator.calculate_subsidy(block.header.daa_score);
|
let expected_reward = self
|
||||||
|
.reward_calculator
|
||||||
|
.calculate_subsidy(block.header.daa_score);
|
||||||
|
|
||||||
// Validate the full block (including transactions)
|
// Validate the full block (including transactions)
|
||||||
if let Err(e) = self.block_validator.validate_block(block, &self.utxo_set, expected_reward) {
|
if let Err(e) = self
|
||||||
|
.block_validator
|
||||||
|
.validate_block(block, &self.utxo_set, expected_reward)
|
||||||
|
{
|
||||||
return BlockValidation::Invalid {
|
return BlockValidation::Invalid {
|
||||||
reason: format!("Invalid block: {}", e),
|
reason: format!("Invalid block: {}", e),
|
||||||
};
|
};
|
||||||
|
|
@ -442,7 +447,10 @@ impl ConsensusService {
|
||||||
// For non-coinbase transactions, validate against UTXO set
|
// For non-coinbase transactions, validate against UTXO set
|
||||||
if !tx.is_coinbase() {
|
if !tx.is_coinbase() {
|
||||||
let current_daa = *self.daa_score.read().await;
|
let current_daa = *self.daa_score.read().await;
|
||||||
if let Err(e) = self.tx_validator.validate_against_utxos(tx, &self.utxo_set, current_daa) {
|
if let Err(e) =
|
||||||
|
self.tx_validator
|
||||||
|
.validate_against_utxos(tx, &self.utxo_set, current_daa)
|
||||||
|
{
|
||||||
// Check if this is a double-spend conflict
|
// Check if this is a double-spend conflict
|
||||||
if matches!(e, ValidationError::UtxoNotFound(_)) {
|
if matches!(e, ValidationError::UtxoNotFound(_)) {
|
||||||
return TxValidation::Conflict;
|
return TxValidation::Conflict;
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,8 @@ impl ContractService {
|
||||||
call_data.extend_from_slice(&args);
|
call_data.extend_from_slice(&args);
|
||||||
|
|
||||||
// Create execution context
|
// Create execution context
|
||||||
let call_context = CallContext::new(vm_contract_id, caller.clone(), value, call_data.clone());
|
let call_context =
|
||||||
|
CallContext::new(vm_contract_id, caller.clone(), value, call_data.clone());
|
||||||
|
|
||||||
let context = ExecutionContext::new(
|
let context = ExecutionContext::new(
|
||||||
synor_vm::context::BlockInfo {
|
synor_vm::context::BlockInfo {
|
||||||
|
|
@ -353,7 +354,10 @@ impl ContractService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets contract metadata.
|
/// Gets contract metadata.
|
||||||
pub async fn get_contract(&self, contract_id: &[u8; 32]) -> anyhow::Result<Option<StoredContract>> {
|
pub async fn get_contract(
|
||||||
|
&self,
|
||||||
|
contract_id: &[u8; 32],
|
||||||
|
) -> anyhow::Result<Option<StoredContract>> {
|
||||||
let store = self.contract_store.read().await;
|
let store = self.contract_store.read().await;
|
||||||
let store = store
|
let store = store
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ use tokio::sync::{broadcast, RwLock};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use synor_governance::{
|
use synor_governance::{
|
||||||
DaoStats, GovernanceConfig, Proposal, ProposalId, ProposalState, ProposalSummary,
|
DaoStats, GovernanceConfig, Proposal, ProposalId, ProposalState, ProposalSummary, ProposalType,
|
||||||
ProposalType, Treasury, TreasuryPoolId, VoteChoice, VotingConfig, DAO,
|
Treasury, TreasuryPoolId, VoteChoice, VotingConfig, DAO,
|
||||||
};
|
};
|
||||||
use synor_types::Address;
|
use synor_types::Address;
|
||||||
|
|
||||||
|
|
@ -247,7 +247,10 @@ impl GovernanceService {
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_block = state.current_block;
|
let current_block = state.current_block;
|
||||||
let proposal = state.dao.execute(proposal_id, executor, current_block)?.clone();
|
let proposal = state
|
||||||
|
.dao
|
||||||
|
.execute(proposal_id, executor, current_block)?
|
||||||
|
.clone();
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
proposal_id = %hex::encode(proposal_id.as_bytes()),
|
proposal_id = %hex::encode(proposal_id.as_bytes()),
|
||||||
|
|
@ -256,7 +259,8 @@ impl GovernanceService {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle proposal execution based on type
|
// Handle proposal execution based on type
|
||||||
self.handle_proposal_execution(&proposal, &mut state).await?;
|
self.handle_proposal_execution(&proposal, &mut state)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(proposal)
|
Ok(proposal)
|
||||||
}
|
}
|
||||||
|
|
@ -268,7 +272,11 @@ impl GovernanceService {
|
||||||
state: &mut GovernanceState,
|
state: &mut GovernanceState,
|
||||||
) -> Result<(), GovernanceError> {
|
) -> Result<(), GovernanceError> {
|
||||||
match &proposal.proposal_type {
|
match &proposal.proposal_type {
|
||||||
ProposalType::TreasurySpend { recipient, amount, reason } => {
|
ProposalType::TreasurySpend {
|
||||||
|
recipient,
|
||||||
|
amount,
|
||||||
|
reason,
|
||||||
|
} => {
|
||||||
info!(
|
info!(
|
||||||
recipient = %recipient,
|
recipient = %recipient,
|
||||||
amount = amount,
|
amount = amount,
|
||||||
|
|
@ -278,7 +286,11 @@ impl GovernanceService {
|
||||||
// In production, this would create a spending request
|
// In production, this would create a spending request
|
||||||
// For now, we log the action
|
// For now, we log the action
|
||||||
}
|
}
|
||||||
ProposalType::ParameterChange { parameter, old_value, new_value } => {
|
ProposalType::ParameterChange {
|
||||||
|
parameter,
|
||||||
|
old_value,
|
||||||
|
new_value,
|
||||||
|
} => {
|
||||||
info!(
|
info!(
|
||||||
parameter = %parameter,
|
parameter = %parameter,
|
||||||
old_value = %old_value,
|
old_value = %old_value,
|
||||||
|
|
@ -287,7 +299,11 @@ impl GovernanceService {
|
||||||
);
|
);
|
||||||
// In production, this would update chain parameters
|
// In production, this would update chain parameters
|
||||||
}
|
}
|
||||||
ProposalType::CouncilChange { action, member, role } => {
|
ProposalType::CouncilChange {
|
||||||
|
action,
|
||||||
|
member,
|
||||||
|
role,
|
||||||
|
} => {
|
||||||
info!(
|
info!(
|
||||||
action = ?action,
|
action = ?action,
|
||||||
member = %member,
|
member = %member,
|
||||||
|
|
@ -341,7 +357,10 @@ impl GovernanceService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a proposal by ID.
|
/// Gets a proposal by ID.
|
||||||
pub async fn get_proposal(&self, proposal_id: &ProposalId) -> Result<Proposal, GovernanceError> {
|
pub async fn get_proposal(
|
||||||
|
&self,
|
||||||
|
proposal_id: &ProposalId,
|
||||||
|
) -> Result<Proposal, GovernanceError> {
|
||||||
let state = self.state.read().await;
|
let state = self.state.read().await;
|
||||||
|
|
||||||
if !state.initialized {
|
if !state.initialized {
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,8 @@ impl MempoolService {
|
||||||
let mut block_rx = self.consensus.subscribe_blocks();
|
let mut block_rx = self.consensus.subscribe_blocks();
|
||||||
|
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
let mut cleanup_interval = tokio::time::interval(Duration::from_secs(CLEANUP_INTERVAL_SECS));
|
let mut cleanup_interval =
|
||||||
|
tokio::time::interval(Duration::from_secs(CLEANUP_INTERVAL_SECS));
|
||||||
cleanup_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
cleanup_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||||
|
|
||||||
info!("Mempool cleanup task started");
|
info!("Mempool cleanup task started");
|
||||||
|
|
@ -232,7 +233,10 @@ impl MempoolService {
|
||||||
*current_size += tx_size;
|
*current_size += tx_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(hash = hex::encode(&hash[..8]), "Transaction added to mempool");
|
debug!(
|
||||||
|
hash = hex::encode(&hash[..8]),
|
||||||
|
"Transaction added to mempool"
|
||||||
|
);
|
||||||
let _ = self.tx_added.send(hash);
|
let _ = self.tx_added.send(hash);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -245,7 +249,10 @@ impl MempoolService {
|
||||||
let mut current_size = self.current_size.write().await;
|
let mut current_size = self.current_size.write().await;
|
||||||
*current_size = current_size.saturating_sub(tx.data.len());
|
*current_size = current_size.saturating_sub(tx.data.len());
|
||||||
|
|
||||||
debug!(hash = hex::encode(&hash[..8]), "Transaction removed from mempool");
|
debug!(
|
||||||
|
hash = hex::encode(&hash[..8]),
|
||||||
|
"Transaction removed from mempool"
|
||||||
|
);
|
||||||
let _ = self.tx_removed.send(*hash);
|
let _ = self.tx_removed.send(*hash);
|
||||||
|
|
||||||
Some(tx)
|
Some(tx)
|
||||||
|
|
@ -285,7 +292,11 @@ impl MempoolService {
|
||||||
|
|
||||||
// Sort by fee rate descending
|
// Sort by fee rate descending
|
||||||
let mut sorted: Vec<_> = txs.values().cloned().collect();
|
let mut sorted: Vec<_> = txs.values().cloned().collect();
|
||||||
sorted.sort_by(|a, b| b.fee_rate.partial_cmp(&a.fee_rate).unwrap_or(std::cmp::Ordering::Equal));
|
sorted.sort_by(|a, b| {
|
||||||
|
b.fee_rate
|
||||||
|
.partial_cmp(&a.fee_rate)
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
});
|
||||||
|
|
||||||
// Select transactions up to max mass
|
// Select transactions up to max mass
|
||||||
let mut selected = Vec::new();
|
let mut selected = Vec::new();
|
||||||
|
|
@ -308,7 +319,11 @@ impl MempoolService {
|
||||||
|
|
||||||
// Sort by fee rate ascending (evict lowest first)
|
// Sort by fee rate ascending (evict lowest first)
|
||||||
let mut sorted: Vec<_> = txs.values().cloned().collect();
|
let mut sorted: Vec<_> = txs.values().cloned().collect();
|
||||||
sorted.sort_by(|a, b| a.fee_rate.partial_cmp(&b.fee_rate).unwrap_or(std::cmp::Ordering::Equal));
|
sorted.sort_by(|a, b| {
|
||||||
|
a.fee_rate
|
||||||
|
.partial_cmp(&b.fee_rate)
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
});
|
||||||
|
|
||||||
let mut freed = 0usize;
|
let mut freed = 0usize;
|
||||||
let mut to_remove = Vec::new();
|
let mut to_remove = Vec::new();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ use tokio::sync::{broadcast, mpsc, RwLock};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use synor_mining::{
|
use synor_mining::{
|
||||||
BlockMiner, BlockTemplate as MiningBlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, MinerCommand, MinerConfig, MinerEvent, MiningResult, MiningStats as CrateMiningStats, TemplateTransaction,
|
BlockMiner, BlockTemplate as MiningBlockTemplate, BlockTemplateBuilder, CoinbaseBuilder,
|
||||||
|
MinerCommand, MinerConfig, MinerEvent, MiningResult, MiningStats as CrateMiningStats,
|
||||||
|
TemplateTransaction,
|
||||||
};
|
};
|
||||||
use synor_types::{Address, Hash256, Network};
|
use synor_types::{Address, Hash256, Network};
|
||||||
|
|
||||||
|
|
@ -118,9 +120,11 @@ impl MinerService {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse coinbase address if provided
|
// Parse coinbase address if provided
|
||||||
let coinbase_address = config.mining.coinbase_address.as_ref().and_then(|addr_str| {
|
let coinbase_address = config
|
||||||
addr_str.parse::<Address>().ok()
|
.mining
|
||||||
});
|
.coinbase_address
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|addr_str| addr_str.parse::<Address>().ok());
|
||||||
|
|
||||||
// Determine network from config
|
// Determine network from config
|
||||||
let network = match config.network.as_str() {
|
let network = match config.network.as_str() {
|
||||||
|
|
@ -261,7 +265,10 @@ impl MinerService {
|
||||||
/// Updates the mining template.
|
/// Updates the mining template.
|
||||||
async fn update_template(&self) -> anyhow::Result<()> {
|
async fn update_template(&self) -> anyhow::Result<()> {
|
||||||
let template = self.build_template().await?;
|
let template = self.build_template().await?;
|
||||||
let _ = self.cmd_tx.send(MinerCommand::NewTemplate(Arc::new(template))).await;
|
let _ = self
|
||||||
|
.cmd_tx
|
||||||
|
.send(MinerCommand::NewTemplate(Arc::new(template)))
|
||||||
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,12 +308,16 @@ impl MinerService {
|
||||||
|
|
||||||
/// Sets coinbase address.
|
/// Sets coinbase address.
|
||||||
pub async fn set_coinbase_address(&self, address: String) -> anyhow::Result<()> {
|
pub async fn set_coinbase_address(&self, address: String) -> anyhow::Result<()> {
|
||||||
let parsed: Address = address.parse()
|
let parsed: Address = address
|
||||||
|
.parse()
|
||||||
.map_err(|e| anyhow::anyhow!("Invalid address: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Invalid address: {}", e))?;
|
||||||
|
|
||||||
// Update miner config
|
// Update miner config
|
||||||
let new_config = MinerConfig::solo(parsed, self.threads);
|
let new_config = MinerConfig::solo(parsed, self.threads);
|
||||||
let _ = self.cmd_tx.send(MinerCommand::UpdateConfig(new_config)).await;
|
let _ = self
|
||||||
|
.cmd_tx
|
||||||
|
.send(MinerCommand::UpdateConfig(new_config))
|
||||||
|
.await;
|
||||||
|
|
||||||
info!(address = %address, "Updated coinbase address");
|
info!(address = %address, "Updated coinbase address");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -314,7 +325,9 @@ impl MinerService {
|
||||||
|
|
||||||
/// Builds a block template for mining.
|
/// Builds a block template for mining.
|
||||||
async fn build_template(&self) -> anyhow::Result<MiningBlockTemplate> {
|
async fn build_template(&self) -> anyhow::Result<MiningBlockTemplate> {
|
||||||
let coinbase_address = self.coinbase_address.clone()
|
let coinbase_address = self
|
||||||
|
.coinbase_address
|
||||||
|
.clone()
|
||||||
.ok_or_else(|| anyhow::anyhow!("No coinbase address set"))?;
|
.ok_or_else(|| anyhow::anyhow!("No coinbase address set"))?;
|
||||||
|
|
||||||
// Get transactions from mempool
|
// Get transactions from mempool
|
||||||
|
|
@ -363,7 +376,8 @@ impl MinerService {
|
||||||
builder = builder.add_transaction(template_tx);
|
builder = builder.add_transaction(template_tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let template = builder.build(template_id)
|
let template = builder
|
||||||
|
.build(template_id)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to build template: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to build template: {}", e))?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
|
|
@ -416,7 +430,9 @@ impl MinerService {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the template that was mined
|
// Get the template that was mined
|
||||||
let template = self.miner.current_template()
|
let template = self
|
||||||
|
.miner
|
||||||
|
.current_template()
|
||||||
.ok_or_else(|| anyhow::anyhow!("No current template"))?;
|
.ok_or_else(|| anyhow::anyhow!("No current template"))?;
|
||||||
|
|
||||||
// Build full block from template and mining result
|
// Build full block from template and mining result
|
||||||
|
|
@ -493,7 +509,9 @@ impl MinerService {
|
||||||
// Get hash from block header for notification
|
// Get hash from block header for notification
|
||||||
let hash = if block.len() >= 32 {
|
let hash = if block.len() >= 32 {
|
||||||
let mut h = [0u8; 32];
|
let mut h = [0u8; 32];
|
||||||
h.copy_from_slice(&blake3::hash(&block[..96.min(block.len())]).as_bytes()[..32]);
|
h.copy_from_slice(
|
||||||
|
&blake3::hash(&block[..96.min(block.len())]).as_bytes()[..32],
|
||||||
|
);
|
||||||
h
|
h
|
||||||
} else {
|
} else {
|
||||||
[0u8; 32]
|
[0u8; 32]
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,10 @@ pub enum NetworkMessage {
|
||||||
/// Block response.
|
/// Block response.
|
||||||
Blocks { data: Vec<Vec<u8>> },
|
Blocks { data: Vec<Vec<u8>> },
|
||||||
/// Headers request.
|
/// Headers request.
|
||||||
GetHeaders { locator: Vec<[u8; 32]>, stop: [u8; 32] },
|
GetHeaders {
|
||||||
|
locator: Vec<[u8; 32]>,
|
||||||
|
stop: [u8; 32],
|
||||||
|
},
|
||||||
/// Headers response.
|
/// Headers response.
|
||||||
Headers { headers: Vec<Vec<u8>> },
|
Headers { headers: Vec<Vec<u8>> },
|
||||||
}
|
}
|
||||||
|
|
@ -112,11 +115,11 @@ impl NetworkService {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse listen address
|
// Parse listen address
|
||||||
let listen_addr_parsed: Multiaddr = config
|
let listen_addr_parsed: Multiaddr = config.p2p.listen_addr.parse().unwrap_or_else(|_| {
|
||||||
.p2p
|
format!("/ip4/0.0.0.0/tcp/{}", synor_network::DEFAULT_PORT)
|
||||||
.listen_addr
|
.parse()
|
||||||
.parse()
|
.unwrap()
|
||||||
.unwrap_or_else(|_| format!("/ip4/0.0.0.0/tcp/{}", synor_network::DEFAULT_PORT).parse().unwrap());
|
});
|
||||||
|
|
||||||
// Parse seed/bootstrap peers
|
// Parse seed/bootstrap peers
|
||||||
let bootstrap_peers: Vec<Multiaddr> = config
|
let bootstrap_peers: Vec<Multiaddr> = config
|
||||||
|
|
@ -364,7 +367,7 @@ impl NetworkService {
|
||||||
}
|
}
|
||||||
NetworkMessage::TxAnnounce { hash } => {
|
NetworkMessage::TxAnnounce { hash } => {
|
||||||
let announcement = TransactionAnnouncement::id_only(
|
let announcement = TransactionAnnouncement::id_only(
|
||||||
synor_types::TransactionId::from_bytes(hash)
|
synor_types::TransactionId::from_bytes(hash),
|
||||||
);
|
);
|
||||||
if let Err(e) = handle.broadcast_transaction(announcement).await {
|
if let Err(e) = handle.broadcast_transaction(announcement).await {
|
||||||
warn!("Failed to broadcast transaction: {}", e);
|
warn!("Failed to broadcast transaction: {}", e);
|
||||||
|
|
@ -406,11 +409,7 @@ impl NetworkService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests blocks from a peer.
|
/// Requests blocks from a peer.
|
||||||
pub async fn request_blocks(
|
pub async fn request_blocks(&self, peer_id: &str, hashes: Vec<[u8; 32]>) -> anyhow::Result<()> {
|
||||||
&self,
|
|
||||||
peer_id: &str,
|
|
||||||
hashes: Vec<[u8; 32]>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let handle_guard = self.handle.read().await;
|
let handle_guard = self.handle.read().await;
|
||||||
if let Some(ref handle) = *handle_guard {
|
if let Some(ref handle) = *handle_guard {
|
||||||
let peer: PeerId = peer_id
|
let peer: PeerId = peer_id
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use tokio::sync::RwLock;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use synor_network::SyncState;
|
use synor_network::SyncState;
|
||||||
use synor_types::{BlockHeader, block::BlockBody};
|
use synor_types::{block::BlockBody, BlockHeader};
|
||||||
|
|
||||||
use crate::config::NodeConfig;
|
use crate::config::NodeConfig;
|
||||||
use crate::services::{
|
use crate::services::{
|
||||||
|
|
@ -109,7 +109,9 @@ impl RpcService {
|
||||||
|
|
||||||
// Start HTTP server
|
// Start HTTP server
|
||||||
if self.http_enabled {
|
if self.http_enabled {
|
||||||
let http_addr: SocketAddr = self.http_addr.parse()
|
let http_addr: SocketAddr = self
|
||||||
|
.http_addr
|
||||||
|
.parse()
|
||||||
.map_err(|e| anyhow::anyhow!("Invalid HTTP address: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Invalid HTTP address: {}", e))?;
|
||||||
|
|
||||||
info!(addr = %http_addr, "Starting HTTP RPC server");
|
info!(addr = %http_addr, "Starting HTTP RPC server");
|
||||||
|
|
@ -119,7 +121,8 @@ impl RpcService {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to start HTTP server: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to start HTTP server: {}", e))?;
|
||||||
|
|
||||||
let local_addr = server.local_addr()
|
let local_addr = server
|
||||||
|
.local_addr()
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?;
|
||||||
info!(addr = %local_addr, "HTTP RPC server started");
|
info!(addr = %local_addr, "HTTP RPC server started");
|
||||||
|
|
||||||
|
|
@ -129,7 +132,9 @@ impl RpcService {
|
||||||
|
|
||||||
// Start WebSocket server
|
// Start WebSocket server
|
||||||
if self.ws_enabled {
|
if self.ws_enabled {
|
||||||
let ws_addr: SocketAddr = self.ws_addr.parse()
|
let ws_addr: SocketAddr = self
|
||||||
|
.ws_addr
|
||||||
|
.parse()
|
||||||
.map_err(|e| anyhow::anyhow!("Invalid WebSocket address: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Invalid WebSocket address: {}", e))?;
|
||||||
|
|
||||||
info!(addr = %ws_addr, "Starting WebSocket RPC server");
|
info!(addr = %ws_addr, "Starting WebSocket RPC server");
|
||||||
|
|
@ -139,7 +144,8 @@ impl RpcService {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to start WebSocket server: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to start WebSocket server: {}", e))?;
|
||||||
|
|
||||||
let local_addr = server.local_addr()
|
let local_addr = server
|
||||||
|
.local_addr()
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to get local address: {}", e))?;
|
||||||
info!(addr = %local_addr, "WebSocket RPC server started");
|
info!(addr = %local_addr, "WebSocket RPC server started");
|
||||||
|
|
||||||
|
|
@ -319,7 +325,10 @@ impl RpcService {
|
||||||
let mempool_size = ctx.mempool.count().await;
|
let mempool_size = ctx.mempool.count().await;
|
||||||
|
|
||||||
// Check actual sync status from network service
|
// Check actual sync status from network service
|
||||||
let synced = ctx.network.sync_status().await
|
let synced = ctx
|
||||||
|
.network
|
||||||
|
.sync_status()
|
||||||
|
.await
|
||||||
.map(|status| matches!(status.state, SyncState::Synced | SyncState::Idle))
|
.map(|status| matches!(status.state, SyncState::Synced | SyncState::Idle))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
|
@ -343,16 +352,19 @@ impl RpcService {
|
||||||
// synor_getPeerInfo
|
// synor_getPeerInfo
|
||||||
module.register_async_method("synor_getPeerInfo", |_, ctx| async move {
|
module.register_async_method("synor_getPeerInfo", |_, ctx| async move {
|
||||||
let peers = ctx.network.peers().await;
|
let peers = ctx.network.peers().await;
|
||||||
let peer_info: Vec<serde_json::Value> = peers.iter().map(|p| {
|
let peer_info: Vec<serde_json::Value> = peers
|
||||||
serde_json::json!({
|
.iter()
|
||||||
"id": p.id,
|
.map(|p| {
|
||||||
"address": p.address.map(|a| a.to_string()).unwrap_or_default(),
|
serde_json::json!({
|
||||||
"isInbound": p.inbound,
|
"id": p.id,
|
||||||
"version": p.version,
|
"address": p.address.map(|a| a.to_string()).unwrap_or_default(),
|
||||||
"userAgent": p.user_agent,
|
"isInbound": p.inbound,
|
||||||
"latencyMs": p.latency_ms
|
"version": p.version,
|
||||||
|
"userAgent": p.user_agent,
|
||||||
|
"latencyMs": p.latency_ms
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}).collect();
|
.collect();
|
||||||
serde_json::json!({"peers": peer_info})
|
serde_json::json!({"peers": peer_info})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -418,7 +430,9 @@ impl RpcService {
|
||||||
|
|
||||||
let bytecode = match hex::decode(¶ms.bytecode) {
|
let bytecode = match hex::decode(¶ms.bytecode) {
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
Err(e) => return serde_json::json!({"error": format!("Invalid bytecode hex: {}", e)}),
|
Err(e) => {
|
||||||
|
return serde_json::json!({"error": format!("Invalid bytecode hex: {}", e)})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let init_args = if params.init_args.is_empty() {
|
let init_args = if params.init_args.is_empty() {
|
||||||
|
|
@ -426,21 +440,27 @@ impl RpcService {
|
||||||
} else {
|
} else {
|
||||||
match hex::decode(¶ms.init_args) {
|
match hex::decode(¶ms.init_args) {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(e) => return serde_json::json!({"error": format!("Invalid init_args hex: {}", e)}),
|
Err(e) => {
|
||||||
|
return serde_json::json!({"error": format!("Invalid init_args hex: {}", e)})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_height = ctx.consensus.current_height().await;
|
let block_height = ctx.consensus.current_height().await;
|
||||||
let timestamp = current_timestamp();
|
let timestamp = current_timestamp();
|
||||||
|
|
||||||
match ctx.contract.deploy(
|
match ctx
|
||||||
bytecode,
|
.contract
|
||||||
init_args,
|
.deploy(
|
||||||
¶ms.deployer,
|
bytecode,
|
||||||
params.gas_limit,
|
init_args,
|
||||||
block_height,
|
¶ms.deployer,
|
||||||
timestamp,
|
params.gas_limit,
|
||||||
).await {
|
block_height,
|
||||||
|
timestamp,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(result) => serde_json::json!({
|
Ok(result) => serde_json::json!({
|
||||||
"contractId": hex::encode(&result.contract_id),
|
"contractId": hex::encode(&result.contract_id),
|
||||||
"address": hex::encode(&result.address),
|
"address": hex::encode(&result.address),
|
||||||
|
|
@ -448,7 +468,7 @@ impl RpcService {
|
||||||
}),
|
}),
|
||||||
Err(e) => serde_json::json!({
|
Err(e) => serde_json::json!({
|
||||||
"error": e.to_string()
|
"error": e.to_string()
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -474,7 +494,9 @@ impl RpcService {
|
||||||
|
|
||||||
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}),
|
Err(e) => {
|
||||||
|
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let args = if params.args.is_empty() {
|
let args = if params.args.is_empty() {
|
||||||
|
|
@ -482,23 +504,29 @@ impl RpcService {
|
||||||
} else {
|
} else {
|
||||||
match hex::decode(¶ms.args) {
|
match hex::decode(¶ms.args) {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(e) => return serde_json::json!({"error": format!("Invalid args hex: {}", e)}),
|
Err(e) => {
|
||||||
|
return serde_json::json!({"error": format!("Invalid args hex: {}", e)})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_height = ctx.consensus.current_height().await;
|
let block_height = ctx.consensus.current_height().await;
|
||||||
let timestamp = current_timestamp();
|
let timestamp = current_timestamp();
|
||||||
|
|
||||||
match ctx.contract.call(
|
match ctx
|
||||||
&contract_id,
|
.contract
|
||||||
¶ms.method,
|
.call(
|
||||||
args,
|
&contract_id,
|
||||||
¶ms.caller,
|
¶ms.method,
|
||||||
params.value,
|
args,
|
||||||
params.gas_limit,
|
¶ms.caller,
|
||||||
block_height,
|
params.value,
|
||||||
timestamp,
|
params.gas_limit,
|
||||||
).await {
|
block_height,
|
||||||
|
timestamp,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let logs: Vec<serde_json::Value> = result.logs.iter().map(|log| {
|
let logs: Vec<serde_json::Value> = result.logs.iter().map(|log| {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
|
|
@ -514,10 +542,10 @@ impl RpcService {
|
||||||
"gasUsed": result.gas_used,
|
"gasUsed": result.gas_used,
|
||||||
"logs": logs
|
"logs": logs
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
Err(e) => serde_json::json!({
|
Err(e) => serde_json::json!({
|
||||||
"error": e.to_string()
|
"error": e.to_string()
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -541,7 +569,9 @@ impl RpcService {
|
||||||
|
|
||||||
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}),
|
Err(e) => {
|
||||||
|
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let args = if params.args.is_empty() {
|
let args = if params.args.is_empty() {
|
||||||
|
|
@ -549,28 +579,34 @@ impl RpcService {
|
||||||
} else {
|
} else {
|
||||||
match hex::decode(¶ms.args) {
|
match hex::decode(¶ms.args) {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(e) => return serde_json::json!({"error": format!("Invalid args hex: {}", e)}),
|
Err(e) => {
|
||||||
|
return serde_json::json!({"error": format!("Invalid args hex: {}", e)})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_height = ctx.consensus.current_height().await;
|
let block_height = ctx.consensus.current_height().await;
|
||||||
let timestamp = current_timestamp();
|
let timestamp = current_timestamp();
|
||||||
|
|
||||||
match ctx.contract.estimate_gas(
|
match ctx
|
||||||
&contract_id,
|
.contract
|
||||||
¶ms.method,
|
.estimate_gas(
|
||||||
args,
|
&contract_id,
|
||||||
¶ms.caller,
|
¶ms.method,
|
||||||
params.value,
|
args,
|
||||||
block_height,
|
¶ms.caller,
|
||||||
timestamp,
|
params.value,
|
||||||
).await {
|
block_height,
|
||||||
|
timestamp,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(gas) => serde_json::json!({
|
Ok(gas) => serde_json::json!({
|
||||||
"estimatedGas": gas
|
"estimatedGas": gas
|
||||||
}),
|
}),
|
||||||
Err(e) => serde_json::json!({
|
Err(e) => serde_json::json!({
|
||||||
"error": e.to_string()
|
"error": e.to_string()
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -588,7 +624,9 @@ impl RpcService {
|
||||||
|
|
||||||
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}),
|
Err(e) => {
|
||||||
|
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match ctx.contract.get_code(&contract_id).await {
|
match ctx.contract.get_code(&contract_id).await {
|
||||||
|
|
@ -600,7 +638,7 @@ impl RpcService {
|
||||||
}),
|
}),
|
||||||
Err(e) => serde_json::json!({
|
Err(e) => serde_json::json!({
|
||||||
"error": e.to_string()
|
"error": e.to_string()
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -619,7 +657,9 @@ impl RpcService {
|
||||||
|
|
||||||
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}),
|
Err(e) => {
|
||||||
|
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = match hex_to_hash(¶ms.key) {
|
let key = match hex_to_hash(¶ms.key) {
|
||||||
|
|
@ -636,7 +676,7 @@ impl RpcService {
|
||||||
}),
|
}),
|
||||||
Err(e) => serde_json::json!({
|
Err(e) => serde_json::json!({
|
||||||
"error": e.to_string()
|
"error": e.to_string()
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -654,7 +694,9 @@ impl RpcService {
|
||||||
|
|
||||||
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
let contract_id = match hex_to_hash(¶ms.contract_id) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(e) => return serde_json::json!({"error": format!("Invalid contract_id: {}", e)}),
|
Err(e) => {
|
||||||
|
return serde_json::json!({"error": format!("Invalid contract_id: {}", e)})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match ctx.contract.get_contract(&contract_id).await {
|
match ctx.contract.get_contract(&contract_id).await {
|
||||||
|
|
@ -669,7 +711,7 @@ impl RpcService {
|
||||||
}),
|
}),
|
||||||
Err(e) => serde_json::json!({
|
Err(e) => serde_json::json!({
|
||||||
"error": e.to_string()
|
"error": e.to_string()
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -705,11 +747,7 @@ impl RpcService {
|
||||||
blue_work: String::new(),
|
blue_work: String::new(),
|
||||||
pruning_point: None,
|
pruning_point: None,
|
||||||
},
|
},
|
||||||
transactions: if include_txs {
|
transactions: if include_txs { vec![] } else { vec![] },
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
},
|
|
||||||
verbose_data: None,
|
verbose_data: None,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -845,8 +883,7 @@ impl RpcService {
|
||||||
pruning_point: None,
|
pruning_point: None,
|
||||||
},
|
},
|
||||||
transactions: vec![],
|
transactions: vec![],
|
||||||
target: "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
target: "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string(),
|
||||||
.to_string(),
|
|
||||||
is_synced: true,
|
is_synced: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ use tokio::sync::RwLock;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use synor_storage::{
|
use synor_storage::{
|
||||||
cf, Database, DatabaseConfig,
|
cf, BlockBody, BlockStore, ChainState, Database, DatabaseConfig, GhostdagStore, HeaderStore,
|
||||||
BlockBody, BlockStore, ChainState, GhostdagStore, HeaderStore,
|
MetadataStore, RelationsStore, StoredGhostdagData, StoredRelations, StoredUtxo,
|
||||||
MetadataStore, RelationsStore, StoredGhostdagData, StoredRelations,
|
TransactionStore, UtxoStore,
|
||||||
StoredUtxo, TransactionStore, UtxoStore,
|
|
||||||
};
|
};
|
||||||
use synor_types::{BlockHeader, BlockId, Hash256, Transaction, TransactionId};
|
use synor_types::{BlockHeader, BlockId, Hash256, Transaction, TransactionId};
|
||||||
|
|
||||||
|
|
@ -168,15 +167,23 @@ impl StorageService {
|
||||||
/// Stores a block header.
|
/// Stores a block header.
|
||||||
pub async fn put_header(&self, header: &BlockHeader) -> anyhow::Result<()> {
|
pub async fn put_header(&self, header: &BlockHeader) -> anyhow::Result<()> {
|
||||||
let store = self.header_store.read().await;
|
let store = self.header_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.put(header).map_err(|e| anyhow::anyhow!("Failed to store header: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.put(header)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to store header: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a block header by hash.
|
/// Gets a block header by hash.
|
||||||
pub async fn get_header(&self, hash: &Hash256) -> anyhow::Result<Option<BlockHeader>> {
|
pub async fn get_header(&self, hash: &Hash256) -> anyhow::Result<Option<BlockHeader>> {
|
||||||
let store = self.header_store.read().await;
|
let store = self.header_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get(hash).map_err(|e| anyhow::anyhow!("Failed to get header: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get(hash)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get header: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a header exists.
|
/// Checks if a header exists.
|
||||||
|
|
@ -192,15 +199,23 @@ impl StorageService {
|
||||||
/// Gets header by height.
|
/// Gets header by height.
|
||||||
pub async fn get_header_by_height(&self, height: u64) -> anyhow::Result<Option<BlockHeader>> {
|
pub async fn get_header_by_height(&self, height: u64) -> anyhow::Result<Option<BlockHeader>> {
|
||||||
let store = self.header_store.read().await;
|
let store = self.header_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_by_height(height).map_err(|e| anyhow::anyhow!("Failed to get header by height: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_by_height(height)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get header by height: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indexes a header by height.
|
/// Indexes a header by height.
|
||||||
pub async fn index_header_by_height(&self, height: u64, hash: &Hash256) -> anyhow::Result<()> {
|
pub async fn index_header_by_height(&self, height: u64, hash: &Hash256) -> anyhow::Result<()> {
|
||||||
let store = self.header_store.read().await;
|
let store = self.header_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.index_by_height(height, hash).map_err(|e| anyhow::anyhow!("Failed to index header: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.index_by_height(height, hash)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to index header: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Block Body Operations ====================
|
// ==================== Block Body Operations ====================
|
||||||
|
|
@ -208,15 +223,23 @@ impl StorageService {
|
||||||
/// Stores a block body.
|
/// Stores a block body.
|
||||||
pub async fn put_block_body(&self, hash: &Hash256, body: &BlockBody) -> anyhow::Result<()> {
|
pub async fn put_block_body(&self, hash: &Hash256, body: &BlockBody) -> anyhow::Result<()> {
|
||||||
let store = self.block_store.read().await;
|
let store = self.block_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.put(hash, body).map_err(|e| anyhow::anyhow!("Failed to store block body: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.put(hash, body)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to store block body: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a block body by hash.
|
/// Gets a block body by hash.
|
||||||
pub async fn get_block_body(&self, hash: &Hash256) -> anyhow::Result<Option<BlockBody>> {
|
pub async fn get_block_body(&self, hash: &Hash256) -> anyhow::Result<Option<BlockBody>> {
|
||||||
let store = self.block_store.read().await;
|
let store = self.block_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get(hash).map_err(|e| anyhow::anyhow!("Failed to get block body: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get(hash)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get block body: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if block exists.
|
/// Checks if block exists.
|
||||||
|
|
@ -233,7 +256,9 @@ impl StorageService {
|
||||||
pub async fn put_block(&self, block: &BlockData) -> anyhow::Result<()> {
|
pub async fn put_block(&self, block: &BlockData) -> anyhow::Result<()> {
|
||||||
debug!(hash = hex::encode(&block.hash[..8]), "Storing block");
|
debug!(hash = hex::encode(&block.hash[..8]), "Storing block");
|
||||||
let db = self.database.read().await;
|
let db = self.database.read().await;
|
||||||
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let db = db
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
|
||||||
// Store header bytes
|
// Store header bytes
|
||||||
db.put(cf::HEADERS, &block.hash, &block.header)
|
db.put(cf::HEADERS, &block.hash, &block.header)
|
||||||
|
|
@ -249,11 +274,15 @@ impl StorageService {
|
||||||
/// Legacy method: Gets a block by hash (raw bytes).
|
/// Legacy method: Gets a block by hash (raw bytes).
|
||||||
pub async fn get_block(&self, hash: &[u8; 32]) -> anyhow::Result<Option<BlockData>> {
|
pub async fn get_block(&self, hash: &[u8; 32]) -> anyhow::Result<Option<BlockData>> {
|
||||||
let db = self.database.read().await;
|
let db = self.database.read().await;
|
||||||
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let db = db
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
|
||||||
let header = db.get(cf::HEADERS, hash)
|
let header = db
|
||||||
|
.get(cf::HEADERS, hash)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to get header: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to get header: {}", e))?;
|
||||||
let body = db.get(cf::BLOCKS, hash)
|
let body = db
|
||||||
|
.get(cf::BLOCKS, hash)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to get body: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to get body: {}", e))?;
|
||||||
|
|
||||||
match (header, body) {
|
match (header, body) {
|
||||||
|
|
@ -271,15 +300,26 @@ impl StorageService {
|
||||||
/// Stores a transaction.
|
/// Stores a transaction.
|
||||||
pub async fn put_transaction(&self, tx: &Transaction) -> anyhow::Result<()> {
|
pub async fn put_transaction(&self, tx: &Transaction) -> anyhow::Result<()> {
|
||||||
let store = self.tx_store.read().await;
|
let store = self.tx_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.put(tx).map_err(|e| anyhow::anyhow!("Failed to store transaction: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.put(tx)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to store transaction: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a transaction by ID.
|
/// Gets a transaction by ID.
|
||||||
pub async fn get_transaction(&self, txid: &TransactionId) -> anyhow::Result<Option<Transaction>> {
|
pub async fn get_transaction(
|
||||||
|
&self,
|
||||||
|
txid: &TransactionId,
|
||||||
|
) -> anyhow::Result<Option<Transaction>> {
|
||||||
let store = self.tx_store.read().await;
|
let store = self.tx_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get(txid).map_err(|e| anyhow::anyhow!("Failed to get transaction: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get(txid)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get transaction: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a transaction exists.
|
/// Checks if a transaction exists.
|
||||||
|
|
@ -295,24 +335,45 @@ impl StorageService {
|
||||||
// ==================== UTXO Operations ====================
|
// ==================== UTXO Operations ====================
|
||||||
|
|
||||||
/// Gets a UTXO.
|
/// Gets a UTXO.
|
||||||
pub async fn get_utxo(&self, txid: &TransactionId, index: u32) -> anyhow::Result<Option<StoredUtxo>> {
|
pub async fn get_utxo(
|
||||||
|
&self,
|
||||||
|
txid: &TransactionId,
|
||||||
|
index: u32,
|
||||||
|
) -> anyhow::Result<Option<StoredUtxo>> {
|
||||||
let store = self.utxo_store.read().await;
|
let store = self.utxo_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get(txid, index).map_err(|e| anyhow::anyhow!("Failed to get UTXO: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get(txid, index)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get UTXO: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores a UTXO.
|
/// Stores a UTXO.
|
||||||
pub async fn put_utxo(&self, txid: &TransactionId, index: u32, utxo: &StoredUtxo) -> anyhow::Result<()> {
|
pub async fn put_utxo(
|
||||||
|
&self,
|
||||||
|
txid: &TransactionId,
|
||||||
|
index: u32,
|
||||||
|
utxo: &StoredUtxo,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let store = self.utxo_store.read().await;
|
let store = self.utxo_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.put(txid, index, utxo).map_err(|e| anyhow::anyhow!("Failed to store UTXO: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.put(txid, index, utxo)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to store UTXO: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a UTXO (marks as spent).
|
/// Deletes a UTXO (marks as spent).
|
||||||
pub async fn delete_utxo(&self, txid: &TransactionId, index: u32) -> anyhow::Result<()> {
|
pub async fn delete_utxo(&self, txid: &TransactionId, index: u32) -> anyhow::Result<()> {
|
||||||
let store = self.utxo_store.read().await;
|
let store = self.utxo_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.delete(txid, index).map_err(|e| anyhow::anyhow!("Failed to delete UTXO: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.delete(txid, index)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to delete UTXO: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a UTXO exists (is unspent).
|
/// Checks if a UTXO exists (is unspent).
|
||||||
|
|
@ -326,77 +387,134 @@ impl StorageService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets all UTXOs for a transaction.
|
/// Gets all UTXOs for a transaction.
|
||||||
pub async fn get_utxos_by_tx(&self, txid: &TransactionId) -> anyhow::Result<Vec<(u32, StoredUtxo)>> {
|
pub async fn get_utxos_by_tx(
|
||||||
|
&self,
|
||||||
|
txid: &TransactionId,
|
||||||
|
) -> anyhow::Result<Vec<(u32, StoredUtxo)>> {
|
||||||
let store = self.utxo_store.read().await;
|
let store = self.utxo_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_by_tx(txid).map_err(|e| anyhow::anyhow!("Failed to get UTXOs: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_by_tx(txid)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get UTXOs: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== DAG Relations Operations ====================
|
// ==================== DAG Relations Operations ====================
|
||||||
|
|
||||||
/// Stores DAG relations for a block.
|
/// Stores DAG relations for a block.
|
||||||
pub async fn put_relations(&self, block_id: &BlockId, relations: &StoredRelations) -> anyhow::Result<()> {
|
pub async fn put_relations(
|
||||||
|
&self,
|
||||||
|
block_id: &BlockId,
|
||||||
|
relations: &StoredRelations,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let store = self.relations_store.read().await;
|
let store = self.relations_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.put(block_id, relations).map_err(|e| anyhow::anyhow!("Failed to store relations: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.put(block_id, relations)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to store relations: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets DAG relations for a block.
|
/// Gets DAG relations for a block.
|
||||||
pub async fn get_relations(&self, block_id: &BlockId) -> anyhow::Result<Option<StoredRelations>> {
|
pub async fn get_relations(
|
||||||
|
&self,
|
||||||
|
block_id: &BlockId,
|
||||||
|
) -> anyhow::Result<Option<StoredRelations>> {
|
||||||
let store = self.relations_store.read().await;
|
let store = self.relations_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get(block_id).map_err(|e| anyhow::anyhow!("Failed to get relations: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get(block_id)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get relations: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets parents of a block.
|
/// Gets parents of a block.
|
||||||
pub async fn get_parents(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> {
|
pub async fn get_parents(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> {
|
||||||
let store = self.relations_store.read().await;
|
let store = self.relations_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_parents(block_id).map_err(|e| anyhow::anyhow!("Failed to get parents: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_parents(block_id)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get parents: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets children of a block.
|
/// Gets children of a block.
|
||||||
pub async fn get_children(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> {
|
pub async fn get_children(&self, block_id: &BlockId) -> anyhow::Result<Vec<BlockId>> {
|
||||||
let store = self.relations_store.read().await;
|
let store = self.relations_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_children(block_id).map_err(|e| anyhow::anyhow!("Failed to get children: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_children(block_id)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get children: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a child to a block's relations.
|
/// Adds a child to a block's relations.
|
||||||
pub async fn add_child(&self, parent_id: &BlockId, child_id: BlockId) -> anyhow::Result<()> {
|
pub async fn add_child(&self, parent_id: &BlockId, child_id: BlockId) -> anyhow::Result<()> {
|
||||||
let store = self.relations_store.read().await;
|
let store = self.relations_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.add_child(parent_id, child_id).map_err(|e| anyhow::anyhow!("Failed to add child: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.add_child(parent_id, child_id)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to add child: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== GHOSTDAG Operations ====================
|
// ==================== GHOSTDAG Operations ====================
|
||||||
|
|
||||||
/// Stores GHOSTDAG data for a block.
|
/// Stores GHOSTDAG data for a block.
|
||||||
pub async fn put_ghostdag(&self, block_id: &BlockId, data: &StoredGhostdagData) -> anyhow::Result<()> {
|
pub async fn put_ghostdag(
|
||||||
|
&self,
|
||||||
|
block_id: &BlockId,
|
||||||
|
data: &StoredGhostdagData,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let store = self.ghostdag_store.read().await;
|
let store = self.ghostdag_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.put(block_id, data).map_err(|e| anyhow::anyhow!("Failed to store GHOSTDAG data: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.put(block_id, data)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to store GHOSTDAG data: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets GHOSTDAG data for a block.
|
/// Gets GHOSTDAG data for a block.
|
||||||
pub async fn get_ghostdag(&self, block_id: &BlockId) -> anyhow::Result<Option<StoredGhostdagData>> {
|
pub async fn get_ghostdag(
|
||||||
|
&self,
|
||||||
|
block_id: &BlockId,
|
||||||
|
) -> anyhow::Result<Option<StoredGhostdagData>> {
|
||||||
let store = self.ghostdag_store.read().await;
|
let store = self.ghostdag_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get(block_id).map_err(|e| anyhow::anyhow!("Failed to get GHOSTDAG data: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get(block_id)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get GHOSTDAG data: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the blue score of a block.
|
/// Gets the blue score of a block.
|
||||||
pub async fn get_blue_score(&self, block_id: &BlockId) -> anyhow::Result<Option<u64>> {
|
pub async fn get_blue_score(&self, block_id: &BlockId) -> anyhow::Result<Option<u64>> {
|
||||||
let store = self.ghostdag_store.read().await;
|
let store = self.ghostdag_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_blue_score(block_id).map_err(|e| anyhow::anyhow!("Failed to get blue score: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_blue_score(block_id)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get blue score: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the selected parent of a block.
|
/// Gets the selected parent of a block.
|
||||||
pub async fn get_selected_parent(&self, block_id: &BlockId) -> anyhow::Result<Option<BlockId>> {
|
pub async fn get_selected_parent(&self, block_id: &BlockId) -> anyhow::Result<Option<BlockId>> {
|
||||||
let store = self.ghostdag_store.read().await;
|
let store = self.ghostdag_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_selected_parent(block_id).map_err(|e| anyhow::anyhow!("Failed to get selected parent: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_selected_parent(block_id)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get selected parent: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Metadata Operations ====================
|
// ==================== Metadata Operations ====================
|
||||||
|
|
@ -404,15 +522,23 @@ impl StorageService {
|
||||||
/// Gets current DAG tips.
|
/// Gets current DAG tips.
|
||||||
pub async fn get_tips(&self) -> anyhow::Result<Vec<BlockId>> {
|
pub async fn get_tips(&self) -> anyhow::Result<Vec<BlockId>> {
|
||||||
let store = self.metadata_store.read().await;
|
let store = self.metadata_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_tips().map_err(|e| anyhow::anyhow!("Failed to get tips: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_tips()
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get tips: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets current DAG tips.
|
/// Sets current DAG tips.
|
||||||
pub async fn set_tips(&self, tips: &[BlockId]) -> anyhow::Result<()> {
|
pub async fn set_tips(&self, tips: &[BlockId]) -> anyhow::Result<()> {
|
||||||
let store = self.metadata_store.read().await;
|
let store = self.metadata_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.set_tips(tips).map_err(|e| anyhow::anyhow!("Failed to set tips: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.set_tips(tips)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to set tips: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the current chain tip (first tip, for legacy compatibility).
|
/// Gets the current chain tip (first tip, for legacy compatibility).
|
||||||
|
|
@ -433,29 +559,45 @@ impl StorageService {
|
||||||
/// Gets the genesis block ID.
|
/// Gets the genesis block ID.
|
||||||
pub async fn get_genesis(&self) -> anyhow::Result<Option<BlockId>> {
|
pub async fn get_genesis(&self) -> anyhow::Result<Option<BlockId>> {
|
||||||
let store = self.metadata_store.read().await;
|
let store = self.metadata_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_genesis().map_err(|e| anyhow::anyhow!("Failed to get genesis: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_genesis()
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get genesis: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the genesis block ID.
|
/// Sets the genesis block ID.
|
||||||
pub async fn set_genesis(&self, genesis: &BlockId) -> anyhow::Result<()> {
|
pub async fn set_genesis(&self, genesis: &BlockId) -> anyhow::Result<()> {
|
||||||
let store = self.metadata_store.read().await;
|
let store = self.metadata_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.set_genesis(genesis).map_err(|e| anyhow::anyhow!("Failed to set genesis: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.set_genesis(genesis)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to set genesis: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the chain state.
|
/// Gets the chain state.
|
||||||
pub async fn get_chain_state(&self) -> anyhow::Result<Option<ChainState>> {
|
pub async fn get_chain_state(&self) -> anyhow::Result<Option<ChainState>> {
|
||||||
let store = self.metadata_store.read().await;
|
let store = self.metadata_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_chain_state().map_err(|e| anyhow::anyhow!("Failed to get chain state: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_chain_state()
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get chain state: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the chain state.
|
/// Sets the chain state.
|
||||||
pub async fn set_chain_state(&self, state: &ChainState) -> anyhow::Result<()> {
|
pub async fn set_chain_state(&self, state: &ChainState) -> anyhow::Result<()> {
|
||||||
let store = self.metadata_store.read().await;
|
let store = self.metadata_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.set_chain_state(state).map_err(|e| anyhow::anyhow!("Failed to set chain state: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.set_chain_state(state)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to set chain state: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets current height from chain state.
|
/// Gets current height from chain state.
|
||||||
|
|
@ -470,15 +612,23 @@ impl StorageService {
|
||||||
/// Gets the pruning point.
|
/// Gets the pruning point.
|
||||||
pub async fn get_pruning_point(&self) -> anyhow::Result<Option<BlockId>> {
|
pub async fn get_pruning_point(&self) -> anyhow::Result<Option<BlockId>> {
|
||||||
let store = self.metadata_store.read().await;
|
let store = self.metadata_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.get_pruning_point().map_err(|e| anyhow::anyhow!("Failed to get pruning point: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.get_pruning_point()
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to get pruning point: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the pruning point.
|
/// Sets the pruning point.
|
||||||
pub async fn set_pruning_point(&self, point: &BlockId) -> anyhow::Result<()> {
|
pub async fn set_pruning_point(&self, point: &BlockId) -> anyhow::Result<()> {
|
||||||
let store = self.metadata_store.read().await;
|
let store = self.metadata_store.read().await;
|
||||||
let store = store.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let store = store
|
||||||
store.set_pruning_point(point).map_err(|e| anyhow::anyhow!("Failed to set pruning point: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
store
|
||||||
|
.set_pruning_point(point)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to set pruning point: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Contract Storage ====================
|
// ==================== Contract Storage ====================
|
||||||
|
|
@ -490,7 +640,9 @@ impl StorageService {
|
||||||
key: &[u8; 32],
|
key: &[u8; 32],
|
||||||
) -> anyhow::Result<Option<Vec<u8>>> {
|
) -> anyhow::Result<Option<Vec<u8>>> {
|
||||||
let db = self.database.read().await;
|
let db = self.database.read().await;
|
||||||
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let db = db
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
|
||||||
// Create composite key: contract_address || storage_key
|
// Create composite key: contract_address || storage_key
|
||||||
let mut composite_key = Vec::with_capacity(64);
|
let mut composite_key = Vec::with_capacity(64);
|
||||||
|
|
@ -510,7 +662,9 @@ impl StorageService {
|
||||||
value: &[u8],
|
value: &[u8],
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let db = self.database.read().await;
|
let db = self.database.read().await;
|
||||||
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let db = db
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
|
||||||
let mut composite_key = Vec::with_capacity(64);
|
let mut composite_key = Vec::with_capacity(64);
|
||||||
composite_key.extend_from_slice(contract);
|
composite_key.extend_from_slice(contract);
|
||||||
|
|
@ -526,15 +680,21 @@ impl StorageService {
|
||||||
pub async fn compact(&self) -> anyhow::Result<()> {
|
pub async fn compact(&self) -> anyhow::Result<()> {
|
||||||
info!("Compacting database");
|
info!("Compacting database");
|
||||||
let db = self.database.read().await;
|
let db = self.database.read().await;
|
||||||
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let db = db
|
||||||
db.compact().map_err(|e| anyhow::anyhow!("Failed to compact database: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
db.compact()
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to compact database: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Flushes pending writes to disk.
|
/// Flushes pending writes to disk.
|
||||||
pub async fn flush(&self) -> anyhow::Result<()> {
|
pub async fn flush(&self) -> anyhow::Result<()> {
|
||||||
let db = self.database.read().await;
|
let db = self.database.read().await;
|
||||||
let db = db.as_ref().ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
let db = db
|
||||||
db.flush().map_err(|e| anyhow::anyhow!("Failed to flush database: {}", e))
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Storage not initialized"))?;
|
||||||
|
db.flush()
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to flush database: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets database statistics.
|
/// Gets database statistics.
|
||||||
|
|
@ -545,7 +705,9 @@ impl StorageService {
|
||||||
let headers_size = db.cf_size(cf::HEADERS).unwrap_or(0);
|
let headers_size = db.cf_size(cf::HEADERS).unwrap_or(0);
|
||||||
let blocks_size = db.cf_size(cf::BLOCKS).unwrap_or(0);
|
let blocks_size = db.cf_size(cf::BLOCKS).unwrap_or(0);
|
||||||
let utxos_size = db.cf_size(cf::UTXOS).unwrap_or(0);
|
let utxos_size = db.cf_size(cf::UTXOS).unwrap_or(0);
|
||||||
let total_size = headers_size + blocks_size + utxos_size
|
let total_size = headers_size
|
||||||
|
+ blocks_size
|
||||||
|
+ utxos_size
|
||||||
+ db.cf_size(cf::TRANSACTIONS).unwrap_or(0)
|
+ db.cf_size(cf::TRANSACTIONS).unwrap_or(0)
|
||||||
+ db.cf_size(cf::RELATIONS).unwrap_or(0)
|
+ db.cf_size(cf::RELATIONS).unwrap_or(0)
|
||||||
+ db.cf_size(cf::GHOSTDAG).unwrap_or(0)
|
+ db.cf_size(cf::GHOSTDAG).unwrap_or(0)
|
||||||
|
|
@ -559,7 +721,7 @@ impl StorageService {
|
||||||
blocks_count,
|
blocks_count,
|
||||||
utxo_count,
|
utxo_count,
|
||||||
disk_usage_bytes: total_size,
|
disk_usage_bytes: total_size,
|
||||||
cache_hits: 0, // Would need cache instrumentation
|
cache_hits: 0, // Would need cache instrumentation
|
||||||
cache_misses: 0,
|
cache_misses: 0,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,11 @@ async fn test_selected_parent_chain() {
|
||||||
for (i, node) in network.nodes.iter().enumerate() {
|
for (i, node) in network.nodes.iter().enumerate() {
|
||||||
let consensus = node.consensus();
|
let consensus = node.consensus();
|
||||||
let chain: Vec<[u8; 32]> = consensus.get_selected_chain(10).await;
|
let chain: Vec<[u8; 32]> = consensus.get_selected_chain(10).await;
|
||||||
info!(node = i, chain_length = chain.len(), "Selected parent chain");
|
info!(
|
||||||
|
node = i,
|
||||||
|
chain_length = chain.len(),
|
||||||
|
"Selected parent chain"
|
||||||
|
);
|
||||||
|
|
||||||
// Chain should be consistent across nodes in same network
|
// Chain should be consistent across nodes in same network
|
||||||
for (j, block) in chain.iter().enumerate() {
|
for (j, block) in chain.iter().enumerate() {
|
||||||
|
|
@ -551,8 +555,16 @@ async fn test_partition_and_recovery() {
|
||||||
// In real test, we'd need fresh config for same ports
|
// In real test, we'd need fresh config for same ports
|
||||||
// For now, just verify nodes didn't crash
|
// For now, just verify nodes didn't crash
|
||||||
|
|
||||||
assert_eq!(node1.state().await, NodeState::Running, "Node 1 should survive partition");
|
assert_eq!(
|
||||||
assert_eq!(node2.state().await, NodeState::Running, "Node 2 should survive partition");
|
node1.state().await,
|
||||||
|
NodeState::Running,
|
||||||
|
"Node 1 should survive partition"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
node2.state().await,
|
||||||
|
NodeState::Running,
|
||||||
|
"Node 2 should survive partition"
|
||||||
|
);
|
||||||
|
|
||||||
node2.stop().await.unwrap();
|
node2.stop().await.unwrap();
|
||||||
node1.stop().await.unwrap();
|
node1.stop().await.unwrap();
|
||||||
|
|
@ -594,11 +606,7 @@ async fn test_difficulty_adjustment() {
|
||||||
let difficulty = consensus.current_difficulty().await;
|
let difficulty = consensus.current_difficulty().await;
|
||||||
let _target = consensus.get_current_target().await;
|
let _target = consensus.get_current_target().await;
|
||||||
|
|
||||||
info!(
|
info!(node = i, difficulty_bits = difficulty, "Difficulty info");
|
||||||
node = i,
|
|
||||||
difficulty_bits = difficulty,
|
|
||||||
"Difficulty info"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Difficulty should be set
|
// Difficulty should be set
|
||||||
// Target is the hash threshold for valid blocks
|
// Target is the hash threshold for valid blocks
|
||||||
|
|
@ -653,7 +661,10 @@ async fn test_block_accepted_subscription() {
|
||||||
// Check if any blocks are received (unlikely in test without mining)
|
// Check if any blocks are received (unlikely in test without mining)
|
||||||
match tokio::time::timeout(Duration::from_millis(500), rx.recv()).await {
|
match tokio::time::timeout(Duration::from_millis(500), rx.recv()).await {
|
||||||
Ok(Ok(hash)) => {
|
Ok(Ok(hash)) => {
|
||||||
info!(block = hex::encode(&hash[..8]), "Received block notification");
|
info!(
|
||||||
|
block = hex::encode(&hash[..8]),
|
||||||
|
"Received block notification"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(Err(_)) => {
|
Ok(Err(_)) => {
|
||||||
info!("Block channel closed");
|
info!("Block channel closed");
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,11 @@ async fn test_three_node_mesh() {
|
||||||
info!(total_connections = total, "Network mesh formed");
|
info!(total_connections = total, "Network mesh formed");
|
||||||
|
|
||||||
// In a 3-node mesh, we expect 2-4 total connections (each connection counted twice)
|
// In a 3-node mesh, we expect 2-4 total connections (each connection counted twice)
|
||||||
assert!(total >= 2, "Expected at least 2 total connections, got {}", total);
|
assert!(
|
||||||
|
total >= 2,
|
||||||
|
"Expected at least 2 total connections, got {}",
|
||||||
|
total
|
||||||
|
);
|
||||||
|
|
||||||
network.stop_all().await.unwrap();
|
network.stop_all().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -532,7 +536,11 @@ async fn test_simultaneous_node_start() {
|
||||||
let net = node.network();
|
let net = node.network();
|
||||||
let count = net.peer_count().await;
|
let count = net.peer_count().await;
|
||||||
total_connections += count;
|
total_connections += count;
|
||||||
info!(node = i, peers = count, "Peer count after simultaneous start");
|
info!(
|
||||||
|
node = i,
|
||||||
|
peers = count,
|
||||||
|
"Peer count after simultaneous start"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,11 @@ async fn test_node_creation() {
|
||||||
assert!(result.is_ok(), "Node creation timed out");
|
assert!(result.is_ok(), "Node creation timed out");
|
||||||
|
|
||||||
let node_result = result.unwrap();
|
let node_result = result.unwrap();
|
||||||
assert!(node_result.is_ok(), "Node creation failed: {:?}", node_result.err());
|
assert!(
|
||||||
|
node_result.is_ok(),
|
||||||
|
"Node creation failed: {:?}",
|
||||||
|
node_result.err()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -185,7 +189,7 @@ async fn test_node_services_accessible() {
|
||||||
|
|
||||||
// Services should be accessible even before start
|
// Services should be accessible even before start
|
||||||
// These return &Arc<T> directly, not Option
|
// 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.network();
|
||||||
let _ = node.consensus();
|
let _ = node.consensus();
|
||||||
let _ = node.mempool();
|
let _ = node.mempool();
|
||||||
|
|
|
||||||
|
|
@ -250,11 +250,19 @@ async fn test_selected_chain_update() {
|
||||||
|
|
||||||
// Log the chain blocks
|
// Log the chain blocks
|
||||||
for (i, block) in chain0.iter().enumerate().take(5) {
|
for (i, block) in chain0.iter().enumerate().take(5) {
|
||||||
info!(position = i, block = hex::encode(&block[..8]), "Node 0 chain");
|
info!(
|
||||||
|
position = i,
|
||||||
|
block = hex::encode(&block[..8]),
|
||||||
|
"Node 0 chain"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, block) in chain1.iter().enumerate().take(5) {
|
for (i, block) in chain1.iter().enumerate().take(5) {
|
||||||
info!(position = i, block = hex::encode(&block[..8]), "Node 1 chain");
|
info!(
|
||||||
|
position = i,
|
||||||
|
block = hex::encode(&block[..8]),
|
||||||
|
"Node 1 chain"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chains should be similar after sync
|
// Chains should be similar after sync
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,9 @@ async fn test_block_request_response() {
|
||||||
if !peers.is_empty() {
|
if !peers.is_empty() {
|
||||||
// Attempt to request blocks (would need actual block hashes)
|
// Attempt to request blocks (would need actual block hashes)
|
||||||
let test_hashes = vec![[0u8; 32]]; // Dummy hash
|
let test_hashes = vec![[0u8; 32]]; // Dummy hash
|
||||||
let result = network_service.request_blocks(&peers[0].id, test_hashes).await;
|
let result = network_service
|
||||||
|
.request_blocks(&peers[0].id, test_hashes)
|
||||||
|
.await;
|
||||||
info!(result = ?result.is_ok(), "Block request result");
|
info!(result = ?result.is_ok(), "Block request result");
|
||||||
// Error expected for non-existent hash, but API should work
|
// Error expected for non-existent hash, but API should work
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,14 +159,12 @@ impl ContractAbi {
|
||||||
|
|
||||||
/// Serializes to JSON.
|
/// Serializes to JSON.
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
serde_json::to_string_pretty(self)
|
serde_json::to_string_pretty(self).map_err(|e| CompilerError::EncodingError(e.to_string()))
|
||||||
.map_err(|e| CompilerError::EncodingError(e.to_string()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserializes from JSON.
|
/// Deserializes from JSON.
|
||||||
pub fn from_json(json: &str) -> Result<Self> {
|
pub fn from_json(json: &str) -> Result<Self> {
|
||||||
serde_json::from_str(json)
|
serde_json::from_str(json).map_err(|e| CompilerError::ParseError(e.to_string()))
|
||||||
.map_err(|e| CompilerError::ParseError(e.to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -358,7 +356,9 @@ pub enum TypeInfo {
|
||||||
Bytes,
|
Bytes,
|
||||||
|
|
||||||
/// Fixed-size bytes.
|
/// Fixed-size bytes.
|
||||||
FixedBytes { size: usize },
|
FixedBytes {
|
||||||
|
size: usize,
|
||||||
|
},
|
||||||
|
|
||||||
/// Address.
|
/// Address.
|
||||||
Address,
|
Address,
|
||||||
|
|
@ -367,22 +367,36 @@ pub enum TypeInfo {
|
||||||
Hash256,
|
Hash256,
|
||||||
|
|
||||||
/// Array.
|
/// Array.
|
||||||
Array { element: Box<TypeInfo> },
|
Array {
|
||||||
|
element: Box<TypeInfo>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Fixed-size array.
|
/// Fixed-size array.
|
||||||
FixedArray { element: Box<TypeInfo>, size: usize },
|
FixedArray {
|
||||||
|
element: Box<TypeInfo>,
|
||||||
|
size: usize,
|
||||||
|
},
|
||||||
|
|
||||||
/// Optional value.
|
/// Optional value.
|
||||||
Option { inner: Box<TypeInfo> },
|
Option {
|
||||||
|
inner: Box<TypeInfo>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Tuple.
|
/// Tuple.
|
||||||
Tuple { elements: Vec<TypeInfo> },
|
Tuple {
|
||||||
|
elements: Vec<TypeInfo>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Struct.
|
/// Struct.
|
||||||
Struct { name: String, fields: Vec<(String, TypeInfo)> },
|
Struct {
|
||||||
|
name: String,
|
||||||
|
fields: Vec<(String, TypeInfo)>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Unknown type.
|
/// Unknown type.
|
||||||
Unknown { name: String },
|
Unknown {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeInfo {
|
impl TypeInfo {
|
||||||
|
|
@ -493,7 +507,9 @@ fn parse_producers_section(metadata: &mut ContractMetadata, data: &[u8]) {
|
||||||
if data_str.contains("rustc") {
|
if data_str.contains("rustc") {
|
||||||
// Extract rustc version if present
|
// Extract rustc version if present
|
||||||
if let Some(start) = data_str.find("rustc") {
|
if let Some(start) = data_str.find("rustc") {
|
||||||
let end = data_str[start..].find('\0').unwrap_or(data_str.len() - start);
|
let end = data_str[start..]
|
||||||
|
.find('\0')
|
||||||
|
.unwrap_or(data_str.len() - start);
|
||||||
let version = &data_str[start..start + end];
|
let version = &data_str[start..start + end];
|
||||||
metadata.rust_version = Some(version.to_string());
|
metadata.rust_version = Some(version.to_string());
|
||||||
}
|
}
|
||||||
|
|
@ -540,7 +556,8 @@ pub fn extract_abi(wasm: &[u8]) -> Result<ContractAbi> {
|
||||||
match payload {
|
match payload {
|
||||||
Payload::TypeSection(reader) => {
|
Payload::TypeSection(reader) => {
|
||||||
for rec_group in reader {
|
for rec_group in reader {
|
||||||
let rec_group = rec_group.map_err(|e| CompilerError::ParseError(e.to_string()))?;
|
let rec_group =
|
||||||
|
rec_group.map_err(|e| CompilerError::ParseError(e.to_string()))?;
|
||||||
for subtype in rec_group.into_types() {
|
for subtype in rec_group.into_types() {
|
||||||
if let wasmparser::CompositeType::Func(func_type) = subtype.composite_type {
|
if let wasmparser::CompositeType::Func(func_type) = subtype.composite_type {
|
||||||
function_types.push(FunctionType {
|
function_types.push(FunctionType {
|
||||||
|
|
@ -562,7 +579,8 @@ pub fn extract_abi(wasm: &[u8]) -> Result<ContractAbi> {
|
||||||
}
|
}
|
||||||
Payload::FunctionSection(reader) => {
|
Payload::FunctionSection(reader) => {
|
||||||
for type_idx in reader {
|
for type_idx in reader {
|
||||||
let type_idx = type_idx.map_err(|e| CompilerError::ParseError(e.to_string()))?;
|
let type_idx =
|
||||||
|
type_idx.map_err(|e| CompilerError::ParseError(e.to_string()))?;
|
||||||
function_type_indices.push(type_idx);
|
function_type_indices.push(type_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -773,13 +791,13 @@ mod tests {
|
||||||
assert_eq!(TypeInfo::U64.type_name(), "u64");
|
assert_eq!(TypeInfo::U64.type_name(), "u64");
|
||||||
assert_eq!(TypeInfo::Address.type_name(), "Address");
|
assert_eq!(TypeInfo::Address.type_name(), "Address");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
TypeInfo::Array { element: Box::new(TypeInfo::U8) }.type_name(),
|
TypeInfo::Array {
|
||||||
|
element: Box::new(TypeInfo::U8)
|
||||||
|
}
|
||||||
|
.type_name(),
|
||||||
"Vec<u8>"
|
"Vec<u8>"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(TypeInfo::FixedBytes { size: 32 }.type_name(), "[u8; 32]");
|
||||||
TypeInfo::FixedBytes { size: 32 }.type_name(),
|
|
||||||
"[u8; 32]"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -260,13 +260,14 @@ impl Optimizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run wasm-opt
|
// Run wasm-opt
|
||||||
debug!("Running wasm-opt with flags: {:?}", self.level.wasm_opt_flags());
|
debug!(
|
||||||
let output = cmd
|
"Running wasm-opt with flags: {:?}",
|
||||||
.output()
|
self.level.wasm_opt_flags()
|
||||||
.map_err(|e| CompilerError::ExternalToolError {
|
);
|
||||||
tool: "wasm-opt".into(),
|
let output = cmd.output().map_err(|e| CompilerError::ExternalToolError {
|
||||||
message: e.to_string(),
|
tool: "wasm-opt".into(),
|
||||||
})?;
|
message: e.to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
// Check result
|
// Check result
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
|
|
@ -388,11 +389,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_optimizer_creation() {
|
fn test_optimizer_creation() {
|
||||||
let optimizer = Optimizer::new(
|
let optimizer = Optimizer::new(OptimizationLevel::Size, StripOptions::default(), None);
|
||||||
OptimizationLevel::Size,
|
|
||||||
StripOptions::default(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
assert_eq!(optimizer.level, OptimizationLevel::Size);
|
assert_eq!(optimizer.level, OptimizationLevel::Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,8 @@ impl Validator {
|
||||||
}
|
}
|
||||||
Payload::ImportSection(reader) => {
|
Payload::ImportSection(reader) => {
|
||||||
for import in reader {
|
for import in reader {
|
||||||
let import = import.map_err(|e| ValidationError::ParseError(e.to_string()))?;
|
let import =
|
||||||
|
import.map_err(|e| ValidationError::ParseError(e.to_string()))?;
|
||||||
|
|
||||||
let kind = match import.ty {
|
let kind = match import.ty {
|
||||||
wasmparser::TypeRef::Func(type_idx) => ImportKind::Function(type_idx),
|
wasmparser::TypeRef::Func(type_idx) => ImportKind::Function(type_idx),
|
||||||
|
|
@ -339,7 +340,8 @@ impl Validator {
|
||||||
}
|
}
|
||||||
Payload::MemorySection(reader) => {
|
Payload::MemorySection(reader) => {
|
||||||
for memory in reader {
|
for memory in reader {
|
||||||
let memory = memory.map_err(|e| ValidationError::ParseError(e.to_string()))?;
|
let memory =
|
||||||
|
memory.map_err(|e| ValidationError::ParseError(e.to_string()))?;
|
||||||
memories.push(MemoryInfo {
|
memories.push(MemoryInfo {
|
||||||
min_pages: memory.initial,
|
min_pages: memory.initial,
|
||||||
max_pages: memory.maximum,
|
max_pages: memory.maximum,
|
||||||
|
|
@ -350,7 +352,8 @@ impl Validator {
|
||||||
}
|
}
|
||||||
Payload::GlobalSection(reader) => {
|
Payload::GlobalSection(reader) => {
|
||||||
for global in reader {
|
for global in reader {
|
||||||
let global = global.map_err(|e| ValidationError::ParseError(e.to_string()))?;
|
let global =
|
||||||
|
global.map_err(|e| ValidationError::ParseError(e.to_string()))?;
|
||||||
if global.ty.mutable {
|
if global.ty.mutable {
|
||||||
has_mutable_globals = true;
|
has_mutable_globals = true;
|
||||||
}
|
}
|
||||||
|
|
@ -358,7 +361,8 @@ impl Validator {
|
||||||
}
|
}
|
||||||
Payload::ExportSection(reader) => {
|
Payload::ExportSection(reader) => {
|
||||||
for export in reader {
|
for export in reader {
|
||||||
let export = export.map_err(|e| ValidationError::ParseError(e.to_string()))?;
|
let export =
|
||||||
|
export.map_err(|e| ValidationError::ParseError(e.to_string()))?;
|
||||||
|
|
||||||
let kind = match export.kind {
|
let kind = match export.kind {
|
||||||
wasmparser::ExternalKind::Func => ExportKind::Function,
|
wasmparser::ExternalKind::Func => ExportKind::Function,
|
||||||
|
|
@ -404,10 +408,14 @@ impl Validator {
|
||||||
|
|
||||||
// Validate entry points (warn if missing, don't error)
|
// Validate entry points (warn if missing, don't error)
|
||||||
if !exports.iter().any(|e| e.name == "__synor_init") {
|
if !exports.iter().any(|e| e.name == "__synor_init") {
|
||||||
result.warnings.push("Missing __synor_init export - contract cannot be initialized".into());
|
result
|
||||||
|
.warnings
|
||||||
|
.push("Missing __synor_init export - contract cannot be initialized".into());
|
||||||
}
|
}
|
||||||
if !exports.iter().any(|e| e.name == "__synor_call") {
|
if !exports.iter().any(|e| e.name == "__synor_call") {
|
||||||
result.warnings.push("Missing __synor_call export - contract cannot be called".into());
|
result
|
||||||
|
.warnings
|
||||||
|
.push("Missing __synor_call export - contract cannot be called".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate entry point signatures
|
// Validate entry point signatures
|
||||||
|
|
@ -427,12 +435,19 @@ impl Validator {
|
||||||
debug!("Validating {} memories", memories.len());
|
debug!("Validating {} memories", memories.len());
|
||||||
if memories.is_empty() {
|
if memories.is_empty() {
|
||||||
// Check if memory is imported
|
// Check if memory is imported
|
||||||
let has_imported_memory = result.imports.iter().any(|i| matches!(i.kind, ImportKind::Memory));
|
let has_imported_memory = result
|
||||||
|
.imports
|
||||||
|
.iter()
|
||||||
|
.any(|i| matches!(i.kind, ImportKind::Memory));
|
||||||
if !has_imported_memory {
|
if !has_imported_memory {
|
||||||
errors.push(ValidationError::MemoryError("No memory defined or imported".into()));
|
errors.push(ValidationError::MemoryError(
|
||||||
|
"No memory defined or imported".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} else if memories.len() > 1 {
|
} else if memories.len() > 1 {
|
||||||
errors.push(ValidationError::MemoryError("Multiple memories not supported".into()));
|
errors.push(ValidationError::MemoryError(
|
||||||
|
"Multiple memories not supported".into(),
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
let mem = &memories[0];
|
let mem = &memories[0];
|
||||||
|
|
||||||
|
|
@ -445,9 +460,9 @@ impl Validator {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.warnings.push(
|
result
|
||||||
"Memory has no maximum limit - recommended to set max_pages".into(),
|
.warnings
|
||||||
);
|
.push("Memory has no maximum limit - recommended to set max_pages".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for shared memory (threads)
|
// Check for shared memory (threads)
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,7 @@ fn make_outpoint(tx_n: u64, index: u32) -> Outpoint {
|
||||||
|
|
||||||
/// Creates a P2PKH output.
|
/// Creates a P2PKH output.
|
||||||
fn make_p2pkh_output(amount: u64) -> TxOutput {
|
fn make_p2pkh_output(amount: u64) -> TxOutput {
|
||||||
TxOutput::new(
|
TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32]))
|
||||||
Amount::from_sompi(amount),
|
|
||||||
ScriptPubKey::p2pkh(&[0u8; 32]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a UTXO entry.
|
/// Creates a UTXO entry.
|
||||||
|
|
@ -235,9 +232,7 @@ fn utxo_lookup(c: &mut Criterion) {
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::from_parameter(set_size),
|
BenchmarkId::from_parameter(set_size),
|
||||||
&(utxo_set, target),
|
&(utxo_set, target),
|
||||||
|b, (set, target)| {
|
|b, (set, target)| b.iter(|| black_box(set.get(target))),
|
||||||
b.iter(|| black_box(set.get(target)))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,9 +249,7 @@ fn utxo_contains(c: &mut Criterion) {
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::from_parameter(set_size),
|
BenchmarkId::from_parameter(set_size),
|
||||||
&(utxo_set, target),
|
&(utxo_set, target),
|
||||||
|b, (set, target)| {
|
|b, (set, target)| b.iter(|| black_box(set.contains(target))),
|
||||||
b.iter(|| black_box(set.contains(target)))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,7 +321,10 @@ fn utxo_diff_apply(c: &mut Criterion) {
|
||||||
|
|
||||||
// Add new entries
|
// Add new entries
|
||||||
for i in n..(n + n / 2) {
|
for i in n..(n + n / 2) {
|
||||||
diff.add(make_outpoint(i as u64, 0), make_utxo_entry(500_000_000, 100, false));
|
diff.add(
|
||||||
|
make_outpoint(i as u64, 0),
|
||||||
|
make_utxo_entry(500_000_000, 100, false),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(set, diff)
|
(set, diff)
|
||||||
|
|
@ -357,14 +353,20 @@ fn utxo_diff_merge(c: &mut Criterion) {
|
||||||
let mut diff2 = UtxoDiff::new();
|
let mut diff2 = UtxoDiff::new();
|
||||||
|
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
diff1.add(make_outpoint(i as u64, 0), make_utxo_entry(100_000_000, 100, false));
|
diff1.add(
|
||||||
|
make_outpoint(i as u64, 0),
|
||||||
|
make_utxo_entry(100_000_000, 100, false),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..(n / 2) {
|
for i in 0..(n / 2) {
|
||||||
diff2.remove(make_outpoint(i as u64, 0));
|
diff2.remove(make_outpoint(i as u64, 0));
|
||||||
}
|
}
|
||||||
for i in n..(n * 2) {
|
for i in n..(n * 2) {
|
||||||
diff2.add(make_outpoint(i as u64, 0), make_utxo_entry(200_000_000, 100, false));
|
diff2.add(
|
||||||
|
make_outpoint(i as u64, 0),
|
||||||
|
make_utxo_entry(200_000_000, 100, false),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(diff1, diff2)
|
(diff1, diff2)
|
||||||
|
|
@ -400,9 +402,7 @@ fn utxo_create_tx_diff(c: &mut Criterion) {
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::from_parameter(input_count),
|
BenchmarkId::from_parameter(input_count),
|
||||||
&(utxo_set, tx),
|
&(utxo_set, tx),
|
||||||
|b, (set, tx)| {
|
|b, (set, tx)| b.iter(|| black_box(set.create_transaction_diff(tx, 1000))),
|
||||||
b.iter(|| black_box(set.create_transaction_diff(tx, 1000)))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -434,9 +434,7 @@ fn batch_tx_validation(c: &mut Criterion) {
|
||||||
let validator = TransactionValidator::new();
|
let validator = TransactionValidator::new();
|
||||||
|
|
||||||
for batch_size in [10, 50, 100] {
|
for batch_size in [10, 50, 100] {
|
||||||
let transactions: Vec<_> = (0..batch_size)
|
let transactions: Vec<_> = (0..batch_size).map(|_| make_transaction(2, 2)).collect();
|
||||||
.map(|_| make_transaction(2, 2))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
group.throughput(Throughput::Elements(batch_size as u64));
|
group.throughput(Throughput::Elements(batch_size as u64));
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
|
|
@ -486,11 +484,7 @@ criterion_group!(
|
||||||
utxo_create_tx_diff,
|
utxo_create_tx_diff,
|
||||||
);
|
);
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(misc_benches, utxo_get_balance, batch_tx_validation,);
|
||||||
misc_benches,
|
|
||||||
utxo_get_balance,
|
|
||||||
batch_tx_validation,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_main!(
|
criterion_main!(
|
||||||
tx_validation_benches,
|
tx_validation_benches,
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
use synor_types::{
|
use synor_types::{
|
||||||
block::BlockBody,
|
block::BlockBody,
|
||||||
transaction::{Outpoint, ScriptPubKey, ScriptType, SubnetworkId},
|
transaction::{Outpoint, ScriptPubKey, ScriptType, SubnetworkId},
|
||||||
Amount, Block, BlockHeader, BlueScore, Hash256, Network, Timestamp,
|
Amount, Block, BlockHeader, BlueScore, Hash256, Network, Timestamp, Transaction, TxInput,
|
||||||
Transaction, TxInput, TxOutput,
|
TxOutput,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Chain configuration parameters.
|
/// Chain configuration parameters.
|
||||||
|
|
@ -59,23 +59,23 @@ impl ChainConfig {
|
||||||
network: Network::Mainnet,
|
network: Network::Mainnet,
|
||||||
genesis,
|
genesis,
|
||||||
genesis_hash,
|
genesis_hash,
|
||||||
target_block_time_ms: 100, // 10 bps
|
target_block_time_ms: 100, // 10 bps
|
||||||
ghostdag_k: 18,
|
ghostdag_k: 18,
|
||||||
max_block_parents: 64,
|
max_block_parents: 64,
|
||||||
max_block_mass: 500_000,
|
max_block_mass: 500_000,
|
||||||
coinbase_maturity: 100,
|
coinbase_maturity: 100,
|
||||||
finality_depth: 86_400, // ~24 hours
|
finality_depth: 86_400, // ~24 hours
|
||||||
pruning_depth: 288_000, // ~8 hours of data
|
pruning_depth: 288_000, // ~8 hours of data
|
||||||
initial_difficulty: 0x1d00ffff, // Bitcoin-style initial difficulty
|
initial_difficulty: 0x1d00ffff, // Bitcoin-style initial difficulty
|
||||||
halving_interval: 315_360_000, // ~1 year at 10 bps
|
halving_interval: 315_360_000, // ~1 year at 10 bps
|
||||||
initial_reward: 500 * 100_000_000, // 500 SYNOR
|
initial_reward: 500 * 100_000_000, // 500 SYNOR
|
||||||
dns_seeds: vec![
|
dns_seeds: vec![
|
||||||
"seed1.synor.cc".to_string(),
|
"seed1.synor.cc".to_string(),
|
||||||
"seed2.synor.cc".to_string(),
|
"seed2.synor.cc".to_string(),
|
||||||
"seed3.synor.cc".to_string(),
|
"seed3.synor.cc".to_string(),
|
||||||
],
|
],
|
||||||
bootstrap_nodes: vec![],
|
bootstrap_nodes: vec![],
|
||||||
bip32_coin_type: 0x5359, // "SY" in hex
|
bip32_coin_type: 0x5359, // "SY" in hex
|
||||||
address_prefix: "synor".to_string(),
|
address_prefix: "synor".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,11 +93,11 @@ impl ChainConfig {
|
||||||
ghostdag_k: 18,
|
ghostdag_k: 18,
|
||||||
max_block_parents: 64,
|
max_block_parents: 64,
|
||||||
max_block_mass: 500_000,
|
max_block_mass: 500_000,
|
||||||
coinbase_maturity: 10, // Lower for testing
|
coinbase_maturity: 10, // Lower for testing
|
||||||
finality_depth: 1000, // Lower for testing
|
finality_depth: 1000, // Lower for testing
|
||||||
pruning_depth: 5000,
|
pruning_depth: 5000,
|
||||||
initial_difficulty: 0x1f00ffff, // Lower difficulty
|
initial_difficulty: 0x1f00ffff, // Lower difficulty
|
||||||
halving_interval: 10_000, // Quick halvings for testing
|
halving_interval: 10_000, // Quick halvings for testing
|
||||||
initial_reward: 500 * 100_000_000,
|
initial_reward: 500 * 100_000_000,
|
||||||
dns_seeds: vec![
|
dns_seeds: vec![
|
||||||
"testnet-seed1.synor.cc".to_string(),
|
"testnet-seed1.synor.cc".to_string(),
|
||||||
|
|
@ -118,20 +118,18 @@ impl ChainConfig {
|
||||||
network: Network::Devnet,
|
network: Network::Devnet,
|
||||||
genesis,
|
genesis,
|
||||||
genesis_hash,
|
genesis_hash,
|
||||||
target_block_time_ms: 1000, // 1 second blocks for dev
|
target_block_time_ms: 1000, // 1 second blocks for dev
|
||||||
ghostdag_k: 3, // Smaller K for dev
|
ghostdag_k: 3, // Smaller K for dev
|
||||||
max_block_parents: 10,
|
max_block_parents: 10,
|
||||||
max_block_mass: 100_000,
|
max_block_mass: 100_000,
|
||||||
coinbase_maturity: 1,
|
coinbase_maturity: 1,
|
||||||
finality_depth: 10,
|
finality_depth: 10,
|
||||||
pruning_depth: 100,
|
pruning_depth: 100,
|
||||||
initial_difficulty: 0x207fffff, // Minimum difficulty
|
initial_difficulty: 0x207fffff, // Minimum difficulty
|
||||||
halving_interval: 100,
|
halving_interval: 100,
|
||||||
initial_reward: 1000 * 100_000_000, // Higher reward for dev
|
initial_reward: 1000 * 100_000_000, // Higher reward for dev
|
||||||
dns_seeds: vec![],
|
dns_seeds: vec![],
|
||||||
bootstrap_nodes: vec![
|
bootstrap_nodes: vec!["/ip4/127.0.0.1/tcp/16511".to_string()],
|
||||||
"/ip4/127.0.0.1/tcp/16511".to_string(),
|
|
||||||
],
|
|
||||||
bip32_coin_type: 0x5359,
|
bip32_coin_type: 0x5359,
|
||||||
address_prefix: "dsynor".to_string(),
|
address_prefix: "dsynor".to_string(),
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +145,11 @@ impl ChainConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates a block against chain rules.
|
/// Validates a block against chain rules.
|
||||||
pub fn validate_block_header(&self, header: &BlockHeader, _parent_blue_score: u64) -> Result<(), GenesisError> {
|
pub fn validate_block_header(
|
||||||
|
&self,
|
||||||
|
header: &BlockHeader,
|
||||||
|
_parent_blue_score: u64,
|
||||||
|
) -> Result<(), GenesisError> {
|
||||||
// Check timestamp is reasonable
|
// Check timestamp is reasonable
|
||||||
let now_ms = Timestamp::now().as_millis();
|
let now_ms = Timestamp::now().as_millis();
|
||||||
let max_future = 2 * 60 * 60 * 1000; // 2 hours
|
let max_future = 2 * 60 * 60 * 1000; // 2 hours
|
||||||
|
|
@ -195,7 +197,8 @@ fn create_mainnet_genesis() -> Block {
|
||||||
let genesis_timestamp = Timestamp::from_millis(1735689600000);
|
let genesis_timestamp = Timestamp::from_millis(1735689600000);
|
||||||
|
|
||||||
// Genesis message embedded in coinbase
|
// Genesis message embedded in coinbase
|
||||||
let genesis_message = b"Synor Genesis - The Dawn of Quantum-Secure Decentralized Computing - 2025";
|
let genesis_message =
|
||||||
|
b"Synor Genesis - The Dawn of Quantum-Secure Decentralized Computing - 2025";
|
||||||
|
|
||||||
// Create coinbase transaction
|
// Create coinbase transaction
|
||||||
let coinbase_tx = create_coinbase_transaction(
|
let coinbase_tx = create_coinbase_transaction(
|
||||||
|
|
@ -209,7 +212,7 @@ fn create_mainnet_genesis() -> Block {
|
||||||
// Create genesis header
|
// Create genesis header
|
||||||
let header = BlockHeader {
|
let header = BlockHeader {
|
||||||
version: 1,
|
version: 1,
|
||||||
parents: vec![], // Genesis has no parents
|
parents: vec![], // Genesis has no parents
|
||||||
merkle_root: txid,
|
merkle_root: txid,
|
||||||
accepted_id_merkle_root: Hash256::ZERO,
|
accepted_id_merkle_root: Hash256::ZERO,
|
||||||
utxo_commitment: Hash256::ZERO,
|
utxo_commitment: Hash256::ZERO,
|
||||||
|
|
@ -342,24 +345,20 @@ fn genesis_allocation_mainnet() -> Vec<GenesisAllocation> {
|
||||||
|
|
||||||
/// Returns testnet genesis allocations.
|
/// Returns testnet genesis allocations.
|
||||||
fn genesis_allocation_testnet() -> Vec<GenesisAllocation> {
|
fn genesis_allocation_testnet() -> Vec<GenesisAllocation> {
|
||||||
vec![
|
vec![GenesisAllocation {
|
||||||
GenesisAllocation {
|
address_hash: Hash256::from([0x10; 32]),
|
||||||
address_hash: Hash256::from([0x10; 32]),
|
amount: Amount::from_synor(1_000_000),
|
||||||
amount: Amount::from_synor(1_000_000),
|
description: "Testnet Faucet".to_string(),
|
||||||
description: "Testnet Faucet".to_string(),
|
}]
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns devnet genesis allocations.
|
/// Returns devnet genesis allocations.
|
||||||
fn genesis_allocation_devnet() -> Vec<GenesisAllocation> {
|
fn genesis_allocation_devnet() -> Vec<GenesisAllocation> {
|
||||||
vec![
|
vec![GenesisAllocation {
|
||||||
GenesisAllocation {
|
address_hash: Hash256::from([0x20; 32]),
|
||||||
address_hash: Hash256::from([0x20; 32]),
|
amount: Amount::from_synor(100_000_000),
|
||||||
amount: Amount::from_synor(100_000_000),
|
description: "Dev Account".to_string(),
|
||||||
description: "Dev Account".to_string(),
|
}]
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a coinbase transaction for genesis.
|
/// Creates a coinbase transaction for genesis.
|
||||||
|
|
@ -430,13 +429,11 @@ pub fn mainnet_checkpoints() -> Vec<Checkpoint> {
|
||||||
|
|
||||||
/// Returns hardcoded checkpoints for testnet.
|
/// Returns hardcoded checkpoints for testnet.
|
||||||
pub fn testnet_checkpoints() -> Vec<Checkpoint> {
|
pub fn testnet_checkpoints() -> Vec<Checkpoint> {
|
||||||
vec![
|
vec![Checkpoint {
|
||||||
Checkpoint {
|
blue_score: 0,
|
||||||
blue_score: 0,
|
hash: ChainConfig::testnet().genesis_hash,
|
||||||
hash: ChainConfig::testnet().genesis_hash,
|
timestamp: 1735689600000,
|
||||||
timestamp: 1735689600000,
|
}]
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Network magic bytes for message framing.
|
/// Network magic bytes for message framing.
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,10 @@ impl FeeDistribution {
|
||||||
let burn = Amount::from_sompi(fee_sompi * 10 / 100);
|
let burn = Amount::from_sompi(fee_sompi * 10 / 100);
|
||||||
let stakers = Amount::from_sompi(fee_sompi * 60 / 100);
|
let stakers = Amount::from_sompi(fee_sompi * 60 / 100);
|
||||||
let community = Amount::from_sompi(fee_sompi * 20 / 100);
|
let community = Amount::from_sompi(fee_sompi * 20 / 100);
|
||||||
let miner = fees.saturating_sub(burn).saturating_sub(stakers).saturating_sub(community);
|
let miner = fees
|
||||||
|
.saturating_sub(burn)
|
||||||
|
.saturating_sub(stakers)
|
||||||
|
.saturating_sub(community);
|
||||||
|
|
||||||
FeeDistribution {
|
FeeDistribution {
|
||||||
burn,
|
burn,
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,10 @@ impl UtxoSet {
|
||||||
|
|
||||||
/// Creates a UTXO set from existing entries.
|
/// Creates a UTXO set from existing entries.
|
||||||
pub fn from_entries(entries: HashMap<Outpoint, UtxoEntry>) -> Self {
|
pub fn from_entries(entries: HashMap<Outpoint, UtxoEntry>) -> Self {
|
||||||
let total: Amount = entries.values().map(|e| e.amount()).fold(Amount::ZERO, |a, b| a.saturating_add(b));
|
let total: Amount = entries
|
||||||
|
.values()
|
||||||
|
.map(|e| e.amount())
|
||||||
|
.fold(Amount::ZERO, |a, b| a.saturating_add(b));
|
||||||
let count = entries.len();
|
let count = entries.len();
|
||||||
UtxoSet {
|
UtxoSet {
|
||||||
utxos: RwLock::new(entries),
|
utxos: RwLock::new(entries),
|
||||||
|
|
@ -278,7 +281,11 @@ impl UtxoSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets all UTXOs for a given address.
|
/// Gets all UTXOs for a given address.
|
||||||
pub fn get_by_address(&self, address: &Address, network: synor_types::Network) -> Vec<(Outpoint, UtxoEntry)> {
|
pub fn get_by_address(
|
||||||
|
&self,
|
||||||
|
address: &Address,
|
||||||
|
network: synor_types::Network,
|
||||||
|
) -> Vec<(Outpoint, UtxoEntry)> {
|
||||||
let utxos = self.utxos.read();
|
let utxos = self.utxos.read();
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
|
@ -410,7 +417,11 @@ impl VirtualUtxoState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the virtual block and applies necessary diffs.
|
/// Updates the virtual block and applies necessary diffs.
|
||||||
pub fn update_virtual(&self, new_virtual: Hash256, chain_diffs: Vec<UtxoDiff>) -> Result<(), UtxoError> {
|
pub fn update_virtual(
|
||||||
|
&self,
|
||||||
|
new_virtual: Hash256,
|
||||||
|
chain_diffs: Vec<UtxoDiff>,
|
||||||
|
) -> Result<(), UtxoError> {
|
||||||
// Apply all diffs in order
|
// Apply all diffs in order
|
||||||
for diff in chain_diffs {
|
for diff in chain_diffs {
|
||||||
self.base.apply_diff(&diff)?;
|
self.base.apply_diff(&diff)?;
|
||||||
|
|
@ -470,10 +481,7 @@ mod tests {
|
||||||
|
|
||||||
fn make_entry(amount: u64) -> UtxoEntry {
|
fn make_entry(amount: u64) -> UtxoEntry {
|
||||||
UtxoEntry::new(
|
UtxoEntry::new(
|
||||||
TxOutput::new(
|
TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32])),
|
||||||
Amount::from_sompi(amount),
|
|
||||||
ScriptPubKey::p2pkh(&[0u8; 32]),
|
|
||||||
),
|
|
||||||
100,
|
100,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
@ -481,10 +489,7 @@ mod tests {
|
||||||
|
|
||||||
fn make_coinbase_entry(amount: u64, daa_score: u64) -> UtxoEntry {
|
fn make_coinbase_entry(amount: u64, daa_score: u64) -> UtxoEntry {
|
||||||
UtxoEntry::new(
|
UtxoEntry::new(
|
||||||
TxOutput::new(
|
TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32])),
|
||||||
Amount::from_sompi(amount),
|
|
||||||
ScriptPubKey::p2pkh(&[0u8; 32]),
|
|
||||||
),
|
|
||||||
daa_score,
|
daa_score,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,11 @@ impl TransactionValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies an input's signature script against the UTXO.
|
/// Verifies an input's signature script against the UTXO.
|
||||||
fn verify_input_script(&self, input: &TxInput, utxo: &UtxoEntry) -> Result<(), ValidationError> {
|
fn verify_input_script(
|
||||||
|
&self,
|
||||||
|
input: &TxInput,
|
||||||
|
utxo: &UtxoEntry,
|
||||||
|
) -> Result<(), ValidationError> {
|
||||||
// Simplified verification - in production would:
|
// Simplified verification - in production would:
|
||||||
// 1. Parse the signature script
|
// 1. Parse the signature script
|
||||||
// 2. Execute against the UTXO's script pubkey
|
// 2. Execute against the UTXO's script pubkey
|
||||||
|
|
@ -282,15 +286,16 @@ impl BlockValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates proof of work.
|
/// Validates proof of work.
|
||||||
pub fn validate_pow(&self, header: &BlockHeader, target: Hash256) -> Result<(), ValidationError> {
|
pub fn validate_pow(
|
||||||
|
&self,
|
||||||
|
header: &BlockHeader,
|
||||||
|
target: Hash256,
|
||||||
|
) -> Result<(), ValidationError> {
|
||||||
let hash = header.block_id();
|
let hash = header.block_id();
|
||||||
|
|
||||||
// Check hash is below target
|
// Check hash is below target
|
||||||
if hash > target {
|
if hash > target {
|
||||||
return Err(ValidationError::InsufficientPow {
|
return Err(ValidationError::InsufficientPow { hash, target });
|
||||||
hash,
|
|
||||||
target,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -340,9 +345,11 @@ impl BlockValidator {
|
||||||
|
|
||||||
// Validate against UTXOs (skip coinbase)
|
// Validate against UTXOs (skip coinbase)
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
let fee = self
|
let fee = self.tx_validator.validate_against_utxos(
|
||||||
.tx_validator
|
tx,
|
||||||
.validate_against_utxos(tx, utxo_set, block.header.daa_score)?;
|
utxo_set,
|
||||||
|
block.header.daa_score,
|
||||||
|
)?;
|
||||||
total_fees = total_fees.saturating_add(fee);
|
total_fees = total_fees.saturating_add(fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -468,7 +475,10 @@ pub enum ValidationError {
|
||||||
InvalidCoinbaseAmount { output: Amount, max: Amount },
|
InvalidCoinbaseAmount { output: Amount, max: Amount },
|
||||||
|
|
||||||
#[error("Invalid merkle root: expected {expected}, computed {computed}")]
|
#[error("Invalid merkle root: expected {expected}, computed {computed}")]
|
||||||
InvalidMerkleRoot { expected: Hash256, computed: Hash256 },
|
InvalidMerkleRoot {
|
||||||
|
expected: Hash256,
|
||||||
|
computed: Hash256,
|
||||||
|
},
|
||||||
|
|
||||||
#[error("UTXO error: {0}")]
|
#[error("UTXO error: {0}")]
|
||||||
UtxoError(#[from] UtxoError),
|
UtxoError(#[from] UtxoError),
|
||||||
|
|
@ -481,10 +491,7 @@ mod tests {
|
||||||
use synor_types::TxOutput;
|
use synor_types::TxOutput;
|
||||||
|
|
||||||
fn make_p2pkh_output(amount: u64) -> TxOutput {
|
fn make_p2pkh_output(amount: u64) -> TxOutput {
|
||||||
TxOutput::new(
|
TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32]))
|
||||||
Amount::from_sompi(amount),
|
|
||||||
ScriptPubKey::p2pkh(&[0u8; 32]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,12 @@ impl TestEnvironment {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create execution context
|
// Create execution context
|
||||||
let call = CallContext::new(contract_id, deployer_addr.clone(), value, init_params.to_vec());
|
let call = CallContext::new(
|
||||||
|
contract_id,
|
||||||
|
deployer_addr.clone(),
|
||||||
|
value,
|
||||||
|
init_params.to_vec(),
|
||||||
|
);
|
||||||
|
|
||||||
let mut storage = MemoryStorage::new();
|
let mut storage = MemoryStorage::new();
|
||||||
// Copy existing storage for this contract if any
|
// Copy existing storage for this contract if any
|
||||||
|
|
|
||||||
|
|
@ -100,11 +100,7 @@ impl MockStorage {
|
||||||
|
|
||||||
/// Gets the number of storage entries for a contract.
|
/// Gets the number of storage entries for a contract.
|
||||||
pub fn len(&self, contract: &ContractId) -> usize {
|
pub fn len(&self, contract: &ContractId) -> usize {
|
||||||
self.data
|
self.data.read().get(contract).map(|m| m.len()).unwrap_or(0)
|
||||||
.read()
|
|
||||||
.get(contract)
|
|
||||||
.map(|m| m.len())
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a contract has any storage.
|
/// Checks if a contract has any storage.
|
||||||
|
|
@ -362,7 +358,10 @@ impl StorageSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets storage entries for a contract.
|
/// Gets storage entries for a contract.
|
||||||
pub fn get_contract(&self, contract: &ContractId) -> Option<&HashMap<StorageKey, StorageValue>> {
|
pub fn get_contract(
|
||||||
|
&self,
|
||||||
|
contract: &ContractId,
|
||||||
|
) -> Option<&HashMap<StorageKey, StorageValue>> {
|
||||||
self.data.get(contract)
|
self.data.get(contract)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -262,8 +262,7 @@ impl TestAccountBuilder {
|
||||||
|
|
||||||
/// Adds a new account with a specific balance.
|
/// Adds a new account with a specific balance.
|
||||||
pub fn add_account(mut self, balance: u64) -> Self {
|
pub fn add_account(mut self, balance: u64) -> Self {
|
||||||
let account = TestAccount::from_index(self.accounts.len() as u64)
|
let account = TestAccount::from_index(self.accounts.len() as u64).set_balance(balance);
|
||||||
.set_balance(balance);
|
|
||||||
self.accounts.push(account);
|
self.accounts.push(account);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -271,8 +270,7 @@ impl TestAccountBuilder {
|
||||||
/// Adds multiple accounts with the same balance.
|
/// Adds multiple accounts with the same balance.
|
||||||
pub fn add_accounts(mut self, count: usize, balance: u64) -> Self {
|
pub fn add_accounts(mut self, count: usize, balance: u64) -> Self {
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
let account = TestAccount::from_index(self.accounts.len() as u64)
|
let account = TestAccount::from_index(self.accounts.len() as u64).set_balance(balance);
|
||||||
.set_balance(balance);
|
|
||||||
self.accounts.push(account);
|
self.accounts.push(account);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
|
|
|
||||||
|
|
@ -238,11 +238,7 @@ criterion_group!(
|
||||||
|
|
||||||
criterion_group!(hash_benches, blake3_hash_benchmark, blake3_hash_large,);
|
criterion_group!(hash_benches, blake3_hash_benchmark, blake3_hash_large,);
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(misc_benches, address_derivation_benchmark, signature_sizes,);
|
||||||
misc_benches,
|
|
||||||
address_derivation_benchmark,
|
|
||||||
signature_sizes,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_main!(
|
criterion_main!(
|
||||||
keygen_benches,
|
keygen_benches,
|
||||||
|
|
|
||||||
|
|
@ -58,13 +58,19 @@ pub fn derive_ed25519_key(master_seed: &[u8], account: u32) -> Result<DerivedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derives a 32-byte seed for Dilithium key generation.
|
/// Derives a 32-byte seed for Dilithium key generation.
|
||||||
pub fn derive_dilithium_seed(master_seed: &[u8], account: u32) -> Result<DerivedKey, DeriveKeyError> {
|
pub fn derive_dilithium_seed(
|
||||||
|
master_seed: &[u8],
|
||||||
|
account: u32,
|
||||||
|
) -> Result<DerivedKey, DeriveKeyError> {
|
||||||
let info = [domain::DILITHIUM_KEY, &account.to_be_bytes()].concat();
|
let info = [domain::DILITHIUM_KEY, &account.to_be_bytes()].concat();
|
||||||
derive_key(master_seed, b"", &info, 32)
|
derive_key(master_seed, b"", &info, 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derives a 32-byte encryption key.
|
/// Derives a 32-byte encryption key.
|
||||||
pub fn derive_encryption_key(master_seed: &[u8], purpose: &[u8]) -> Result<DerivedKey, DeriveKeyError> {
|
pub fn derive_encryption_key(
|
||||||
|
master_seed: &[u8],
|
||||||
|
purpose: &[u8],
|
||||||
|
) -> Result<DerivedKey, DeriveKeyError> {
|
||||||
let info = [domain::ENCRYPTION_KEY, purpose].concat();
|
let info = [domain::ENCRYPTION_KEY, purpose].concat();
|
||||||
derive_key(master_seed, b"", &info, 32)
|
derive_key(master_seed, b"", &info, 32)
|
||||||
}
|
}
|
||||||
|
|
@ -81,8 +87,8 @@ pub fn derive_child_key(
|
||||||
return Err(DeriveKeyError::InvalidKeyLength);
|
return Err(DeriveKeyError::InvalidKeyLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut hmac = Hmac::<Sha3_256>::new_from_slice(chain_code)
|
let mut hmac =
|
||||||
.map_err(|_| DeriveKeyError::HmacError)?;
|
Hmac::<Sha3_256>::new_from_slice(chain_code).map_err(|_| DeriveKeyError::HmacError)?;
|
||||||
|
|
||||||
// For hardened keys (index >= 0x80000000), use parent key
|
// For hardened keys (index >= 0x80000000), use parent key
|
||||||
// For normal keys, use parent public key (not implemented here for simplicity)
|
// For normal keys, use parent public key (not implemented here for simplicity)
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,7 @@
|
||||||
//! Provides hybrid keypairs combining Ed25519 and Dilithium3 for quantum resistance.
|
//! Provides hybrid keypairs combining Ed25519 and Dilithium3 for quantum resistance.
|
||||||
|
|
||||||
use crate::{mnemonic::Mnemonic, signature::HybridSignature, Address, Hash256, Network};
|
use crate::{mnemonic::Mnemonic, signature::HybridSignature, Address, Hash256, Network};
|
||||||
use ed25519_dalek::{
|
use ed25519_dalek::{Signer, SigningKey as Ed25519SigningKey, VerifyingKey as Ed25519VerifyingKey};
|
||||||
Signer, SigningKey as Ed25519SigningKey, VerifyingKey as Ed25519VerifyingKey,
|
|
||||||
};
|
|
||||||
use pqcrypto_dilithium::dilithium3;
|
use pqcrypto_dilithium::dilithium3;
|
||||||
use pqcrypto_traits::sign::{PublicKey as PqPublicKey, SignedMessage as PqSignedMessage};
|
use pqcrypto_traits::sign::{PublicKey as PqPublicKey, SignedMessage as PqSignedMessage};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
|
|
@ -80,7 +78,7 @@ impl Dilithium3Keypair {
|
||||||
/// Creates a keypair from seed bytes using deterministic key generation.
|
/// Creates a keypair from seed bytes using deterministic key generation.
|
||||||
pub fn from_seed(seed: &[u8; 32]) -> Self {
|
pub fn from_seed(seed: &[u8; 32]) -> Self {
|
||||||
// Use SHAKE256 to expand seed to required size
|
// Use SHAKE256 to expand seed to required size
|
||||||
use sha3::{Shake256, digest::Update};
|
use sha3::{digest::Update, Shake256};
|
||||||
|
|
||||||
let mut hasher = Shake256::default();
|
let mut hasher = Shake256::default();
|
||||||
hasher.update(b"synor-dilithium3-keygen");
|
hasher.update(b"synor-dilithium3-keygen");
|
||||||
|
|
@ -170,7 +168,8 @@ impl PublicKey {
|
||||||
let ed_verifying_key = Ed25519VerifyingKey::from_bytes(&self.ed25519)
|
let ed_verifying_key = Ed25519VerifyingKey::from_bytes(&self.ed25519)
|
||||||
.map_err(|_| KeypairError::InvalidPublicKey)?;
|
.map_err(|_| KeypairError::InvalidPublicKey)?;
|
||||||
|
|
||||||
let ed_sig_array = signature.ed25519_array()
|
let ed_sig_array = signature
|
||||||
|
.ed25519_array()
|
||||||
.ok_or(KeypairError::InvalidSignature)?;
|
.ok_or(KeypairError::InvalidSignature)?;
|
||||||
let ed_sig = ed25519_dalek::Signature::from_bytes(&ed_sig_array);
|
let ed_sig = ed25519_dalek::Signature::from_bytes(&ed_sig_array);
|
||||||
ed_verifying_key
|
ed_verifying_key
|
||||||
|
|
|
||||||
|
|
@ -87,15 +87,15 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
pub mod keypair;
|
|
||||||
pub mod signature;
|
|
||||||
pub mod mnemonic;
|
|
||||||
pub mod kdf;
|
pub mod kdf;
|
||||||
|
pub mod keypair;
|
||||||
|
pub mod mnemonic;
|
||||||
|
pub mod signature;
|
||||||
|
|
||||||
pub use keypair::{HybridKeypair, Ed25519Keypair, PublicKey, SecretKey};
|
|
||||||
pub use signature::{HybridSignature, Signature, SignatureError};
|
|
||||||
pub use mnemonic::{Mnemonic, MnemonicError};
|
|
||||||
pub use kdf::{derive_key, DeriveKeyError};
|
pub use kdf::{derive_key, DeriveKeyError};
|
||||||
|
pub use keypair::{Ed25519Keypair, HybridKeypair, PublicKey, SecretKey};
|
||||||
|
pub use mnemonic::{Mnemonic, MnemonicError};
|
||||||
|
pub use signature::{HybridSignature, Signature, SignatureError};
|
||||||
|
|
||||||
/// Re-export common types
|
/// Re-export common types
|
||||||
pub use synor_types::{Address, Hash256, Network};
|
pub use synor_types::{Address, Hash256, Network};
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,13 @@ impl std::fmt::Display for Mnemonic {
|
||||||
// Only show first and last word for security
|
// Only show first and last word for security
|
||||||
let words: Vec<&str> = self.words();
|
let words: Vec<&str> = self.words();
|
||||||
if words.len() >= 2 {
|
if words.len() >= 2 {
|
||||||
write!(f, "{} ... {} ({} words)", words[0], words[words.len() - 1], words.len())
|
write!(
|
||||||
|
f,
|
||||||
|
"{} ... {} ({} words)",
|
||||||
|
words[0],
|
||||||
|
words[words.len() - 1],
|
||||||
|
words.len()
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "[invalid mnemonic]")
|
write!(f, "[invalid mnemonic]")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use synor_dag::{
|
use synor_dag::{BlockDag, BlockId, GhostdagManager, ReachabilityStore, GHOSTDAG_K};
|
||||||
BlockDag, BlockId, GhostdagManager, ReachabilityStore, GHOSTDAG_K,
|
|
||||||
};
|
|
||||||
use synor_types::Hash256;
|
use synor_types::Hash256;
|
||||||
|
|
||||||
/// Helper to create deterministic block IDs.
|
/// Helper to create deterministic block IDs.
|
||||||
|
|
@ -46,7 +44,8 @@ fn build_linear_chain(
|
||||||
let block = make_block_id(i as u64);
|
let block = make_block_id(i as u64);
|
||||||
let parent = blocks[i - 1];
|
let parent = blocks[i - 1];
|
||||||
|
|
||||||
dag.insert_block(block, vec![parent], i as u64 * 100).unwrap();
|
dag.insert_block(block, vec![parent], i as u64 * 100)
|
||||||
|
.unwrap();
|
||||||
reachability.add_block(block, parent, &[parent]).unwrap();
|
reachability.add_block(block, parent, &[parent]).unwrap();
|
||||||
ghostdag.add_block(block, &[parent]).unwrap();
|
ghostdag.add_block(block, &[parent]).unwrap();
|
||||||
blocks.push(block);
|
blocks.push(block);
|
||||||
|
|
@ -77,8 +76,11 @@ fn build_wide_dag(
|
||||||
let parents: Vec<_> = layers[d - 1].iter().copied().collect();
|
let parents: Vec<_> = layers[d - 1].iter().copied().collect();
|
||||||
let selected_parent = parents[0];
|
let selected_parent = parents[0];
|
||||||
|
|
||||||
dag.insert_block(block, parents.clone(), block_num * 100).unwrap();
|
dag.insert_block(block, parents.clone(), block_num * 100)
|
||||||
reachability.add_block(block, selected_parent, &parents).unwrap();
|
.unwrap();
|
||||||
|
reachability
|
||||||
|
.add_block(block, selected_parent, &parents)
|
||||||
|
.unwrap();
|
||||||
ghostdag.add_block(block, &parents).unwrap();
|
ghostdag.add_block(block, &parents).unwrap();
|
||||||
|
|
||||||
layer.push(block);
|
layer.push(block);
|
||||||
|
|
@ -113,8 +115,11 @@ fn block_insertion_linear(c: &mut Criterion) {
|
||||||
let new_block = make_block_id(n as u64);
|
let new_block = make_block_id(n as u64);
|
||||||
let parent = *blocks.last().unwrap();
|
let parent = *blocks.last().unwrap();
|
||||||
|
|
||||||
dag.insert_block(new_block, vec![parent], n as u64 * 100).unwrap();
|
dag.insert_block(new_block, vec![parent], n as u64 * 100)
|
||||||
reachability.add_block(new_block, parent, &[parent]).unwrap();
|
.unwrap();
|
||||||
|
reachability
|
||||||
|
.add_block(new_block, parent, &[parent])
|
||||||
|
.unwrap();
|
||||||
black_box(ghostdag.add_block(new_block, &[parent]).unwrap())
|
black_box(ghostdag.add_block(new_block, &[parent]).unwrap())
|
||||||
},
|
},
|
||||||
criterion::BatchSize::SmallInput,
|
criterion::BatchSize::SmallInput,
|
||||||
|
|
@ -146,8 +151,11 @@ fn block_insertion_wide(c: &mut Criterion) {
|
||||||
let parents: Vec<_> = layers.last().unwrap().iter().copied().collect();
|
let parents: Vec<_> = layers.last().unwrap().iter().copied().collect();
|
||||||
let selected_parent = parents[0];
|
let selected_parent = parents[0];
|
||||||
|
|
||||||
dag.insert_block(new_block, parents.clone(), 99999 * 100).unwrap();
|
dag.insert_block(new_block, parents.clone(), 99999 * 100)
|
||||||
reachability.add_block(new_block, selected_parent, &parents).unwrap();
|
.unwrap();
|
||||||
|
reachability
|
||||||
|
.add_block(new_block, selected_parent, &parents)
|
||||||
|
.unwrap();
|
||||||
black_box(ghostdag.add_block(new_block, &parents).unwrap())
|
black_box(ghostdag.add_block(new_block, &parents).unwrap())
|
||||||
},
|
},
|
||||||
criterion::BatchSize::SmallInput,
|
criterion::BatchSize::SmallInput,
|
||||||
|
|
@ -269,8 +277,11 @@ fn merge_set_calculation(c: &mut Criterion) {
|
||||||
let parents: Vec<_> = layers.last().unwrap().iter().copied().collect();
|
let parents: Vec<_> = layers.last().unwrap().iter().copied().collect();
|
||||||
let selected_parent = parents[0];
|
let selected_parent = parents[0];
|
||||||
|
|
||||||
dag.insert_block(new_block, parents.clone(), 99999 * 100).unwrap();
|
dag.insert_block(new_block, parents.clone(), 99999 * 100)
|
||||||
reachability.add_block(new_block, selected_parent, &parents).unwrap();
|
.unwrap();
|
||||||
|
reachability
|
||||||
|
.add_block(new_block, selected_parent, &parents)
|
||||||
|
.unwrap();
|
||||||
let data = ghostdag.add_block(new_block, &parents).unwrap();
|
let data = ghostdag.add_block(new_block, &parents).unwrap();
|
||||||
black_box(data.merge_set_size())
|
black_box(data.merge_set_size())
|
||||||
},
|
},
|
||||||
|
|
@ -294,7 +305,8 @@ fn k_cluster_validation(c: &mut Criterion) {
|
||||||
let genesis = make_block_id(0);
|
let genesis = make_block_id(0);
|
||||||
let dag = Arc::new(BlockDag::new(genesis, 0));
|
let dag = Arc::new(BlockDag::new(genesis, 0));
|
||||||
let reachability = Arc::new(ReachabilityStore::new(genesis));
|
let reachability = Arc::new(ReachabilityStore::new(genesis));
|
||||||
let ghostdag = GhostdagManager::with_k(dag.clone(), reachability.clone(), k_val);
|
let ghostdag =
|
||||||
|
GhostdagManager::with_k(dag.clone(), reachability.clone(), k_val);
|
||||||
build_wide_dag(5, k_val as usize, &dag, &reachability, &ghostdag);
|
build_wide_dag(5, k_val as usize, &dag, &reachability, &ghostdag);
|
||||||
(dag, reachability, ghostdag)
|
(dag, reachability, ghostdag)
|
||||||
},
|
},
|
||||||
|
|
@ -354,9 +366,7 @@ fn dag_tips_query(c: &mut Criterion) {
|
||||||
let (dag, reachability, ghostdag) = setup_dag();
|
let (dag, reachability, ghostdag) = setup_dag();
|
||||||
build_wide_dag(10, 4, &dag, &reachability, &ghostdag);
|
build_wide_dag(10, 4, &dag, &reachability, &ghostdag);
|
||||||
|
|
||||||
c.bench_function("dag_get_tips", |b| {
|
c.bench_function("dag_get_tips", |b| b.iter(|| black_box(dag.tips())));
|
||||||
b.iter(|| black_box(dag.tips()))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Criterion Groups ====================
|
// ==================== Criterion Groups ====================
|
||||||
|
|
@ -367,16 +377,9 @@ criterion_group!(
|
||||||
block_insertion_wide,
|
block_insertion_wide,
|
||||||
);
|
);
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(score_benches, blue_score_calculation, blue_score_batch,);
|
||||||
score_benches,
|
|
||||||
blue_score_calculation,
|
|
||||||
blue_score_batch,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(chain_benches, selected_chain_traversal,);
|
||||||
chain_benches,
|
|
||||||
selected_chain_traversal,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(
|
||||||
reachability_benches,
|
reachability_benches,
|
||||||
|
|
@ -384,11 +387,7 @@ criterion_group!(
|
||||||
reachability_anticone_check,
|
reachability_anticone_check,
|
||||||
);
|
);
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(merge_benches, merge_set_calculation, k_cluster_validation,);
|
||||||
merge_benches,
|
|
||||||
merge_set_calculation,
|
|
||||||
k_cluster_validation,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(
|
||||||
data_access_benches,
|
data_access_benches,
|
||||||
|
|
|
||||||
|
|
@ -449,8 +449,7 @@ mod tests {
|
||||||
let dag = BlockDag::new(genesis_id, 0);
|
let dag = BlockDag::new(genesis_id, 0);
|
||||||
|
|
||||||
let block1 = make_block_id(1);
|
let block1 = make_block_id(1);
|
||||||
dag.insert_block(block1, vec![genesis_id], 100)
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(dag.len(), 2);
|
assert_eq!(dag.len(), 2);
|
||||||
assert!(dag.contains(&block1));
|
assert!(dag.contains(&block1));
|
||||||
|
|
@ -471,8 +470,7 @@ mod tests {
|
||||||
|
|
||||||
// Add a child block
|
// Add a child block
|
||||||
let block1 = make_block_id(1);
|
let block1 = make_block_id(1);
|
||||||
dag.insert_block(block1, vec![genesis_id], 100)
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Now only block1 is a tip
|
// Now only block1 is a tip
|
||||||
assert_eq!(dag.tips().len(), 1);
|
assert_eq!(dag.tips().len(), 1);
|
||||||
|
|
@ -481,8 +479,7 @@ mod tests {
|
||||||
|
|
||||||
// Add another block with genesis as parent (parallel mining)
|
// Add another block with genesis as parent (parallel mining)
|
||||||
let block2 = make_block_id(2);
|
let block2 = make_block_id(2);
|
||||||
dag.insert_block(block2, vec![genesis_id], 100)
|
dag.insert_block(block2, vec![genesis_id], 100).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Now we have two tips
|
// Now we have two tips
|
||||||
assert_eq!(dag.tips().len(), 2);
|
assert_eq!(dag.tips().len(), 2);
|
||||||
|
|
@ -498,15 +495,12 @@ mod tests {
|
||||||
let block1 = make_block_id(1);
|
let block1 = make_block_id(1);
|
||||||
let block2 = make_block_id(2);
|
let block2 = make_block_id(2);
|
||||||
|
|
||||||
dag.insert_block(block1, vec![genesis_id], 100)
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
||||||
.unwrap();
|
dag.insert_block(block2, vec![genesis_id], 100).unwrap();
|
||||||
dag.insert_block(block2, vec![genesis_id], 100)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Block with multiple parents
|
// Block with multiple parents
|
||||||
let block3 = make_block_id(3);
|
let block3 = make_block_id(3);
|
||||||
dag.insert_block(block3, vec![block1, block2], 200)
|
dag.insert_block(block3, vec![block1, block2], 200).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let parents = dag.get_parents(&block3).unwrap();
|
let parents = dag.get_parents(&block3).unwrap();
|
||||||
assert_eq!(parents.len(), 2);
|
assert_eq!(parents.len(), 2);
|
||||||
|
|
@ -523,12 +517,9 @@ mod tests {
|
||||||
let block2 = make_block_id(2);
|
let block2 = make_block_id(2);
|
||||||
let block3 = make_block_id(3);
|
let block3 = make_block_id(3);
|
||||||
|
|
||||||
dag.insert_block(block1, vec![genesis_id], 100)
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
||||||
.unwrap();
|
dag.insert_block(block2, vec![block1], 200).unwrap();
|
||||||
dag.insert_block(block2, vec![block1], 200)
|
dag.insert_block(block3, vec![block2], 300).unwrap();
|
||||||
.unwrap();
|
|
||||||
dag.insert_block(block3, vec![block2], 300)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let ancestors = dag.get_ancestors(&block3, 10);
|
let ancestors = dag.get_ancestors(&block3, 10);
|
||||||
assert!(ancestors.contains(&block2));
|
assert!(ancestors.contains(&block2));
|
||||||
|
|
@ -542,8 +533,7 @@ mod tests {
|
||||||
let dag = BlockDag::new(genesis_id, 0);
|
let dag = BlockDag::new(genesis_id, 0);
|
||||||
|
|
||||||
let block1 = make_block_id(1);
|
let block1 = make_block_id(1);
|
||||||
dag.insert_block(block1, vec![genesis_id], 100)
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Try to insert same block again
|
// Try to insert same block again
|
||||||
let result = dag.insert_block(block1, vec![genesis_id], 100);
|
let result = dag.insert_block(block1, vec![genesis_id], 100);
|
||||||
|
|
@ -571,12 +561,9 @@ mod tests {
|
||||||
let block2 = make_block_id(2);
|
let block2 = make_block_id(2);
|
||||||
let block3 = make_block_id(3);
|
let block3 = make_block_id(3);
|
||||||
|
|
||||||
dag.insert_block(block1, vec![genesis_id], 100)
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
||||||
.unwrap();
|
dag.insert_block(block2, vec![block1], 200).unwrap();
|
||||||
dag.insert_block(block2, vec![block1], 200)
|
dag.insert_block(block3, vec![block2], 300).unwrap();
|
||||||
.unwrap();
|
|
||||||
dag.insert_block(block3, vec![block2], 300)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let order = dag.iter_topological();
|
let order = dag.iter_topological();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -264,8 +264,9 @@ impl GhostdagManager {
|
||||||
) -> Result<(Vec<BlockId>, Vec<BlockId>, HashMap<BlockId, usize>), GhostdagError> {
|
) -> Result<(Vec<BlockId>, Vec<BlockId>, HashMap<BlockId, usize>), GhostdagError> {
|
||||||
let mut blues: Vec<BlockId> = Vec::with_capacity(merge_set.len());
|
let mut blues: Vec<BlockId> = Vec::with_capacity(merge_set.len());
|
||||||
let mut reds: Vec<BlockId> = Vec::new();
|
let mut reds: Vec<BlockId> = Vec::new();
|
||||||
let mut blues_anticone_sizes: HashMap<BlockId, usize> =
|
let mut blues_anticone_sizes: HashMap<BlockId, usize> = HashMap::with_capacity(
|
||||||
HashMap::with_capacity(merge_set.len() + selected_parent_data.blues_anticone_sizes.len());
|
merge_set.len() + selected_parent_data.blues_anticone_sizes.len(),
|
||||||
|
);
|
||||||
|
|
||||||
// Initialize with selected parent's blues anticone sizes
|
// Initialize with selected parent's blues anticone sizes
|
||||||
blues_anticone_sizes.extend(selected_parent_data.blues_anticone_sizes.iter());
|
blues_anticone_sizes.extend(selected_parent_data.blues_anticone_sizes.iter());
|
||||||
|
|
@ -378,7 +379,8 @@ impl GhostdagManager {
|
||||||
new_blue: &BlockId,
|
new_blue: &BlockId,
|
||||||
current_blues: &[BlockId],
|
current_blues: &[BlockId],
|
||||||
) -> Result<bool, GhostdagError> {
|
) -> Result<bool, GhostdagError> {
|
||||||
let (_, anticone_blues) = self.calculate_blues_anticone_optimized(new_blue, current_blues)?;
|
let (_, anticone_blues) =
|
||||||
|
self.calculate_blues_anticone_optimized(new_blue, current_blues)?;
|
||||||
// Build a temporary anticone sizes map for the check
|
// Build a temporary anticone sizes map for the check
|
||||||
let mut temp_sizes = HashMap::new();
|
let mut temp_sizes = HashMap::new();
|
||||||
for blue in current_blues {
|
for blue in current_blues {
|
||||||
|
|
@ -483,7 +485,11 @@ mod tests {
|
||||||
Hash256::from_bytes(bytes)
|
Hash256::from_bytes(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_test_dag() -> (std::sync::Arc<BlockDag>, std::sync::Arc<ReachabilityStore>, GhostdagManager) {
|
fn setup_test_dag() -> (
|
||||||
|
std::sync::Arc<BlockDag>,
|
||||||
|
std::sync::Arc<ReachabilityStore>,
|
||||||
|
GhostdagManager,
|
||||||
|
) {
|
||||||
let genesis = make_block_id(0);
|
let genesis = make_block_id(0);
|
||||||
let dag = std::sync::Arc::new(BlockDag::new(genesis, 0));
|
let dag = std::sync::Arc::new(BlockDag::new(genesis, 0));
|
||||||
let reachability = std::sync::Arc::new(ReachabilityStore::new(genesis));
|
let reachability = std::sync::Arc::new(ReachabilityStore::new(genesis));
|
||||||
|
|
@ -550,7 +556,9 @@ mod tests {
|
||||||
// Now create a block with both branches as parents
|
// Now create a block with both branches as parents
|
||||||
let block4 = make_block_id(4);
|
let block4 = make_block_id(4);
|
||||||
dag.insert_block(block4, vec![block3, block2], 300).unwrap();
|
dag.insert_block(block4, vec![block3, block2], 300).unwrap();
|
||||||
reachability.add_block(block4, block3, &[block3, block2]).unwrap();
|
reachability
|
||||||
|
.add_block(block4, block3, &[block3, block2])
|
||||||
|
.unwrap();
|
||||||
let data4 = ghostdag.add_block(block4, &[block3, block2]).unwrap();
|
let data4 = ghostdag.add_block(block4, &[block3, block2]).unwrap();
|
||||||
|
|
||||||
// Block3 should be selected parent (higher chain)
|
// Block3 should be selected parent (higher chain)
|
||||||
|
|
@ -565,7 +573,8 @@ mod tests {
|
||||||
let mut current = genesis;
|
let mut current = genesis;
|
||||||
for i in 1..=5 {
|
for i in 1..=5 {
|
||||||
let block = make_block_id(i);
|
let block = make_block_id(i);
|
||||||
dag.insert_block(block, vec![current], i as u64 * 100).unwrap();
|
dag.insert_block(block, vec![current], i as u64 * 100)
|
||||||
|
.unwrap();
|
||||||
reachability.add_block(block, current, &[current]).unwrap();
|
reachability.add_block(block, current, &[current]).unwrap();
|
||||||
let data = ghostdag.add_block(block, &[current]).unwrap();
|
let data = ghostdag.add_block(block, &[current]).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,16 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
pub mod dag;
|
pub mod dag;
|
||||||
pub mod reachability;
|
|
||||||
pub mod ghostdag;
|
pub mod ghostdag;
|
||||||
pub mod ordering;
|
pub mod ordering;
|
||||||
pub mod pruning;
|
pub mod pruning;
|
||||||
|
pub mod reachability;
|
||||||
|
|
||||||
pub use dag::{BlockDag, DagError, BlockRelations};
|
pub use dag::{BlockDag, BlockRelations, DagError};
|
||||||
pub use reachability::{ReachabilityStore, ReachabilityError};
|
pub use ghostdag::{GhostdagData, GhostdagError, GhostdagManager};
|
||||||
pub use ghostdag::{GhostdagManager, GhostdagData, GhostdagError};
|
|
||||||
pub use ordering::{BlockOrdering, OrderedBlock};
|
pub use ordering::{BlockOrdering, OrderedBlock};
|
||||||
pub use pruning::{PruningManager, PruningConfig};
|
pub use pruning::{PruningConfig, PruningManager};
|
||||||
|
pub use reachability::{ReachabilityError, ReachabilityStore};
|
||||||
|
|
||||||
use synor_types::Hash256;
|
use synor_types::Hash256;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,7 @@
|
||||||
//! 3. Within the merge set, blues come before reds
|
//! 3. Within the merge set, blues come before reds
|
||||||
//! 4. Within blues/reds, ordering is by blue score then hash
|
//! 4. Within blues/reds, ordering is by blue score then hash
|
||||||
|
|
||||||
use crate::{
|
use crate::{ghostdag::GhostdagManager, BlockId, BlueScore};
|
||||||
ghostdag::GhostdagManager,
|
|
||||||
BlockId, BlueScore,
|
|
||||||
};
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -122,11 +119,7 @@ impl BlockOrdering {
|
||||||
// Add sorted merge blocks
|
// Add sorted merge blocks
|
||||||
for (block_id, blue_score, is_blue) in merge_blocks {
|
for (block_id, blue_score, is_blue) in merge_blocks {
|
||||||
ordering.push(OrderedBlock::new(
|
ordering.push(OrderedBlock::new(
|
||||||
block_id,
|
block_id, position, blue_score, is_blue, true, // Is a merge block
|
||||||
position,
|
|
||||||
blue_score,
|
|
||||||
is_blue,
|
|
||||||
true, // Is a merge block
|
|
||||||
));
|
));
|
||||||
position += 1;
|
position += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +131,11 @@ impl BlockOrdering {
|
||||||
/// Gets only the blocks that need to be processed between two points.
|
/// Gets only the blocks that need to be processed between two points.
|
||||||
///
|
///
|
||||||
/// Returns blocks that are in `to`'s past but not in `from`'s past.
|
/// Returns blocks that are in `to`'s past but not in `from`'s past.
|
||||||
pub fn get_diff(&self, from: &BlockId, to: &BlockId) -> Result<Vec<OrderedBlock>, OrderingError> {
|
pub fn get_diff(
|
||||||
|
&self,
|
||||||
|
from: &BlockId,
|
||||||
|
to: &BlockId,
|
||||||
|
) -> Result<Vec<OrderedBlock>, OrderingError> {
|
||||||
let from_ordering = self.get_ordering(from)?;
|
let from_ordering = self.get_ordering(from)?;
|
||||||
let to_ordering = self.get_ordering(to)?;
|
let to_ordering = self.get_ordering(to)?;
|
||||||
|
|
||||||
|
|
@ -164,14 +161,17 @@ impl BlockOrdering {
|
||||||
|
|
||||||
match (pos_a, pos_b) {
|
match (pos_a, pos_b) {
|
||||||
(Some(pa), Some(pb)) => Ok(pa < pb),
|
(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, Some(_)) => Ok(false), // A is not in past of B
|
||||||
(None, None) => Err(OrderingError::BlocksNotRelated(*a, *b)),
|
(None, None) => Err(OrderingError::BlocksNotRelated(*a, *b)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the merge set at a specific block in canonical order.
|
/// Gets the merge set at a specific block in canonical order.
|
||||||
pub fn get_merge_set_ordered(&self, block_id: &BlockId) -> Result<Vec<OrderedBlock>, OrderingError> {
|
pub fn get_merge_set_ordered(
|
||||||
|
&self,
|
||||||
|
block_id: &BlockId,
|
||||||
|
) -> Result<Vec<OrderedBlock>, OrderingError> {
|
||||||
let data = self.ghostdag.get_data(block_id)?;
|
let data = self.ghostdag.get_data(block_id)?;
|
||||||
let mut merge_blocks = Vec::new();
|
let mut merge_blocks = Vec::new();
|
||||||
let mut position = 0u64;
|
let mut position = 0u64;
|
||||||
|
|
@ -191,15 +191,13 @@ impl BlockOrdering {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by blue score and hash
|
// Sort by blue score and hash
|
||||||
merge_blocks.sort_by(|a, b| {
|
merge_blocks.sort_by(|a, b| match (a.is_blue, b.is_blue) {
|
||||||
match (a.is_blue, b.is_blue) {
|
(true, false) => Ordering::Less,
|
||||||
(true, false) => Ordering::Less,
|
(false, true) => Ordering::Greater,
|
||||||
(false, true) => Ordering::Greater,
|
_ => match b.blue_score.cmp(&a.blue_score) {
|
||||||
_ => match b.blue_score.cmp(&a.blue_score) {
|
Ordering::Equal => a.id.cmp(&b.id),
|
||||||
Ordering::Equal => a.id.cmp(&b.id),
|
other => other,
|
||||||
other => other,
|
},
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update positions after sorting
|
// Update positions after sorting
|
||||||
|
|
@ -333,10 +331,8 @@ mod tests {
|
||||||
let block1 = make_block_id(1);
|
let block1 = make_block_id(1);
|
||||||
let block2 = make_block_id(2);
|
let block2 = make_block_id(2);
|
||||||
|
|
||||||
dag.insert_block(block1, vec![genesis], 100)
|
dag.insert_block(block1, vec![genesis], 100).unwrap();
|
||||||
.unwrap();
|
dag.insert_block(block2, vec![block1], 200).unwrap();
|
||||||
dag.insert_block(block2, vec![block1], 200)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
reachability.add_block(block1, genesis, &[genesis]).unwrap();
|
reachability.add_block(block1, genesis, &[genesis]).unwrap();
|
||||||
reachability.add_block(block2, block1, &[block1]).unwrap();
|
reachability.add_block(block2, block1, &[block1]).unwrap();
|
||||||
|
|
@ -361,10 +357,8 @@ mod tests {
|
||||||
let block1 = make_block_id(1);
|
let block1 = make_block_id(1);
|
||||||
let block2 = make_block_id(2);
|
let block2 = make_block_id(2);
|
||||||
|
|
||||||
dag.insert_block(block1, vec![genesis], 100)
|
dag.insert_block(block1, vec![genesis], 100).unwrap();
|
||||||
.unwrap();
|
dag.insert_block(block2, vec![block1], 200).unwrap();
|
||||||
dag.insert_block(block2, vec![block1], 200)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
reachability.add_block(block1, genesis, &[genesis]).unwrap();
|
reachability.add_block(block1, genesis, &[genesis]).unwrap();
|
||||||
reachability.add_block(block2, block1, &[block1]).unwrap();
|
reachability.add_block(block2, block1, &[block1]).unwrap();
|
||||||
|
|
@ -383,8 +377,7 @@ mod tests {
|
||||||
let (dag, reachability, ghostdag, ordering) = setup_test();
|
let (dag, reachability, ghostdag, ordering) = setup_test();
|
||||||
|
|
||||||
let block1 = make_block_id(1);
|
let block1 = make_block_id(1);
|
||||||
dag.insert_block(block1, vec![genesis], 100)
|
dag.insert_block(block1, vec![genesis], 100).unwrap();
|
||||||
.unwrap();
|
|
||||||
reachability.add_block(block1, genesis, &[genesis]).unwrap();
|
reachability.add_block(block1, genesis, &[genesis]).unwrap();
|
||||||
ghostdag.add_block(block1, &[genesis]).unwrap();
|
ghostdag.add_block(block1, &[genesis]).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,11 @@ impl ReachabilityStore {
|
||||||
|
|
||||||
/// Checks if block `ancestor` is in the past of block `descendant`.
|
/// Checks if block `ancestor` is in the past of block `descendant`.
|
||||||
/// Returns true if ancestor is an ancestor of descendant.
|
/// Returns true if ancestor is an ancestor of descendant.
|
||||||
pub fn is_ancestor(&self, ancestor: &BlockId, descendant: &BlockId) -> Result<bool, ReachabilityError> {
|
pub fn is_ancestor(
|
||||||
|
&self,
|
||||||
|
ancestor: &BlockId,
|
||||||
|
descendant: &BlockId,
|
||||||
|
) -> Result<bool, ReachabilityError> {
|
||||||
if ancestor == descendant {
|
if ancestor == descendant {
|
||||||
return Ok(true); // A block is its own ancestor
|
return Ok(true); // A block is its own ancestor
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +136,11 @@ impl ReachabilityStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if two blocks are in each other's anticone (neither is ancestor of other).
|
/// Checks if two blocks are in each other's anticone (neither is ancestor of other).
|
||||||
pub fn is_anticone(&self, block_a: &BlockId, block_b: &BlockId) -> Result<bool, ReachabilityError> {
|
pub fn is_anticone(
|
||||||
|
&self,
|
||||||
|
block_a: &BlockId,
|
||||||
|
block_b: &BlockId,
|
||||||
|
) -> Result<bool, ReachabilityError> {
|
||||||
if block_a == block_b {
|
if block_a == block_b {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
@ -171,11 +179,8 @@ impl ReachabilityStore {
|
||||||
let (new_interval, parent_needs_realloc) = self.allocate_interval(&parent_data, &data)?;
|
let (new_interval, parent_needs_realloc) = self.allocate_interval(&parent_data, &data)?;
|
||||||
|
|
||||||
// Create new block's data
|
// Create new block's data
|
||||||
let new_data = ReachabilityData::new(
|
let new_data =
|
||||||
new_interval,
|
ReachabilityData::new(new_interval, Some(selected_parent), parent_data.height + 1);
|
||||||
Some(selected_parent),
|
|
||||||
parent_data.height + 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update parent's tree children
|
// Update parent's tree children
|
||||||
if let Some(parent) = data.get_mut(&selected_parent) {
|
if let Some(parent) = data.get_mut(&selected_parent) {
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use std::collections::HashMap;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use synor_types::{Address, Hash256};
|
use synor_types::{Address, Hash256};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -145,10 +145,7 @@ pub enum ProposalType {
|
||||||
options: Vec<String>,
|
options: Vec<String>,
|
||||||
},
|
},
|
||||||
/// Custom proposal with arbitrary data.
|
/// Custom proposal with arbitrary data.
|
||||||
Custom {
|
Custom { action_type: String, data: Vec<u8> },
|
||||||
action_type: String,
|
|
||||||
data: Vec<u8>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Council member action.
|
/// Council member action.
|
||||||
|
|
@ -160,7 +157,9 @@ pub enum CouncilAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State of a proposal.
|
/// State of a proposal.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
pub enum ProposalState {
|
pub enum ProposalState {
|
||||||
/// Proposal is pending (voting hasn't started).
|
/// Proposal is pending (voting hasn't started).
|
||||||
Pending,
|
Pending,
|
||||||
|
|
@ -183,13 +182,18 @@ impl ProposalState {
|
||||||
pub fn is_final(&self) -> bool {
|
pub fn is_final(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
ProposalState::Executed | ProposalState::Cancelled | ProposalState::Expired | ProposalState::Defeated
|
ProposalState::Executed
|
||||||
|
| ProposalState::Cancelled
|
||||||
|
| ProposalState::Expired
|
||||||
|
| ProposalState::Defeated
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vote choice.
|
/// Vote choice.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
pub enum VoteChoice {
|
pub enum VoteChoice {
|
||||||
/// Vote in favor.
|
/// Vote in favor.
|
||||||
Yes,
|
Yes,
|
||||||
|
|
@ -401,7 +405,9 @@ impl Proposal {
|
||||||
match self.state {
|
match self.state {
|
||||||
ProposalState::Pending => Some(self.voting_starts_block.saturating_sub(current_block)),
|
ProposalState::Pending => Some(self.voting_starts_block.saturating_sub(current_block)),
|
||||||
ProposalState::Active => Some(self.voting_ends_block.saturating_sub(current_block)),
|
ProposalState::Active => Some(self.voting_ends_block.saturating_sub(current_block)),
|
||||||
ProposalState::Passed => Some(self.execution_allowed_block.saturating_sub(current_block)),
|
ProposalState::Passed => {
|
||||||
|
Some(self.execution_allowed_block.saturating_sub(current_block))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -581,7 +587,8 @@ impl DAO {
|
||||||
current_block: u64,
|
current_block: u64,
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
) -> Result<(), DaoError> {
|
) -> Result<(), DaoError> {
|
||||||
let proposal = self.proposals
|
let proposal = self
|
||||||
|
.proposals
|
||||||
.get_mut(proposal_id)
|
.get_mut(proposal_id)
|
||||||
.ok_or(DaoError::ProposalNotFound)?;
|
.ok_or(DaoError::ProposalNotFound)?;
|
||||||
|
|
||||||
|
|
@ -644,7 +651,8 @@ impl DAO {
|
||||||
_executor: &Address,
|
_executor: &Address,
|
||||||
current_block: u64,
|
current_block: u64,
|
||||||
) -> Result<&Proposal, DaoError> {
|
) -> Result<&Proposal, DaoError> {
|
||||||
let proposal = self.proposals
|
let proposal = self
|
||||||
|
.proposals
|
||||||
.get_mut(proposal_id)
|
.get_mut(proposal_id)
|
||||||
.ok_or(DaoError::ProposalNotFound)?;
|
.ok_or(DaoError::ProposalNotFound)?;
|
||||||
|
|
||||||
|
|
@ -683,7 +691,8 @@ impl DAO {
|
||||||
proposal_id: &ProposalId,
|
proposal_id: &ProposalId,
|
||||||
canceller: &Address,
|
canceller: &Address,
|
||||||
) -> Result<(), DaoError> {
|
) -> Result<(), DaoError> {
|
||||||
let proposal = self.proposals
|
let proposal = self
|
||||||
|
.proposals
|
||||||
.get_mut(proposal_id)
|
.get_mut(proposal_id)
|
||||||
.ok_or(DaoError::ProposalNotFound)?;
|
.ok_or(DaoError::ProposalNotFound)?;
|
||||||
|
|
||||||
|
|
@ -713,11 +722,7 @@ impl DAO {
|
||||||
pub fn get_by_proposer(&self, proposer: &Address) -> Vec<&Proposal> {
|
pub fn get_by_proposer(&self, proposer: &Address) -> Vec<&Proposal> {
|
||||||
self.by_proposer
|
self.by_proposer
|
||||||
.get(proposer)
|
.get(proposer)
|
||||||
.map(|ids| {
|
.map(|ids| ids.iter().filter_map(|id| self.proposals.get(id)).collect())
|
||||||
ids.iter()
|
|
||||||
.filter_map(|id| self.proposals.get(id))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -726,8 +731,8 @@ impl DAO {
|
||||||
self.proposals
|
self.proposals
|
||||||
.values()
|
.values()
|
||||||
.filter(|p| {
|
.filter(|p| {
|
||||||
p.state == ProposalState::Active ||
|
p.state == ProposalState::Active
|
||||||
(p.state == ProposalState::Pending && current_block >= p.voting_starts_block)
|
|| (p.state == ProposalState::Pending && current_block >= p.voting_starts_block)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
@ -753,10 +758,22 @@ impl DAO {
|
||||||
|
|
||||||
DaoStats {
|
DaoStats {
|
||||||
total_proposals: proposals.len() as u64,
|
total_proposals: proposals.len() as u64,
|
||||||
active_proposals: proposals.iter().filter(|p| p.state == ProposalState::Active).count() as u64,
|
active_proposals: proposals
|
||||||
passed_proposals: proposals.iter().filter(|p| p.state == ProposalState::Passed || p.state == ProposalState::Executed).count() as u64,
|
.iter()
|
||||||
defeated_proposals: proposals.iter().filter(|p| p.state == ProposalState::Defeated).count() as u64,
|
.filter(|p| p.state == ProposalState::Active)
|
||||||
executed_proposals: proposals.iter().filter(|p| p.state == ProposalState::Executed).count() as u64,
|
.count() as u64,
|
||||||
|
passed_proposals: proposals
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.state == ProposalState::Passed || p.state == ProposalState::Executed)
|
||||||
|
.count() as u64,
|
||||||
|
defeated_proposals: proposals
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.state == ProposalState::Defeated)
|
||||||
|
.count() as u64,
|
||||||
|
executed_proposals: proposals
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.state == ProposalState::Executed)
|
||||||
|
.count() as u64,
|
||||||
total_votes_cast: proposals.iter().map(|p| p.votes.len()).sum::<usize>() as u64,
|
total_votes_cast: proposals.iter().map(|p| p.votes.len()).sum::<usize>() as u64,
|
||||||
council_members: self.council.len(),
|
council_members: self.council.len(),
|
||||||
}
|
}
|
||||||
|
|
@ -797,19 +814,21 @@ mod tests {
|
||||||
let mut dao = DAO::new(VotingConfig::fast());
|
let mut dao = DAO::new(VotingConfig::fast());
|
||||||
let proposer = test_address(1);
|
let proposer = test_address(1);
|
||||||
|
|
||||||
let id = dao.create_proposal(
|
let id = dao
|
||||||
proposer,
|
.create_proposal(
|
||||||
1_000_000 * 100_000_000, // 1M tokens
|
proposer,
|
||||||
ProposalType::Signaling {
|
1_000_000 * 100_000_000, // 1M tokens
|
||||||
title: "Test".to_string(),
|
ProposalType::Signaling {
|
||||||
description: "Test proposal".to_string(),
|
title: "Test".to_string(),
|
||||||
options: vec!["Yes".to_string(), "No".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(),
|
"Test Proposal".to_string(),
|
||||||
100,
|
"This is a test".to_string(),
|
||||||
70_000_000 * 100_000_000,
|
100,
|
||||||
).unwrap();
|
70_000_000 * 100_000_000,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let proposal = dao.get_proposal(&id).unwrap();
|
let proposal = dao.get_proposal(&id).unwrap();
|
||||||
assert_eq!(proposal.state, ProposalState::Pending);
|
assert_eq!(proposal.state, ProposalState::Pending);
|
||||||
|
|
@ -845,27 +864,38 @@ mod tests {
|
||||||
let voter2 = test_address(3);
|
let voter2 = test_address(3);
|
||||||
|
|
||||||
// Create proposal
|
// Create proposal
|
||||||
let id = dao.create_proposal(
|
let id = dao
|
||||||
proposer,
|
.create_proposal(
|
||||||
1_000_000 * 100_000_000,
|
proposer,
|
||||||
ProposalType::Signaling {
|
1_000_000 * 100_000_000,
|
||||||
title: "Test".to_string(),
|
ProposalType::Signaling {
|
||||||
description: "Test".to_string(),
|
title: "Test".to_string(),
|
||||||
options: vec![],
|
description: "Test".to_string(),
|
||||||
},
|
options: vec![],
|
||||||
"Test".to_string(),
|
},
|
||||||
"Test".to_string(),
|
"Test".to_string(),
|
||||||
0,
|
"Test".to_string(),
|
||||||
70_000_000 * 100_000_000,
|
0,
|
||||||
).unwrap();
|
70_000_000 * 100_000_000,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Try voting before start
|
// Try voting before start
|
||||||
let result = dao.vote(&id, voter1.clone(), 100_000, VoteChoice::Yes, 5, None);
|
let result = dao.vote(&id, voter1.clone(), 100_000, VoteChoice::Yes, 5, None);
|
||||||
assert!(matches!(result, Err(DaoError::VotingNotStarted)));
|
assert!(matches!(result, Err(DaoError::VotingNotStarted)));
|
||||||
|
|
||||||
// Vote after start
|
// Vote after start
|
||||||
dao.vote(&id, voter1.clone(), 1_000_000_000, VoteChoice::Yes, 15, None).unwrap();
|
dao.vote(
|
||||||
dao.vote(&id, voter2, 500_000_000, VoteChoice::No, 16, None).unwrap();
|
&id,
|
||||||
|
voter1.clone(),
|
||||||
|
1_000_000_000,
|
||||||
|
VoteChoice::Yes,
|
||||||
|
15,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
dao.vote(&id, voter2, 500_000_000, VoteChoice::No, 16, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let proposal = dao.get_proposal(&id).unwrap();
|
let proposal = dao.get_proposal(&id).unwrap();
|
||||||
assert_eq!(proposal.votes.len(), 2);
|
assert_eq!(proposal.votes.len(), 2);
|
||||||
|
|
@ -888,22 +918,25 @@ mod tests {
|
||||||
let mut dao = DAO::new(VotingConfig::fast());
|
let mut dao = DAO::new(VotingConfig::fast());
|
||||||
let proposer = test_address(1);
|
let proposer = test_address(1);
|
||||||
|
|
||||||
let id = dao.create_proposal(
|
let id = dao
|
||||||
proposer.clone(),
|
.create_proposal(
|
||||||
1_000_000 * 100_000_000,
|
proposer.clone(),
|
||||||
ProposalType::Signaling {
|
1_000_000 * 100_000_000,
|
||||||
title: "Test".to_string(),
|
ProposalType::Signaling {
|
||||||
description: "Test".to_string(),
|
title: "Test".to_string(),
|
||||||
options: vec![],
|
description: "Test".to_string(),
|
||||||
},
|
options: vec![],
|
||||||
"Test".to_string(),
|
},
|
||||||
"Test".to_string(),
|
"Test".to_string(),
|
||||||
0,
|
"Test".to_string(),
|
||||||
100 * 100_000_000, // Small total supply for easy quorum
|
0,
|
||||||
).unwrap();
|
100 * 100_000_000, // Small total supply for easy quorum
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Vote with more than quorum
|
// Vote with more than quorum
|
||||||
dao.vote(&id, proposer, 10 * 100_000_000, VoteChoice::Yes, 15, None).unwrap();
|
dao.vote(&id, proposer, 10 * 100_000_000, VoteChoice::Yes, 15, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Update state after voting ends
|
// Update state after voting ends
|
||||||
dao.update_all_states(200);
|
dao.update_all_states(200);
|
||||||
|
|
@ -920,19 +953,21 @@ mod tests {
|
||||||
|
|
||||||
dao.set_guardian(guardian.clone());
|
dao.set_guardian(guardian.clone());
|
||||||
|
|
||||||
let id = dao.create_proposal(
|
let id = dao
|
||||||
proposer,
|
.create_proposal(
|
||||||
1_000_000 * 100_000_000,
|
proposer,
|
||||||
ProposalType::Signaling {
|
1_000_000 * 100_000_000,
|
||||||
title: "Test".to_string(),
|
ProposalType::Signaling {
|
||||||
description: "Test".to_string(),
|
title: "Test".to_string(),
|
||||||
options: vec![],
|
description: "Test".to_string(),
|
||||||
},
|
options: vec![],
|
||||||
"Test".to_string(),
|
},
|
||||||
"Test".to_string(),
|
"Test".to_string(),
|
||||||
0,
|
"Test".to_string(),
|
||||||
70_000_000 * 100_000_000,
|
0,
|
||||||
).unwrap();
|
70_000_000 * 100_000_000,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Guardian cancels
|
// Guardian cancels
|
||||||
dao.cancel(&id, &guardian).unwrap();
|
dao.cancel(&id, &guardian).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ pub mod treasury;
|
||||||
pub mod vesting;
|
pub mod vesting;
|
||||||
|
|
||||||
pub use dao::{
|
pub use dao::{
|
||||||
DaoStats, Proposal, ProposalId, ProposalState, ProposalSummary, ProposalType, Vote,
|
DaoStats, Proposal, ProposalId, ProposalState, ProposalSummary, ProposalType, Vote, VoteChoice,
|
||||||
VoteChoice, VotingConfig, VotingPower, DAO,
|
VotingConfig, VotingPower, DAO,
|
||||||
};
|
};
|
||||||
pub use multisig::{
|
pub use multisig::{
|
||||||
MultisigConfig, MultisigTransaction, MultisigTxId, MultisigTxState, MultisigWallet,
|
MultisigConfig, MultisigTransaction, MultisigTxId, MultisigTxState, MultisigWallet,
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
use synor_types::{Address, Hash256};
|
use synor_types::{Address, Hash256};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -81,7 +81,11 @@ impl MultisigConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a 2-of-3 multisig.
|
/// Creates a 2-of-3 multisig.
|
||||||
pub fn two_of_three(signer1: Address, signer2: Address, signer3: Address) -> Result<Self, MultisigError> {
|
pub fn two_of_three(
|
||||||
|
signer1: Address,
|
||||||
|
signer2: Address,
|
||||||
|
signer3: Address,
|
||||||
|
) -> Result<Self, MultisigError> {
|
||||||
Self::new(2, vec![signer1, signer2, signer3])
|
Self::new(2, vec![signer1, signer2, signer3])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +117,9 @@ impl MultisigConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State of a multisig transaction.
|
/// State of a multisig transaction.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
pub enum MultisigTxState {
|
pub enum MultisigTxState {
|
||||||
/// Transaction is pending signatures.
|
/// Transaction is pending signatures.
|
||||||
Pending,
|
Pending,
|
||||||
|
|
@ -131,26 +137,15 @@ pub enum MultisigTxState {
|
||||||
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
||||||
pub enum MultisigTxType {
|
pub enum MultisigTxType {
|
||||||
/// Transfer tokens to an address.
|
/// Transfer tokens to an address.
|
||||||
Transfer {
|
Transfer { to: Address, amount: u64 },
|
||||||
to: Address,
|
|
||||||
amount: u64,
|
|
||||||
},
|
|
||||||
/// Add a new signer.
|
/// Add a new signer.
|
||||||
AddSigner {
|
AddSigner { new_signer: Address },
|
||||||
new_signer: Address,
|
|
||||||
},
|
|
||||||
/// Remove a signer.
|
/// Remove a signer.
|
||||||
RemoveSigner {
|
RemoveSigner { signer: Address },
|
||||||
signer: Address,
|
|
||||||
},
|
|
||||||
/// Change the threshold.
|
/// Change the threshold.
|
||||||
ChangeThreshold {
|
ChangeThreshold { new_threshold: u32 },
|
||||||
new_threshold: u32,
|
|
||||||
},
|
|
||||||
/// Execute a raw transaction.
|
/// Execute a raw transaction.
|
||||||
RawTransaction {
|
RawTransaction { tx_data: Vec<u8> },
|
||||||
tx_data: Vec<u8>,
|
|
||||||
},
|
|
||||||
/// Create a vesting contract.
|
/// Create a vesting contract.
|
||||||
CreateVesting {
|
CreateVesting {
|
||||||
beneficiary: Address,
|
beneficiary: Address,
|
||||||
|
|
@ -160,14 +155,9 @@ pub enum MultisigTxType {
|
||||||
description: String,
|
description: String,
|
||||||
},
|
},
|
||||||
/// Approve a DAO proposal.
|
/// Approve a DAO proposal.
|
||||||
ApproveProposal {
|
ApproveProposal { proposal_id: Hash256 },
|
||||||
proposal_id: Hash256,
|
|
||||||
},
|
|
||||||
/// Custom action with arbitrary data.
|
/// Custom action with arbitrary data.
|
||||||
Custom {
|
Custom { action_type: String, data: Vec<u8> },
|
||||||
action_type: String,
|
|
||||||
data: Vec<u8>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A pending multisig transaction.
|
/// A pending multisig transaction.
|
||||||
|
|
@ -394,7 +384,8 @@ impl MultisigWallet {
|
||||||
return Err(MultisigError::NotAuthorized);
|
return Err(MultisigError::NotAuthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx = self.pending_transactions
|
let tx = self
|
||||||
|
.pending_transactions
|
||||||
.get_mut(tx_id)
|
.get_mut(tx_id)
|
||||||
.ok_or(MultisigError::TransactionNotFound)?;
|
.ok_or(MultisigError::TransactionNotFound)?;
|
||||||
|
|
||||||
|
|
@ -438,7 +429,8 @@ impl MultisigWallet {
|
||||||
return Err(MultisigError::NotAuthorized);
|
return Err(MultisigError::NotAuthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx = self.pending_transactions
|
let tx = self
|
||||||
|
.pending_transactions
|
||||||
.get_mut(tx_id)
|
.get_mut(tx_id)
|
||||||
.ok_or(MultisigError::TransactionNotFound)?;
|
.ok_or(MultisigError::TransactionNotFound)?;
|
||||||
|
|
||||||
|
|
@ -517,7 +509,8 @@ impl MultisigWallet {
|
||||||
canceller: &Address,
|
canceller: &Address,
|
||||||
) -> Result<(), MultisigError> {
|
) -> Result<(), MultisigError> {
|
||||||
// Only proposer can cancel
|
// Only proposer can cancel
|
||||||
let tx = self.pending_transactions
|
let tx = self
|
||||||
|
.pending_transactions
|
||||||
.get_mut(tx_id)
|
.get_mut(tx_id)
|
||||||
.ok_or(MultisigError::TransactionNotFound)?;
|
.ok_or(MultisigError::TransactionNotFound)?;
|
||||||
|
|
||||||
|
|
@ -564,7 +557,8 @@ impl MultisigWallet {
|
||||||
|
|
||||||
/// Cleans up expired transactions.
|
/// Cleans up expired transactions.
|
||||||
pub fn cleanup_expired(&mut self, current_time: u64) -> usize {
|
pub fn cleanup_expired(&mut self, current_time: u64) -> usize {
|
||||||
let expired: Vec<_> = self.pending_transactions
|
let expired: Vec<_> = self
|
||||||
|
.pending_transactions
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, tx)| tx.is_expired(current_time) && tx.state == MultisigTxState::Pending)
|
.filter(|(_, tx)| tx.is_expired(current_time) && tx.state == MultisigTxState::Pending)
|
||||||
.map(|(id, _)| *id)
|
.map(|(id, _)| *id)
|
||||||
|
|
@ -591,7 +585,10 @@ impl MultisigWallet {
|
||||||
threshold: self.config.threshold,
|
threshold: self.config.threshold,
|
||||||
balance: self.balance,
|
balance: self.balance,
|
||||||
pending_count: pending.len(),
|
pending_count: pending.len(),
|
||||||
ready_count: pending.iter().filter(|tx| tx.state == MultisigTxState::Ready).count(),
|
ready_count: pending
|
||||||
|
.iter()
|
||||||
|
.filter(|tx| tx.state == MultisigTxState::Ready)
|
||||||
|
.count(),
|
||||||
total_executed: self.executed_transactions.len(),
|
total_executed: self.executed_transactions.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -662,20 +659,20 @@ impl MultisigManager {
|
||||||
pub fn wallets_for_signer(&self, signer: &Address) -> Vec<&MultisigWallet> {
|
pub fn wallets_for_signer(&self, signer: &Address) -> Vec<&MultisigWallet> {
|
||||||
self.by_signer
|
self.by_signer
|
||||||
.get(signer)
|
.get(signer)
|
||||||
.map(|ids| {
|
.map(|ids| ids.iter().filter_map(|id| self.wallets.get(id)).collect())
|
||||||
ids.iter()
|
|
||||||
.filter_map(|id| self.wallets.get(id))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets all transactions awaiting a signer across all wallets.
|
/// Gets all transactions awaiting a signer across all wallets.
|
||||||
pub fn awaiting_signature(&self, signer: &Address) -> Vec<(&MultisigWallet, &MultisigTransaction)> {
|
pub fn awaiting_signature(
|
||||||
|
&self,
|
||||||
|
signer: &Address,
|
||||||
|
) -> Vec<(&MultisigWallet, &MultisigTransaction)> {
|
||||||
self.wallets_for_signer(signer)
|
self.wallets_for_signer(signer)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|wallet| {
|
.flat_map(|wallet| {
|
||||||
wallet.awaiting_signature(signer)
|
wallet
|
||||||
|
.awaiting_signature(signer)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |tx| (wallet, tx))
|
.map(move |tx| (wallet, tx))
|
||||||
})
|
})
|
||||||
|
|
@ -707,11 +704,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multisig_config() {
|
fn test_multisig_config() {
|
||||||
let config = MultisigConfig::two_of_three(
|
let config =
|
||||||
test_address(1),
|
MultisigConfig::two_of_three(test_address(1), test_address(2), test_address(3))
|
||||||
test_address(2),
|
.unwrap();
|
||||||
test_address(3),
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(config.threshold, 2);
|
assert_eq!(config.threshold, 2);
|
||||||
assert_eq!(config.signer_count(), 3);
|
assert_eq!(config.signer_count(), 3);
|
||||||
|
|
@ -742,22 +737,25 @@ mod tests {
|
||||||
let signer3 = test_address(3);
|
let signer3 = test_address(3);
|
||||||
let recipient = test_address(10);
|
let recipient = test_address(10);
|
||||||
|
|
||||||
let config = MultisigConfig::two_of_three(
|
let config =
|
||||||
signer1.clone(),
|
MultisigConfig::two_of_three(signer1.clone(), signer2.clone(), signer3.clone())
|
||||||
signer2.clone(),
|
.unwrap();
|
||||||
signer3.clone(),
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
let mut wallet = MultisigWallet::new("Test Wallet".to_string(), config, 0);
|
let mut wallet = MultisigWallet::new("Test Wallet".to_string(), config, 0);
|
||||||
wallet.deposit(1_000_000);
|
wallet.deposit(1_000_000);
|
||||||
|
|
||||||
// Propose a transfer
|
// Propose a transfer
|
||||||
let tx_id = wallet.propose(
|
let tx_id = wallet
|
||||||
MultisigTxType::Transfer { to: recipient, amount: 100_000 },
|
.propose(
|
||||||
&signer1,
|
MultisigTxType::Transfer {
|
||||||
100,
|
to: recipient,
|
||||||
"Pay developer".to_string(),
|
amount: 100_000,
|
||||||
).unwrap();
|
},
|
||||||
|
&signer1,
|
||||||
|
100,
|
||||||
|
"Pay developer".to_string(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Check proposer auto-signed
|
// Check proposer auto-signed
|
||||||
let tx = wallet.get_transaction(&tx_id).unwrap();
|
let tx = wallet.get_transaction(&tx_id).unwrap();
|
||||||
|
|
@ -780,21 +778,24 @@ mod tests {
|
||||||
let signer2 = test_address(2);
|
let signer2 = test_address(2);
|
||||||
let signer3 = test_address(3);
|
let signer3 = test_address(3);
|
||||||
|
|
||||||
let config = MultisigConfig::two_of_three(
|
let config =
|
||||||
signer1.clone(),
|
MultisigConfig::two_of_three(signer1.clone(), signer2.clone(), signer3.clone())
|
||||||
signer2.clone(),
|
.unwrap();
|
||||||
signer3.clone(),
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
let mut wallet = MultisigWallet::new("Test".to_string(), config, 0);
|
let mut wallet = MultisigWallet::new("Test".to_string(), config, 0);
|
||||||
wallet.deposit(1_000_000);
|
wallet.deposit(1_000_000);
|
||||||
|
|
||||||
let tx_id = wallet.propose(
|
let tx_id = wallet
|
||||||
MultisigTxType::Transfer { to: test_address(10), amount: 100 },
|
.propose(
|
||||||
&signer1,
|
MultisigTxType::Transfer {
|
||||||
100,
|
to: test_address(10),
|
||||||
"Test".to_string(),
|
amount: 100,
|
||||||
).unwrap();
|
},
|
||||||
|
&signer1,
|
||||||
|
100,
|
||||||
|
"Test".to_string(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Try to execute with only 1 signature
|
// Try to execute with only 1 signature
|
||||||
let result = wallet.execute(&tx_id, &signer1, 101);
|
let result = wallet.execute(&tx_id, &signer1, 101);
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@
|
||||||
//! - Automated fund streams
|
//! - Automated fund streams
|
||||||
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use std::collections::HashMap;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use synor_types::{Address, Hash256};
|
use synor_types::{Address, Hash256};
|
||||||
|
|
||||||
use crate::multisig::{MultisigTxId, MultisigWallet};
|
use crate::multisig::{MultisigTxId, MultisigWallet};
|
||||||
|
|
@ -16,7 +16,16 @@ use crate::ProposalId;
|
||||||
|
|
||||||
/// Unique identifier for a treasury pool.
|
/// Unique identifier for a treasury pool.
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
)]
|
)]
|
||||||
pub struct TreasuryPoolId(pub [u8; 32]);
|
pub struct TreasuryPoolId(pub [u8; 32]);
|
||||||
|
|
||||||
|
|
@ -156,9 +165,9 @@ impl SpendingLimit {
|
||||||
/// High security: 1% per tx, 5% per week.
|
/// High security: 1% per tx, 5% per week.
|
||||||
pub fn high_security(total_balance: u64) -> Self {
|
pub fn high_security(total_balance: u64) -> Self {
|
||||||
SpendingLimit::new(
|
SpendingLimit::new(
|
||||||
total_balance / 100, // 1% per tx
|
total_balance / 100, // 1% per tx
|
||||||
total_balance / 20, // 5% per week
|
total_balance / 20, // 5% per week
|
||||||
7 * 24 * 60 * 60, // 1 week
|
7 * 24 * 60 * 60, // 1 week
|
||||||
)
|
)
|
||||||
.with_cooling_period(24 * 60 * 60) // 1 day cooling
|
.with_cooling_period(24 * 60 * 60) // 1 day cooling
|
||||||
}
|
}
|
||||||
|
|
@ -166,9 +175,9 @@ impl SpendingLimit {
|
||||||
/// Medium security: 5% per tx, 20% per month.
|
/// Medium security: 5% per tx, 20% per month.
|
||||||
pub fn medium_security(total_balance: u64) -> Self {
|
pub fn medium_security(total_balance: u64) -> Self {
|
||||||
SpendingLimit::new(
|
SpendingLimit::new(
|
||||||
total_balance / 20, // 5% per tx
|
total_balance / 20, // 5% per tx
|
||||||
total_balance / 5, // 20% per month
|
total_balance / 5, // 20% per month
|
||||||
30 * 24 * 60 * 60, // 1 month
|
30 * 24 * 60 * 60, // 1 month
|
||||||
)
|
)
|
||||||
.with_cooling_period(6 * 60 * 60) // 6 hour cooling
|
.with_cooling_period(6 * 60 * 60) // 6 hour cooling
|
||||||
}
|
}
|
||||||
|
|
@ -176,9 +185,9 @@ impl SpendingLimit {
|
||||||
/// Low security: 10% per tx, 50% per month.
|
/// Low security: 10% per tx, 50% per month.
|
||||||
pub fn low_security(total_balance: u64) -> Self {
|
pub fn low_security(total_balance: u64) -> Self {
|
||||||
SpendingLimit::new(
|
SpendingLimit::new(
|
||||||
total_balance / 10, // 10% per tx
|
total_balance / 10, // 10% per tx
|
||||||
total_balance / 2, // 50% per month
|
total_balance / 2, // 50% per month
|
||||||
30 * 24 * 60 * 60, // 1 month
|
30 * 24 * 60 * 60, // 1 month
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -336,8 +345,8 @@ impl TreasuryConfig {
|
||||||
approvals: ApprovalRequirements {
|
approvals: ApprovalRequirements {
|
||||||
multisig_required: true,
|
multisig_required: true,
|
||||||
dao_threshold: Some(50_000 * 100_000_000), // 50k SYNOR needs DAO
|
dao_threshold: Some(50_000 * 100_000_000), // 50k SYNOR needs DAO
|
||||||
council_required: true, // Council must approve team allocations
|
council_required: true, // Council must approve team allocations
|
||||||
min_delay: 7 * 24 * 60 * 60, // 7 day delay
|
min_delay: 7 * 24 * 60 * 60, // 7 day delay
|
||||||
},
|
},
|
||||||
allow_deposits: false, // No external deposits
|
allow_deposits: false, // No external deposits
|
||||||
frozen: false,
|
frozen: false,
|
||||||
|
|
@ -497,21 +506,29 @@ impl TreasuryPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check spending limits
|
// Check spending limits
|
||||||
self.config.spending_limit.can_spend(amount, timestamp)
|
self.config
|
||||||
|
.spending_limit
|
||||||
|
.can_spend(amount, timestamp)
|
||||||
.map_err(TreasuryError::SpendingLimitExceeded)?;
|
.map_err(TreasuryError::SpendingLimitExceeded)?;
|
||||||
|
|
||||||
// Determine initial state based on requirements
|
// Determine initial state based on requirements
|
||||||
let initial_state = if self.config.approvals.multisig_required && self.multisig_wallet.is_some() {
|
let initial_state =
|
||||||
SpendingRequestState::PendingMultisig
|
if self.config.approvals.multisig_required && self.multisig_wallet.is_some() {
|
||||||
} else if self.config.approvals.dao_threshold.map_or(false, |t| amount >= t) {
|
SpendingRequestState::PendingMultisig
|
||||||
SpendingRequestState::PendingDao
|
} else if self
|
||||||
} else if self.config.approvals.council_required {
|
.config
|
||||||
SpendingRequestState::PendingCouncil
|
.approvals
|
||||||
} else {
|
.dao_threshold
|
||||||
SpendingRequestState::Timelocked {
|
.map_or(false, |t| amount >= t)
|
||||||
execute_after: timestamp + self.config.approvals.min_delay,
|
{
|
||||||
}
|
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 {
|
let request = SpendingRequest {
|
||||||
id,
|
id,
|
||||||
|
|
@ -538,7 +555,9 @@ impl TreasuryPool {
|
||||||
multisig_tx: MultisigTxId,
|
multisig_tx: MultisigTxId,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
) -> Result<(), TreasuryError> {
|
) -> Result<(), TreasuryError> {
|
||||||
let request = self.pending_requests.get_mut(request_id)
|
let request = self
|
||||||
|
.pending_requests
|
||||||
|
.get_mut(request_id)
|
||||||
.ok_or(TreasuryError::RequestNotFound)?;
|
.ok_or(TreasuryError::RequestNotFound)?;
|
||||||
|
|
||||||
if request.state != SpendingRequestState::PendingMultisig {
|
if request.state != SpendingRequestState::PendingMultisig {
|
||||||
|
|
@ -548,7 +567,10 @@ impl TreasuryPool {
|
||||||
request.multisig_tx = Some(multisig_tx);
|
request.multisig_tx = Some(multisig_tx);
|
||||||
|
|
||||||
// Move to next approval stage
|
// Move to next approval stage
|
||||||
let needs_dao = self.config.approvals.dao_threshold
|
let needs_dao = self
|
||||||
|
.config
|
||||||
|
.approvals
|
||||||
|
.dao_threshold
|
||||||
.map_or(false, |t| request.amount >= t);
|
.map_or(false, |t| request.amount >= t);
|
||||||
|
|
||||||
if needs_dao {
|
if needs_dao {
|
||||||
|
|
@ -571,7 +593,9 @@ impl TreasuryPool {
|
||||||
proposal_id: ProposalId,
|
proposal_id: ProposalId,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
) -> Result<(), TreasuryError> {
|
) -> Result<(), TreasuryError> {
|
||||||
let request = self.pending_requests.get_mut(request_id)
|
let request = self
|
||||||
|
.pending_requests
|
||||||
|
.get_mut(request_id)
|
||||||
.ok_or(TreasuryError::RequestNotFound)?;
|
.ok_or(TreasuryError::RequestNotFound)?;
|
||||||
|
|
||||||
if request.state != SpendingRequestState::PendingDao {
|
if request.state != SpendingRequestState::PendingDao {
|
||||||
|
|
@ -599,7 +623,9 @@ impl TreasuryPool {
|
||||||
required_approvals: usize,
|
required_approvals: usize,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
) -> Result<(), TreasuryError> {
|
) -> Result<(), TreasuryError> {
|
||||||
let request = self.pending_requests.get_mut(request_id)
|
let request = self
|
||||||
|
.pending_requests
|
||||||
|
.get_mut(request_id)
|
||||||
.ok_or(TreasuryError::RequestNotFound)?;
|
.ok_or(TreasuryError::RequestNotFound)?;
|
||||||
|
|
||||||
if request.state != SpendingRequestState::PendingCouncil {
|
if request.state != SpendingRequestState::PendingCouncil {
|
||||||
|
|
@ -625,7 +651,9 @@ impl TreasuryPool {
|
||||||
request_id: &Hash256,
|
request_id: &Hash256,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
) -> Result<SpendingRequest, TreasuryError> {
|
) -> Result<SpendingRequest, TreasuryError> {
|
||||||
let request = self.pending_requests.get(request_id)
|
let request = self
|
||||||
|
.pending_requests
|
||||||
|
.get(request_id)
|
||||||
.ok_or(TreasuryError::RequestNotFound)?;
|
.ok_or(TreasuryError::RequestNotFound)?;
|
||||||
|
|
||||||
// Check if ready
|
// Check if ready
|
||||||
|
|
@ -654,7 +682,9 @@ impl TreasuryPool {
|
||||||
// Execute
|
// Execute
|
||||||
self.balance -= amount;
|
self.balance -= amount;
|
||||||
self.total_spent += amount;
|
self.total_spent += amount;
|
||||||
self.config.spending_limit.record_spending(amount, timestamp);
|
self.config
|
||||||
|
.spending_limit
|
||||||
|
.record_spending(amount, timestamp);
|
||||||
|
|
||||||
// Update request state and remove from pending
|
// Update request state and remove from pending
|
||||||
let mut executed_request = self.pending_requests.remove(request_id).unwrap();
|
let mut executed_request = self.pending_requests.remove(request_id).unwrap();
|
||||||
|
|
@ -669,7 +699,9 @@ impl TreasuryPool {
|
||||||
request_id: &Hash256,
|
request_id: &Hash256,
|
||||||
canceller: &Address,
|
canceller: &Address,
|
||||||
) -> Result<(), TreasuryError> {
|
) -> Result<(), TreasuryError> {
|
||||||
let request = self.pending_requests.get_mut(request_id)
|
let request = self
|
||||||
|
.pending_requests
|
||||||
|
.get_mut(request_id)
|
||||||
.ok_or(TreasuryError::RequestNotFound)?;
|
.ok_or(TreasuryError::RequestNotFound)?;
|
||||||
|
|
||||||
// Only proposer can cancel
|
// Only proposer can cancel
|
||||||
|
|
@ -710,14 +742,7 @@ impl TreasuryPool {
|
||||||
// Reserve the amount
|
// Reserve the amount
|
||||||
self.balance -= amount;
|
self.balance -= amount;
|
||||||
|
|
||||||
let stream = FundingStream::new(
|
let stream = FundingStream::new(id, recipient, amount, start_time, duration, description);
|
||||||
id,
|
|
||||||
recipient,
|
|
||||||
amount,
|
|
||||||
start_time,
|
|
||||||
duration,
|
|
||||||
description,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.streams.insert(id, stream);
|
self.streams.insert(id, stream);
|
||||||
Ok(self.streams.get(&id).unwrap())
|
Ok(self.streams.get(&id).unwrap())
|
||||||
|
|
@ -730,7 +755,9 @@ impl TreasuryPool {
|
||||||
claimer: &Address,
|
claimer: &Address,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
) -> Result<u64, TreasuryError> {
|
) -> Result<u64, TreasuryError> {
|
||||||
let stream = self.streams.get_mut(stream_id)
|
let stream = self
|
||||||
|
.streams
|
||||||
|
.get_mut(stream_id)
|
||||||
.ok_or(TreasuryError::StreamNotFound)?;
|
.ok_or(TreasuryError::StreamNotFound)?;
|
||||||
|
|
||||||
if &stream.recipient != claimer {
|
if &stream.recipient != claimer {
|
||||||
|
|
@ -745,7 +772,9 @@ impl TreasuryPool {
|
||||||
|
|
||||||
/// Cancels a funding stream (returns unvested to pool).
|
/// Cancels a funding stream (returns unvested to pool).
|
||||||
pub fn cancel_stream(&mut self, stream_id: &Hash256) -> Result<u64, TreasuryError> {
|
pub fn cancel_stream(&mut self, stream_id: &Hash256) -> Result<u64, TreasuryError> {
|
||||||
let stream = self.streams.get_mut(stream_id)
|
let stream = self
|
||||||
|
.streams
|
||||||
|
.get_mut(stream_id)
|
||||||
.ok_or(TreasuryError::StreamNotFound)?;
|
.ok_or(TreasuryError::StreamNotFound)?;
|
||||||
|
|
||||||
let remaining = stream.total_amount - stream.claimed_amount;
|
let remaining = stream.total_amount - stream.claimed_amount;
|
||||||
|
|
@ -842,7 +871,9 @@ impl Treasury {
|
||||||
) -> Result<(), TreasuryError> {
|
) -> Result<(), TreasuryError> {
|
||||||
let wallet_id = wallet.id;
|
let wallet_id = wallet.id;
|
||||||
|
|
||||||
let pool = self.pools.get_mut(pool_id)
|
let pool = self
|
||||||
|
.pools
|
||||||
|
.get_mut(pool_id)
|
||||||
.ok_or(TreasuryError::PoolNotFound)?;
|
.ok_or(TreasuryError::PoolNotFound)?;
|
||||||
|
|
||||||
pool.set_multisig(wallet_id);
|
pool.set_multisig(wallet_id);
|
||||||
|
|
@ -985,8 +1016,15 @@ impl std::fmt::Display for SpendingError {
|
||||||
Self::CoolingPeriod { remaining } => {
|
Self::CoolingPeriod { remaining } => {
|
||||||
write!(f, "Cooling period: {} seconds remaining", remaining)
|
write!(f, "Cooling period: {} seconds remaining", remaining)
|
||||||
}
|
}
|
||||||
Self::ExceedsPeriodLimit { requested, remaining } => {
|
Self::ExceedsPeriodLimit {
|
||||||
write!(f, "Requested {} exceeds remaining period allowance of {}", requested, remaining)
|
requested,
|
||||||
|
remaining,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Requested {} exceeds remaining period allowance of {}",
|
||||||
|
requested, remaining
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1025,8 +1063,15 @@ impl std::fmt::Display for TreasuryError {
|
||||||
Self::PoolNotFound => write!(f, "Treasury pool not found"),
|
Self::PoolNotFound => write!(f, "Treasury pool not found"),
|
||||||
Self::PoolFrozen => write!(f, "Treasury pool is frozen"),
|
Self::PoolFrozen => write!(f, "Treasury pool is frozen"),
|
||||||
Self::DepositsNotAllowed => write!(f, "Deposits not allowed for this pool"),
|
Self::DepositsNotAllowed => write!(f, "Deposits not allowed for this pool"),
|
||||||
Self::InsufficientBalance { requested, available } => {
|
Self::InsufficientBalance {
|
||||||
write!(f, "Insufficient balance: {} requested, {} available", requested, available)
|
requested,
|
||||||
|
available,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Insufficient balance: {} requested, {} available",
|
||||||
|
requested, available
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Self::SpendingLimitExceeded(e) => write!(f, "Spending limit exceeded: {}", e),
|
Self::SpendingLimitExceeded(e) => write!(f, "Spending limit exceeded: {}", e),
|
||||||
Self::RequestNotFound => write!(f, "Spending request not found"),
|
Self::RequestNotFound => write!(f, "Spending request not found"),
|
||||||
|
|
@ -1049,7 +1094,11 @@ mod tests {
|
||||||
fn test_address(n: u8) -> Address {
|
fn test_address(n: u8) -> Address {
|
||||||
let mut bytes = [0u8; 32];
|
let mut bytes = [0u8; 32];
|
||||||
bytes[0] = n;
|
bytes[0] = n;
|
||||||
Address::from_parts(synor_types::Network::Devnet, synor_types::address::AddressType::P2PKH, bytes)
|
Address::from_parts(
|
||||||
|
synor_types::Network::Devnet,
|
||||||
|
synor_types::address::AddressType::P2PKH,
|
||||||
|
bytes,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_hash(n: u8) -> Hash256 {
|
fn test_hash(n: u8) -> Hash256 {
|
||||||
|
|
@ -1061,9 +1110,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spending_limit() {
|
fn test_spending_limit() {
|
||||||
let mut limit = SpendingLimit::new(
|
let mut limit = SpendingLimit::new(
|
||||||
1000, // 1000 per tx
|
1000, // 1000 per tx
|
||||||
5000, // 5000 per period
|
5000, // 5000 per period
|
||||||
3600, // 1 hour period
|
3600, // 1 hour period
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should allow first spend
|
// Should allow first spend
|
||||||
|
|
@ -1088,13 +1137,7 @@ mod tests {
|
||||||
fn test_treasury_pool() {
|
fn test_treasury_pool() {
|
||||||
let pool_id = TreasuryPoolId::new([1u8; 32]);
|
let pool_id = TreasuryPoolId::new([1u8; 32]);
|
||||||
let config = TreasuryConfig::ecosystem(1_000_000);
|
let config = TreasuryConfig::ecosystem(1_000_000);
|
||||||
let mut pool = TreasuryPool::new(
|
let mut pool = TreasuryPool::new(pool_id, "Test Pool".to_string(), config, 1_000_000, 0);
|
||||||
pool_id,
|
|
||||||
"Test Pool".to_string(),
|
|
||||||
config,
|
|
||||||
1_000_000,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create spending request
|
// Create spending request
|
||||||
let request_id = test_hash(1);
|
let request_id = test_hash(1);
|
||||||
|
|
@ -1108,7 +1151,8 @@ mod tests {
|
||||||
"Test payment".to_string(),
|
"Test payment".to_string(),
|
||||||
proposer,
|
proposer,
|
||||||
100,
|
100,
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert!(pool.pending_requests.contains_key(&request_id));
|
assert!(pool.pending_requests.contains_key(&request_id));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,8 @@ impl VestingSchedule {
|
||||||
|
|
||||||
// Linear vesting between cliff and end
|
// Linear vesting between cliff and end
|
||||||
// vested = total * elapsed / vesting_duration
|
// vested = total * elapsed / vesting_duration
|
||||||
let vested = (self.total_amount as u128 * elapsed as u128 / self.vesting_duration as u128) as u64;
|
let vested =
|
||||||
|
(self.total_amount as u128 * elapsed as u128 / self.vesting_duration as u128) as u64;
|
||||||
vested
|
vested
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,7 +132,9 @@ impl VestingSchedule {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Current state of a vesting contract.
|
/// Current state of a vesting contract.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
pub enum VestingState {
|
pub enum VestingState {
|
||||||
/// Vesting is active.
|
/// Vesting is active.
|
||||||
Active,
|
Active,
|
||||||
|
|
@ -302,7 +305,9 @@ impl VestingContract {
|
||||||
|
|
||||||
/// Returns the unvested amount.
|
/// Returns the unvested amount.
|
||||||
pub fn unvested_amount(&self, timestamp: u64) -> u64 {
|
pub fn unvested_amount(&self, timestamp: u64) -> u64 {
|
||||||
self.schedule.total_amount.saturating_sub(self.vested_amount(timestamp))
|
self.schedule
|
||||||
|
.total_amount
|
||||||
|
.saturating_sub(self.vested_amount(timestamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Claims available tokens.
|
/// Claims available tokens.
|
||||||
|
|
@ -429,7 +434,8 @@ impl VestingManager {
|
||||||
schedule: VestingSchedule,
|
schedule: VestingSchedule,
|
||||||
description: String,
|
description: String,
|
||||||
) -> Result<Hash256, VestingError> {
|
) -> Result<Hash256, VestingError> {
|
||||||
let contract = VestingContract::new(beneficiary.clone(), grantor.clone(), schedule, description)?;
|
let contract =
|
||||||
|
VestingContract::new(beneficiary.clone(), grantor.clone(), schedule, description)?;
|
||||||
let id = contract.id;
|
let id = contract.id;
|
||||||
|
|
||||||
// Index by beneficiary
|
// Index by beneficiary
|
||||||
|
|
@ -462,11 +468,7 @@ impl VestingManager {
|
||||||
pub fn get_by_beneficiary(&self, beneficiary: &Address) -> Vec<&VestingContract> {
|
pub fn get_by_beneficiary(&self, beneficiary: &Address) -> Vec<&VestingContract> {
|
||||||
self.by_beneficiary
|
self.by_beneficiary
|
||||||
.get(beneficiary)
|
.get(beneficiary)
|
||||||
.map(|ids| {
|
.map(|ids| ids.iter().filter_map(|id| self.contracts.get(id)).collect())
|
||||||
ids.iter()
|
|
||||||
.filter_map(|id| self.contracts.get(id))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -474,22 +476,28 @@ impl VestingManager {
|
||||||
pub fn get_by_grantor(&self, grantor: &Address) -> Vec<&VestingContract> {
|
pub fn get_by_grantor(&self, grantor: &Address) -> Vec<&VestingContract> {
|
||||||
self.by_grantor
|
self.by_grantor
|
||||||
.get(grantor)
|
.get(grantor)
|
||||||
.map(|ids| {
|
.map(|ids| ids.iter().filter_map(|id| self.contracts.get(id)).collect())
|
||||||
ids.iter()
|
|
||||||
.filter_map(|id| self.contracts.get(id))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Claims tokens from a vesting contract.
|
/// Claims tokens from a vesting contract.
|
||||||
pub fn claim(&mut self, id: &Hash256, caller: &Address, timestamp: u64) -> Result<u64, VestingError> {
|
pub fn claim(
|
||||||
|
&mut self,
|
||||||
|
id: &Hash256,
|
||||||
|
caller: &Address,
|
||||||
|
timestamp: u64,
|
||||||
|
) -> Result<u64, VestingError> {
|
||||||
let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?;
|
let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?;
|
||||||
contract.claim(caller, timestamp)
|
contract.claim(caller, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Revokes a vesting contract.
|
/// Revokes a vesting contract.
|
||||||
pub fn revoke(&mut self, id: &Hash256, caller: &Address, timestamp: u64) -> Result<u64, VestingError> {
|
pub fn revoke(
|
||||||
|
&mut self,
|
||||||
|
id: &Hash256,
|
||||||
|
caller: &Address,
|
||||||
|
timestamp: u64,
|
||||||
|
) -> Result<u64, VestingError> {
|
||||||
let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?;
|
let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?;
|
||||||
contract.revoke(caller, timestamp)
|
contract.revoke(caller, timestamp)
|
||||||
}
|
}
|
||||||
|
|
@ -523,9 +531,18 @@ impl VestingManager {
|
||||||
let contracts: Vec<_> = self.contracts.values().collect();
|
let contracts: Vec<_> = self.contracts.values().collect();
|
||||||
VestingStats {
|
VestingStats {
|
||||||
total_contracts: contracts.len(),
|
total_contracts: contracts.len(),
|
||||||
active_contracts: contracts.iter().filter(|c| c.state == VestingState::Active).count(),
|
active_contracts: contracts
|
||||||
revoked_contracts: contracts.iter().filter(|c| c.state == VestingState::Revoked).count(),
|
.iter()
|
||||||
completed_contracts: contracts.iter().filter(|c| c.state == VestingState::Completed).count(),
|
.filter(|c| c.state == VestingState::Active)
|
||||||
|
.count(),
|
||||||
|
revoked_contracts: contracts
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.state == VestingState::Revoked)
|
||||||
|
.count(),
|
||||||
|
completed_contracts: contracts
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.state == VestingState::Completed)
|
||||||
|
.count(),
|
||||||
total_amount: contracts.iter().map(|c| c.schedule.total_amount).sum(),
|
total_amount: contracts.iter().map(|c| c.schedule.total_amount).sum(),
|
||||||
total_claimed: contracts.iter().map(|c| c.claimed_amount).sum(),
|
total_claimed: contracts.iter().map(|c| c.claimed_amount).sum(),
|
||||||
}
|
}
|
||||||
|
|
@ -556,7 +573,11 @@ mod tests {
|
||||||
fn test_address(n: u8) -> Address {
|
fn test_address(n: u8) -> Address {
|
||||||
let mut bytes = [0u8; 32];
|
let mut bytes = [0u8; 32];
|
||||||
bytes[0] = n;
|
bytes[0] = n;
|
||||||
Address::from_parts(synor_types::Network::Devnet, synor_types::address::AddressType::P2PKH, bytes)
|
Address::from_parts(
|
||||||
|
synor_types::Network::Devnet,
|
||||||
|
synor_types::address::AddressType::P2PKH,
|
||||||
|
bytes,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -591,7 +612,8 @@ mod tests {
|
||||||
grantor,
|
grantor,
|
||||||
VestingSchedule::new(1_000_000, 0, 12, start_time, true), // 1 year, no cliff
|
VestingSchedule::new(1_000_000, 0, 12, start_time, true), // 1 year, no cliff
|
||||||
"Test vesting".to_string(),
|
"Test vesting".to_string(),
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// After 6 months, should have ~50% vested
|
// After 6 months, should have ~50% vested
|
||||||
let six_months = 6 * 30 * 24 * 60 * 60;
|
let six_months = 6 * 30 * 24 * 60 * 60;
|
||||||
|
|
@ -617,7 +639,8 @@ mod tests {
|
||||||
grantor.clone(),
|
grantor.clone(),
|
||||||
VestingSchedule::new(1_000_000, 0, 12, 0, true),
|
VestingSchedule::new(1_000_000, 0, 12, 0, true),
|
||||||
"Revocable".to_string(),
|
"Revocable".to_string(),
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Revoke at 6 months
|
// Revoke at 6 months
|
||||||
let six_months = 6 * 30 * 24 * 60 * 60;
|
let six_months = 6 * 30 * 24 * 60 * 60;
|
||||||
|
|
@ -637,7 +660,8 @@ mod tests {
|
||||||
grantor.clone(),
|
grantor.clone(),
|
||||||
VestingSchedule::new(1_000_000, 0, 12, 0, false), // non-revocable
|
VestingSchedule::new(1_000_000, 0, 12, 0, false), // non-revocable
|
||||||
"Non-revocable".to_string(),
|
"Non-revocable".to_string(),
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let result = contract.revoke(&grantor, 0);
|
let result = contract.revoke(&grantor, 0);
|
||||||
assert!(matches!(result, Err(VestingError::NotRevocable)));
|
assert!(matches!(result, Err(VestingError::NotRevocable)));
|
||||||
|
|
@ -649,12 +673,14 @@ mod tests {
|
||||||
let beneficiary = test_address(1);
|
let beneficiary = test_address(1);
|
||||||
let grantor = test_address(2);
|
let grantor = test_address(2);
|
||||||
|
|
||||||
let id = manager.create_vesting(
|
let id = manager
|
||||||
beneficiary.clone(),
|
.create_vesting(
|
||||||
grantor,
|
beneficiary.clone(),
|
||||||
VestingSchedule::new(1_000_000, 0, 12, 0, true),
|
grantor,
|
||||||
"Test".to_string(),
|
VestingSchedule::new(1_000_000, 0, 12, 0, true),
|
||||||
).unwrap();
|
"Test".to_string(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert!(manager.get(&id).is_some());
|
assert!(manager.get(&id).is_some());
|
||||||
assert_eq!(manager.get_by_beneficiary(&beneficiary).len(), 1);
|
assert_eq!(manager.get_by_beneficiary(&beneficiary).len(), 1);
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@
|
||||||
//!
|
//!
|
||||||
//! Run with: cargo bench -p synor-mining
|
//! Run with: cargo bench -p synor-mining
|
||||||
|
|
||||||
use criterion::{
|
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||||
black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
|
|
||||||
};
|
|
||||||
use synor_mining::kheavyhash::{HashrateBenchmark, KHeavyHash, ParallelMiner};
|
use synor_mining::kheavyhash::{HashrateBenchmark, KHeavyHash, ParallelMiner};
|
||||||
use synor_mining::matrix::{gf_mul, HeavyMatrix, OptimizedMatrix};
|
use synor_mining::matrix::{gf_mul, HeavyMatrix, OptimizedMatrix};
|
||||||
use synor_mining::Target;
|
use synor_mining::Target;
|
||||||
|
|
@ -141,17 +139,13 @@ fn bench_mining_throughput(c: &mut Criterion) {
|
||||||
|
|
||||||
for count in [100, 1000, 10000] {
|
for count in [100, 1000, 10000] {
|
||||||
group.throughput(Throughput::Elements(count));
|
group.throughput(Throughput::Elements(count));
|
||||||
group.bench_with_input(
|
group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &count| {
|
||||||
BenchmarkId::from_parameter(count),
|
b.iter(|| {
|
||||||
&count,
|
for nonce in 0..count {
|
||||||
|b, &count| {
|
black_box(hasher.finalize(&pre_hash, nonce));
|
||||||
b.iter(|| {
|
}
|
||||||
for nonce in 0..count {
|
})
|
||||||
black_box(hasher.finalize(&pre_hash, nonce));
|
});
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
|
|
@ -168,9 +162,7 @@ fn bench_mining_with_target(c: &mut Criterion) {
|
||||||
group.bench_function("mine_easy_target", |b| {
|
group.bench_function("mine_easy_target", |b| {
|
||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| 0u64,
|
|| 0u64,
|
||||||
|start_nonce| {
|
|start_nonce| black_box(hasher.mine(&header, &target, start_nonce, 10000)),
|
||||||
black_box(hasher.mine(&header, &target, start_nonce, 10000))
|
|
||||||
},
|
|
||||||
criterion::BatchSize::SmallInput,
|
criterion::BatchSize::SmallInput,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
@ -212,13 +204,9 @@ fn bench_parallel_miner_creation(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("parallel_miner_create");
|
let mut group = c.benchmark_group("parallel_miner_create");
|
||||||
|
|
||||||
for threads in [1, 2, 4, 8] {
|
for threads in [1, 2, 4, 8] {
|
||||||
group.bench_with_input(
|
group.bench_with_input(BenchmarkId::from_parameter(threads), &threads, |b, &t| {
|
||||||
BenchmarkId::from_parameter(threads),
|
b.iter(|| black_box(ParallelMiner::new(t)))
|
||||||
&threads,
|
});
|
||||||
|b, &t| {
|
|
||||||
b.iter(|| black_box(ParallelMiner::new(t)))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
|
|
@ -296,11 +284,7 @@ fn bench_matrix_memory(c: &mut Criterion) {
|
||||||
|
|
||||||
// ==================== Criterion Groups ====================
|
// ==================== Criterion Groups ====================
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(gf_benches, bench_gf_mul, bench_gf_mul_single,);
|
||||||
gf_benches,
|
|
||||||
bench_gf_mul,
|
|
||||||
bench_gf_mul_single,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(
|
||||||
matrix_benches,
|
matrix_benches,
|
||||||
|
|
@ -332,10 +316,7 @@ criterion_group!(
|
||||||
bench_verify,
|
bench_verify,
|
||||||
);
|
);
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(parallel_benches, bench_parallel_miner_creation,);
|
||||||
parallel_benches,
|
|
||||||
bench_parallel_miner_creation,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_main!(
|
criterion_main!(
|
||||||
gf_benches,
|
gf_benches,
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@
|
||||||
//!
|
//!
|
||||||
//! Run with: cargo bench -p synor-mining --bench mining_bench
|
//! Run with: cargo bench -p synor-mining --bench mining_bench
|
||||||
|
|
||||||
use criterion::{
|
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||||
black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
|
|
||||||
};
|
|
||||||
use synor_mining::{
|
use synor_mining::{
|
||||||
kheavyhash::{KHeavyHash, ParallelMiner},
|
kheavyhash::{KHeavyHash, ParallelMiner},
|
||||||
matrix::{GfTables, HeavyMatrix, OptimizedMatrix},
|
matrix::{GfTables, HeavyMatrix, OptimizedMatrix},
|
||||||
miner::{BlockMiner, MinerConfig, ParallelBlockMiner},
|
miner::{BlockMiner, MinerConfig, ParallelBlockMiner},
|
||||||
template::{BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction},
|
template::{
|
||||||
|
BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction,
|
||||||
|
},
|
||||||
Target,
|
Target,
|
||||||
};
|
};
|
||||||
use synor_types::{Address, Hash256, Network};
|
use synor_types::{Address, Hash256, Network};
|
||||||
|
|
@ -146,9 +146,7 @@ fn matrix_initialization(c: &mut Criterion) {
|
||||||
b.iter(|| black_box(OptimizedMatrix::new()))
|
b.iter(|| black_box(OptimizedMatrix::new()))
|
||||||
});
|
});
|
||||||
|
|
||||||
group.bench_function("gf_tables_new", |b| {
|
group.bench_function("gf_tables_new", |b| b.iter(|| black_box(GfTables::new())));
|
||||||
b.iter(|| black_box(GfTables::new()))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Custom seed initialization
|
// Custom seed initialization
|
||||||
for seed_idx in [0u8, 42u8, 255u8] {
|
for seed_idx in [0u8, 42u8, 255u8] {
|
||||||
|
|
@ -156,9 +154,7 @@ fn matrix_initialization(c: &mut Criterion) {
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::new("matrix_from_seed", seed_idx),
|
BenchmarkId::new("matrix_from_seed", seed_idx),
|
||||||
&seed,
|
&seed,
|
||||||
|b, s| {
|
|b, s| b.iter(|| black_box(HeavyMatrix::from_seed(s))),
|
||||||
b.iter(|| black_box(HeavyMatrix::from_seed(s)))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,27 +228,15 @@ fn nonce_search_rate(c: &mut Criterion) {
|
||||||
for range in [1000u64, 5000, 10000] {
|
for range in [1000u64, 5000, 10000] {
|
||||||
let target = Target::from_bytes([0x00; 32]); // Impossible target
|
let target = Target::from_bytes([0x00; 32]); // Impossible target
|
||||||
group.throughput(Throughput::Elements(range));
|
group.throughput(Throughput::Elements(range));
|
||||||
group.bench_with_input(
|
group.bench_with_input(BenchmarkId::new("range_search", range), &range, |b, &r| {
|
||||||
BenchmarkId::new("range_search", range),
|
b.iter(|| black_box(hasher.mine(&header, &target, 0, r)))
|
||||||
&range,
|
});
|
||||||
|b, &r| {
|
|
||||||
b.iter(|| black_box(hasher.mine(&header, &target, 0, r)))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search with progress callback
|
// Search with progress callback
|
||||||
group.bench_function("search_with_callback", |b| {
|
group.bench_function("search_with_callback", |b| {
|
||||||
let target = Target::max();
|
let target = Target::max();
|
||||||
b.iter(|| {
|
b.iter(|| hasher.mine_with_callback(&header, &target, 0, 5000, |_tried, _nonce| true))
|
||||||
hasher.mine_with_callback(
|
|
||||||
&header,
|
|
||||||
&target,
|
|
||||||
0,
|
|
||||||
5000,
|
|
||||||
|_tried, _nonce| true,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
|
|
@ -265,14 +249,10 @@ fn parallel_nonce_search(c: &mut Criterion) {
|
||||||
let target = Target::max();
|
let target = Target::max();
|
||||||
|
|
||||||
for threads in [1, 2, 4] {
|
for threads in [1, 2, 4] {
|
||||||
group.bench_with_input(
|
group.bench_with_input(BenchmarkId::new("threads", threads), &threads, |b, &t| {
|
||||||
BenchmarkId::new("threads", threads),
|
let miner = ParallelMiner::new(t);
|
||||||
&threads,
|
b.iter(|| black_box(miner.mine(&header, &target, 0)))
|
||||||
|b, &t| {
|
});
|
||||||
let miner = ParallelMiner::new(t);
|
|
||||||
b.iter(|| black_box(miner.mine(&header, &target, 0)))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
|
|
@ -291,9 +271,8 @@ fn block_template_creation(c: &mut Criterion) {
|
||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| {
|
|| {
|
||||||
// Setup: create transactions
|
// Setup: create transactions
|
||||||
let txs: Vec<TemplateTransaction> = (0..count)
|
let txs: Vec<TemplateTransaction> =
|
||||||
.map(|i| test_template_tx(i as u64))
|
(0..count).map(|i| test_template_tx(i as u64)).collect();
|
||||||
.collect();
|
|
||||||
txs
|
txs
|
||||||
},
|
},
|
||||||
|txs| {
|
|txs| {
|
||||||
|
|
@ -331,17 +310,13 @@ fn template_header_creation(c: &mut Criterion) {
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::new("header_for_mining", tx_count),
|
BenchmarkId::new("header_for_mining", tx_count),
|
||||||
&template,
|
&template,
|
||||||
|b, tmpl| {
|
|b, tmpl| b.iter(|| black_box(tmpl.header_for_mining())),
|
||||||
b.iter(|| black_box(tmpl.header_for_mining()))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::new("header_with_extra_nonce", tx_count),
|
BenchmarkId::new("header_with_extra_nonce", tx_count),
|
||||||
&template,
|
&template,
|
||||||
|b, tmpl| {
|
|b, tmpl| b.iter(|| black_box(tmpl.header_with_extra_nonce(12345))),
|
||||||
b.iter(|| black_box(tmpl.header_with_extra_nonce(12345)))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -357,17 +332,13 @@ fn template_validation(c: &mut Criterion) {
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::new("validate", tx_count),
|
BenchmarkId::new("validate", tx_count),
|
||||||
&template,
|
&template,
|
||||||
|b, tmpl| {
|
|b, tmpl| b.iter(|| black_box(tmpl.validate())),
|
||||||
b.iter(|| black_box(tmpl.validate()))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::new("get_target", tx_count),
|
BenchmarkId::new("get_target", tx_count),
|
||||||
&template,
|
&template,
|
||||||
|b, tmpl| {
|
|b, tmpl| b.iter(|| black_box(tmpl.get_target())),
|
||||||
b.iter(|| black_box(tmpl.get_target()))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,9 +349,7 @@ fn coinbase_building(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("coinbase_building");
|
let mut group = c.benchmark_group("coinbase_building");
|
||||||
|
|
||||||
group.bench_function("coinbase_simple", |b| {
|
group.bench_function("coinbase_simple", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| black_box(CoinbaseBuilder::new(test_address(), 1000).build()))
|
||||||
black_box(CoinbaseBuilder::new(test_address(), 1000).build())
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group.bench_function("coinbase_with_data", |b| {
|
group.bench_function("coinbase_with_data", |b| {
|
||||||
|
|
@ -390,7 +359,7 @@ fn coinbase_building(c: &mut Criterion) {
|
||||||
.extra_data(b"Synor Mining Pool v1.0.0".to_vec())
|
.extra_data(b"Synor Mining Pool v1.0.0".to_vec())
|
||||||
.reward(500_00000000)
|
.reward(500_00000000)
|
||||||
.fees(1_00000000)
|
.fees(1_00000000)
|
||||||
.build()
|
.build(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -407,7 +376,7 @@ fn coinbase_building(c: &mut Criterion) {
|
||||||
CoinbaseBuilder::new(test_address(), 1000)
|
CoinbaseBuilder::new(test_address(), 1000)
|
||||||
.extra_data(data.clone())
|
.extra_data(data.clone())
|
||||||
.reward(500_00000000)
|
.reward(500_00000000)
|
||||||
.build()
|
.build(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -477,9 +446,7 @@ fn target_operations(c: &mut Criterion) {
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::new("from_bits", format!("{:08x}", bits)),
|
BenchmarkId::new("from_bits", format!("{:08x}", bits)),
|
||||||
&bits,
|
&bits,
|
||||||
|b, &bit| {
|
|b, &bit| b.iter(|| black_box(Target::from_bits(bit))),
|
||||||
b.iter(|| black_box(Target::from_bits(bit)))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -607,11 +574,7 @@ criterion_group!(
|
||||||
matrix_memory_patterns,
|
matrix_memory_patterns,
|
||||||
);
|
);
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(nonce_benches, nonce_search_rate, parallel_nonce_search,);
|
||||||
nonce_benches,
|
|
||||||
nonce_search_rate,
|
|
||||||
parallel_nonce_search,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(
|
||||||
template_benches,
|
template_benches,
|
||||||
|
|
@ -628,16 +591,9 @@ criterion_group!(
|
||||||
parallel_block_miner_creation,
|
parallel_block_miner_creation,
|
||||||
);
|
);
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(misc_benches, target_operations, mining_stats_operations,);
|
||||||
misc_benches,
|
|
||||||
target_operations,
|
|
||||||
mining_stats_operations,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(e2e_benches, end_to_end_mining,);
|
||||||
e2e_benches,
|
|
||||||
end_to_end_mining,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_main!(
|
criterion_main!(
|
||||||
throughput_benches,
|
throughput_benches,
|
||||||
|
|
|
||||||
|
|
@ -251,12 +251,7 @@ impl ParallelMiner {
|
||||||
/// Mines using all threads.
|
/// Mines using all threads.
|
||||||
///
|
///
|
||||||
/// Each thread gets a different nonce range to search.
|
/// Each thread gets a different nonce range to search.
|
||||||
pub fn mine(
|
pub fn mine(&self, header: &[u8], target: &Target, start_nonce: u64) -> Option<PowHash> {
|
||||||
&self,
|
|
||||||
header: &[u8],
|
|
||||||
target: &Target,
|
|
||||||
start_nonce: u64,
|
|
||||||
) -> Option<PowHash> {
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,13 @@ pub mod template;
|
||||||
|
|
||||||
pub use kheavyhash::{KHeavyHash, PowHash};
|
pub use kheavyhash::{KHeavyHash, PowHash};
|
||||||
pub use matrix::HeavyMatrix;
|
pub use matrix::HeavyMatrix;
|
||||||
pub use miner::{BlockMiner, MinerCommand, MinerConfig, MinerEvent, MiningResult, ParallelBlockMiner};
|
pub use miner::{
|
||||||
|
BlockMiner, MinerCommand, MinerConfig, MinerEvent, MiningResult, ParallelBlockMiner,
|
||||||
|
};
|
||||||
pub use stratum::{StratumClient, StratumJob, StratumServer};
|
pub use stratum::{StratumClient, StratumJob, StratumServer};
|
||||||
pub use template::{BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction};
|
pub use template::{
|
||||||
|
BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction,
|
||||||
|
};
|
||||||
|
|
||||||
use synor_types::{Address, Hash256};
|
use synor_types::{Address, Hash256};
|
||||||
|
|
||||||
|
|
@ -297,27 +301,24 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_target_comparison() {
|
fn test_target_comparison() {
|
||||||
let target = Target::from_bytes([
|
let target = Target::from_bytes([
|
||||||
0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff, 0xff,
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Hash with more leading zeros should pass
|
// Hash with more leading zeros should pass
|
||||||
let easy_hash = Hash256::from_bytes([
|
let easy_hash = Hash256::from_bytes([
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x01,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
||||||
]);
|
]);
|
||||||
assert!(target.is_met_by(&easy_hash));
|
assert!(target.is_met_by(&easy_hash));
|
||||||
|
|
||||||
// Hash with fewer leading zeros should fail
|
// Hash with fewer leading zeros should fail
|
||||||
let hard_hash = Hash256::from_bytes([
|
let hard_hash = Hash256::from_bytes([
|
||||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
]);
|
]);
|
||||||
assert!(!target.is_met_by(&hard_hash));
|
assert!(!target.is_met_by(&hard_hash));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
//! The matrix is a 64x64 matrix where each element is in GF(2^8).
|
//! The matrix is a 64x64 matrix where each element is in GF(2^8).
|
||||||
//! Matrix multiplication is performed using Galois field arithmetic.
|
//! Matrix multiplication is performed using Galois field arithmetic.
|
||||||
|
|
||||||
|
|
||||||
/// The irreducible polynomial for GF(2^8): x^8 + x^4 + x^3 + x + 1
|
/// The irreducible polynomial for GF(2^8): x^8 + x^4 + x^3 + x + 1
|
||||||
const GF_POLY: u16 = 0x11B;
|
const GF_POLY: u16 = 0x11B;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,8 @@ impl StratumServer {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reconstruct header: header_hash + extra_nonce1 + extra_nonce2
|
// Reconstruct header: header_hash + extra_nonce1 + extra_nonce2
|
||||||
let mut header_data = Vec::with_capacity(header_hash.len() + extra_nonce1.len() + extra_nonce2.len());
|
let mut header_data =
|
||||||
|
Vec::with_capacity(header_hash.len() + extra_nonce1.len() + extra_nonce2.len());
|
||||||
header_data.extend_from_slice(&header_hash);
|
header_data.extend_from_slice(&header_hash);
|
||||||
header_data.extend_from_slice(&extra_nonce1);
|
header_data.extend_from_slice(&extra_nonce1);
|
||||||
header_data.extend_from_slice(&extra_nonce2);
|
header_data.extend_from_slice(&extra_nonce2);
|
||||||
|
|
@ -442,19 +443,13 @@ impl StratumServer {
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = request.get("id").and_then(|v| v.as_u64()).unwrap_or(0);
|
let id = request.get("id").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
let method = request
|
let method = request.get("method").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
.get("method")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
let response = match method {
|
let response = match method {
|
||||||
"mining.subscribe" => {
|
"mining.subscribe" => {
|
||||||
let extra_nonce = self.allocate_extra_nonce();
|
let extra_nonce = self.allocate_extra_nonce();
|
||||||
let result = serde_json::json!([
|
let result =
|
||||||
[["mining.notify", "1"]],
|
serde_json::json!([[["mining.notify", "1"]], extra_nonce, 8]);
|
||||||
extra_nonce,
|
|
||||||
8
|
|
||||||
]);
|
|
||||||
StratumResponse {
|
StratumResponse {
|
||||||
id,
|
id,
|
||||||
result: Some(result),
|
result: Some(result),
|
||||||
|
|
@ -463,7 +458,8 @@ impl StratumServer {
|
||||||
}
|
}
|
||||||
"mining.authorize" => {
|
"mining.authorize" => {
|
||||||
let params = request.get("params").cloned().unwrap_or_default();
|
let params = request.get("params").cloned().unwrap_or_default();
|
||||||
let worker = params.get(0).and_then(|v| v.as_str()).unwrap_or("unknown");
|
let worker =
|
||||||
|
params.get(0).and_then(|v| v.as_str()).unwrap_or("unknown");
|
||||||
_worker_name = Some(worker.to_string());
|
_worker_name = Some(worker.to_string());
|
||||||
authorized = true;
|
authorized = true;
|
||||||
self.register_worker(worker.to_string(), self.allocate_extra_nonce());
|
self.register_worker(worker.to_string(), self.allocate_extra_nonce());
|
||||||
|
|
@ -485,10 +481,26 @@ impl StratumServer {
|
||||||
// Parse submission
|
// Parse submission
|
||||||
let params = request.get("params").cloned().unwrap_or_default();
|
let params = request.get("params").cloned().unwrap_or_default();
|
||||||
let submission = ShareSubmission {
|
let submission = ShareSubmission {
|
||||||
worker: params.get(0).and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
worker: params
|
||||||
job_id: params.get(1).and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
.get(0)
|
||||||
extra_nonce2: params.get(2).and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
.and_then(|v| v.as_str())
|
||||||
nonce: params.get(3).and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
.unwrap_or("")
|
||||||
|
.to_string(),
|
||||||
|
job_id: params
|
||||||
|
.get(1)
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string(),
|
||||||
|
extra_nonce2: params
|
||||||
|
.get(2)
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string(),
|
||||||
|
nonce: params
|
||||||
|
.get(3)
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string(),
|
||||||
timestamp: current_timestamp(),
|
timestamp: current_timestamp(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -680,7 +692,10 @@ mod tests {
|
||||||
hashrate: 1000.0,
|
hashrate: 1000.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(info.shares_valid + info.shares_invalid, info.shares_submitted);
|
assert_eq!(
|
||||||
|
info.shares_valid + info.shares_invalid,
|
||||||
|
info.shares_submitted
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -699,7 +714,7 @@ mod tests {
|
||||||
// Create a job with easy targets
|
// Create a job with easy targets
|
||||||
let job = StratumJob {
|
let job = StratumJob {
|
||||||
job_id: "1".to_string(),
|
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
|
share_target: "ff".repeat(32), // All 0xff = easy target
|
||||||
block_target: "00".repeat(32), // All 0x00 = impossible target
|
block_target: "00".repeat(32), // All 0x00 = impossible target
|
||||||
timestamp: 1234567890,
|
timestamp: 1234567890,
|
||||||
|
|
@ -745,7 +760,11 @@ mod tests {
|
||||||
timestamp: 1234567890,
|
timestamp: 1234567890,
|
||||||
};
|
};
|
||||||
let result = server.validate_share(&valid_submission);
|
let result = server.validate_share(&valid_submission);
|
||||||
assert!(matches!(result, ShareResult::ValidShare), "Expected ValidShare, got {:?}", result);
|
assert!(
|
||||||
|
matches!(result, ShareResult::ValidShare),
|
||||||
|
"Expected ValidShare, got {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
// Test duplicate share
|
// Test duplicate share
|
||||||
let duplicate_submission = ShareSubmission {
|
let duplicate_submission = ShareSubmission {
|
||||||
|
|
|
||||||
|
|
@ -239,13 +239,17 @@ impl BlockTemplateBuilder {
|
||||||
|
|
||||||
/// Builds the block template.
|
/// Builds the block template.
|
||||||
pub fn build(mut self, template_id: u64) -> Result<BlockTemplate, MiningError> {
|
pub fn build(mut self, template_id: u64) -> Result<BlockTemplate, MiningError> {
|
||||||
let selected = self.selected_parent.or_else(|| self.parents.first().copied())
|
let selected = self
|
||||||
|
.selected_parent
|
||||||
|
.or_else(|| self.parents.first().copied())
|
||||||
.ok_or_else(|| MiningError::InvalidTemplate("No parents".into()))?;
|
.ok_or_else(|| MiningError::InvalidTemplate("No parents".into()))?;
|
||||||
|
|
||||||
// Build header data first before taking coinbase
|
// Build header data first before taking coinbase
|
||||||
let header_data = self.build_header(&selected)?;
|
let header_data = self.build_header(&selected)?;
|
||||||
|
|
||||||
let coinbase = self.coinbase.take()
|
let coinbase = self
|
||||||
|
.coinbase
|
||||||
|
.take()
|
||||||
.ok_or_else(|| MiningError::InvalidTemplate("No coinbase".into()))?;
|
.ok_or_else(|| MiningError::InvalidTemplate("No coinbase".into()))?;
|
||||||
|
|
||||||
// Calculate fees
|
// Calculate fees
|
||||||
|
|
@ -321,7 +325,11 @@ impl BlockTemplateBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all txids
|
// Collect all txids
|
||||||
let txids: Vec<&[u8; 32]> = self.transactions.iter().map(|tx| tx.txid.as_bytes()).collect();
|
let txids: Vec<&[u8; 32]> = self
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.map(|tx| tx.txid.as_bytes())
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Simple merkle tree
|
// Simple merkle tree
|
||||||
merkle_root(&txids)
|
merkle_root(&txids)
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@ use libp2p::{
|
||||||
gossipsub::{self, IdentTopic, MessageAuthenticity, MessageId, ValidationMode},
|
gossipsub::{self, IdentTopic, MessageAuthenticity, MessageId, ValidationMode},
|
||||||
identify,
|
identify,
|
||||||
kad::{self, store::MemoryStore},
|
kad::{self, store::MemoryStore},
|
||||||
mdns,
|
mdns, ping,
|
||||||
ping,
|
|
||||||
request_response::{self, ProtocolSupport},
|
request_response::{self, ProtocolSupport},
|
||||||
swarm::NetworkBehaviour,
|
swarm::NetworkBehaviour,
|
||||||
PeerId, StreamProtocol,
|
PeerId, StreamProtocol,
|
||||||
|
|
@ -35,7 +34,10 @@ pub struct SynorBehaviour {
|
||||||
|
|
||||||
impl SynorBehaviour {
|
impl SynorBehaviour {
|
||||||
/// Creates a new behaviour with the given configuration.
|
/// Creates a new behaviour with the given configuration.
|
||||||
pub fn new(keypair: &libp2p::identity::Keypair, config: &NetworkConfig) -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new(
|
||||||
|
keypair: &libp2p::identity::Keypair,
|
||||||
|
config: &NetworkConfig,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let local_peer_id = keypair.public().to_peer_id();
|
let local_peer_id = keypair.public().to_peer_id();
|
||||||
|
|
||||||
// GossipSub configuration
|
// GossipSub configuration
|
||||||
|
|
@ -68,21 +70,15 @@ impl SynorBehaviour {
|
||||||
|
|
||||||
// Request-response configuration
|
// Request-response configuration
|
||||||
let request_response = request_response::Behaviour::new(
|
let request_response = request_response::Behaviour::new(
|
||||||
[(
|
[(StreamProtocol::new(SYNOR_PROTOCOL), ProtocolSupport::Full)],
|
||||||
StreamProtocol::new(SYNOR_PROTOCOL),
|
request_response::Config::default().with_request_timeout(config.sync.request_timeout),
|
||||||
ProtocolSupport::Full,
|
|
||||||
)],
|
|
||||||
request_response::Config::default()
|
|
||||||
.with_request_timeout(config.sync.request_timeout),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Identify configuration
|
// Identify configuration
|
||||||
let identify_config = identify::Config::new(
|
let identify_config =
|
||||||
"/synor/id/1.0.0".to_string(),
|
identify::Config::new("/synor/id/1.0.0".to_string(), keypair.public())
|
||||||
keypair.public(),
|
.with_agent_version(crate::user_agent())
|
||||||
)
|
.with_push_listen_addr_updates(true);
|
||||||
.with_agent_version(crate::user_agent())
|
|
||||||
.with_push_listen_addr_updates(true);
|
|
||||||
|
|
||||||
let identify = identify::Behaviour::new(identify_config);
|
let identify = identify::Behaviour::new(identify_config);
|
||||||
|
|
||||||
|
|
@ -94,10 +90,7 @@ impl SynorBehaviour {
|
||||||
);
|
);
|
||||||
|
|
||||||
// mDNS configuration
|
// mDNS configuration
|
||||||
let mdns = mdns::tokio::Behaviour::new(
|
let mdns = mdns::tokio::Behaviour::new(mdns::Config::default(), local_peer_id)?;
|
||||||
mdns::Config::default(),
|
|
||||||
local_peer_id,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(SynorBehaviour {
|
Ok(SynorBehaviour {
|
||||||
gossipsub,
|
gossipsub,
|
||||||
|
|
@ -135,22 +128,35 @@ impl SynorBehaviour {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Publishes a block announcement.
|
/// Publishes a block announcement.
|
||||||
pub fn publish_block(&mut self, data: Vec<u8>) -> Result<gossipsub::MessageId, gossipsub::PublishError> {
|
pub fn publish_block(
|
||||||
|
&mut self,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<gossipsub::MessageId, gossipsub::PublishError> {
|
||||||
self.publish(topics::BLOCKS, data)
|
self.publish(topics::BLOCKS, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Publishes a transaction announcement.
|
/// Publishes a transaction announcement.
|
||||||
pub fn publish_transaction(&mut self, data: Vec<u8>) -> Result<gossipsub::MessageId, gossipsub::PublishError> {
|
pub fn publish_transaction(
|
||||||
|
&mut self,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<gossipsub::MessageId, gossipsub::PublishError> {
|
||||||
self.publish(topics::TRANSACTIONS, data)
|
self.publish(topics::TRANSACTIONS, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Publishes a header announcement.
|
/// Publishes a header announcement.
|
||||||
pub fn publish_header(&mut self, data: Vec<u8>) -> Result<gossipsub::MessageId, gossipsub::PublishError> {
|
pub fn publish_header(
|
||||||
|
&mut self,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<gossipsub::MessageId, gossipsub::PublishError> {
|
||||||
self.publish(topics::HEADERS, data)
|
self.publish(topics::HEADERS, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a request to a peer.
|
/// Sends a request to a peer.
|
||||||
pub fn send_request(&mut self, peer: &PeerId, request: SynorRequest) -> request_response::OutboundRequestId {
|
pub fn send_request(
|
||||||
|
&mut self,
|
||||||
|
peer: &PeerId,
|
||||||
|
request: SynorRequest,
|
||||||
|
) -> request_response::OutboundRequestId {
|
||||||
self.request_response.send_request(peer, request)
|
self.request_response.send_request(peer, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,10 +197,7 @@ impl SynorBehaviour {
|
||||||
/// Returns peers subscribed to a topic.
|
/// Returns peers subscribed to a topic.
|
||||||
pub fn topic_peers(&self, topic: &str) -> Vec<PeerId> {
|
pub fn topic_peers(&self, topic: &str) -> Vec<PeerId> {
|
||||||
let topic = IdentTopic::new(topic);
|
let topic = IdentTopic::new(topic);
|
||||||
self.gossipsub
|
self.gossipsub.mesh_peers(&topic.hash()).cloned().collect()
|
||||||
.mesh_peers(&topic.hash())
|
|
||||||
.cloned()
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ impl Default for NetworkConfig {
|
||||||
NetworkConfig {
|
NetworkConfig {
|
||||||
chain_id: ChainId::Mainnet,
|
chain_id: ChainId::Mainnet,
|
||||||
listen_addresses: vec![
|
listen_addresses: vec![
|
||||||
format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT).parse().unwrap(),
|
format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT)
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
format!("/ip6/::/tcp/{}", DEFAULT_PORT).parse().unwrap(),
|
format!("/ip6/::/tcp/{}", DEFAULT_PORT).parse().unwrap(),
|
||||||
],
|
],
|
||||||
bootstrap_peers: Vec::new(),
|
bootstrap_peers: Vec::new(),
|
||||||
|
|
@ -74,11 +76,9 @@ impl NetworkConfig {
|
||||||
NetworkConfig {
|
NetworkConfig {
|
||||||
chain_id: ChainId::Testnet,
|
chain_id: ChainId::Testnet,
|
||||||
bootstrap_peers: testnet_bootstrap_peers(),
|
bootstrap_peers: testnet_bootstrap_peers(),
|
||||||
listen_addresses: vec![
|
listen_addresses: vec![format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT + 1000)
|
||||||
format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT + 1000)
|
.parse()
|
||||||
.parse()
|
.unwrap()],
|
||||||
.unwrap(),
|
|
||||||
],
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -229,10 +229,7 @@ fn testnet_bootstrap_peers() -> Vec<Multiaddr> {
|
||||||
// "/dns4/testnet-seed3.synor.cc/tcp/17511/p2p/12D3KooW...",
|
// "/dns4/testnet-seed3.synor.cc/tcp/17511/p2p/12D3KooW...",
|
||||||
];
|
];
|
||||||
|
|
||||||
seeds
|
seeds.iter().filter_map(|s| s.parse().ok()).collect()
|
||||||
.iter()
|
|
||||||
.filter_map(|s| s.parse().ok())
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns devnet bootstrap peers for local development.
|
/// Returns devnet bootstrap peers for local development.
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ impl Default for EclipseConfig {
|
||||||
min_outbound: 8,
|
min_outbound: 8,
|
||||||
anchor_count: 4,
|
anchor_count: 4,
|
||||||
rotation_interval: Duration::from_secs(3600), // 1 hour
|
rotation_interval: Duration::from_secs(3600), // 1 hour
|
||||||
rotation_percentage: 0.1, // 10%
|
rotation_percentage: 0.1, // 10%
|
||||||
trial_period: Duration::from_secs(300), // 5 minutes
|
trial_period: Duration::from_secs(300), // 5 minutes
|
||||||
max_trial_peers: 5,
|
max_trial_peers: 5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -414,7 +414,9 @@ mod tests {
|
||||||
|
|
||||||
// Different subnet should be allowed
|
// Different subnet should be allowed
|
||||||
let different_ip = Some(IpAddr::V4(Ipv4Addr::new(10, 1, 1, 1)));
|
let different_ip = Some(IpAddr::V4(Ipv4Addr::new(10, 1, 1, 1)));
|
||||||
assert!(protection.check_connection(&peer3, different_ip, false).is_ok());
|
assert!(protection
|
||||||
|
.check_connection(&peer3, different_ip, false)
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,9 @@ pub use behaviour::SynorBehaviour;
|
||||||
pub use config::NetworkConfig;
|
pub use config::NetworkConfig;
|
||||||
pub use eclipse::{EclipseConfig, EclipseProtection, EclipseStats, PeerType};
|
pub use eclipse::{EclipseConfig, EclipseProtection, EclipseStats, PeerType};
|
||||||
pub use message::{BlockAnnouncement, Message, TransactionAnnouncement};
|
pub use message::{BlockAnnouncement, Message, TransactionAnnouncement};
|
||||||
pub use partition::{PartitionConfig, PartitionDetector, PartitionReason, PartitionStats, PartitionStatus};
|
pub use partition::{
|
||||||
|
PartitionConfig, PartitionDetector, PartitionReason, PartitionStats, PartitionStatus,
|
||||||
|
};
|
||||||
pub use peer::{PeerInfo, PeerManager, PeerState};
|
pub use peer::{PeerInfo, PeerManager, PeerState};
|
||||||
pub use protocol::{SynorProtocol, SynorRequest, SynorResponse};
|
pub use protocol::{SynorProtocol, SynorRequest, SynorResponse};
|
||||||
pub use rate_limit::{
|
pub use rate_limit::{
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,9 @@ impl Headers {
|
||||||
|
|
||||||
/// Creates an empty headers response.
|
/// Creates an empty headers response.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Headers { headers: Vec::new() }
|
Headers {
|
||||||
|
headers: Vec::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,15 +119,24 @@ pub enum PartitionReason {
|
||||||
BlockProductionStalled { duration_secs: u64 },
|
BlockProductionStalled { duration_secs: u64 },
|
||||||
/// Block production rate significantly lower than expected.
|
/// Block production rate significantly lower than expected.
|
||||||
/// Rates are stored as millibps (blocks per 1000 seconds) for precision.
|
/// Rates are stored as millibps (blocks per 1000 seconds) for precision.
|
||||||
LowBlockRate { current_rate_millibps: u32, expected_rate_millibps: u32 },
|
LowBlockRate {
|
||||||
|
current_rate_millibps: u32,
|
||||||
|
expected_rate_millibps: u32,
|
||||||
|
},
|
||||||
/// Our tips don't match peer tips.
|
/// Our tips don't match peer tips.
|
||||||
/// `matching_peers_pct` is 0-100.
|
/// `matching_peers_pct` is 0-100.
|
||||||
TipDivergence { matching_peers_pct: u8, threshold_pct: u8 },
|
TipDivergence {
|
||||||
|
matching_peers_pct: u8,
|
||||||
|
threshold_pct: u8,
|
||||||
|
},
|
||||||
/// Tip is too old.
|
/// Tip is too old.
|
||||||
/// `age_secs` and `max_age_secs` are in seconds.
|
/// `age_secs` and `max_age_secs` are in seconds.
|
||||||
StaleTip { age_secs: u64, max_age_secs: u64 },
|
StaleTip { age_secs: u64, max_age_secs: u64 },
|
||||||
/// Protocol version mismatch with majority.
|
/// Protocol version mismatch with majority.
|
||||||
ProtocolVersionSkew { our_version: u32, majority_version: u32 },
|
ProtocolVersionSkew {
|
||||||
|
our_version: u32,
|
||||||
|
majority_version: u32,
|
||||||
|
},
|
||||||
/// Most peers have higher blue scores, we may be on a minority fork.
|
/// Most peers have higher blue scores, we may be on a minority fork.
|
||||||
BehindNetwork { our_score: u64, network_score: u64 },
|
BehindNetwork { our_score: u64, network_score: u64 },
|
||||||
/// All connections are inbound (potential eclipse attack).
|
/// All connections are inbound (potential eclipse attack).
|
||||||
|
|
@ -141,40 +150,59 @@ impl PartitionReason {
|
||||||
pub fn description(&self) -> String {
|
pub fn description(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
PartitionReason::InsufficientPeers { current, required } => {
|
PartitionReason::InsufficientPeers { current, required } => {
|
||||||
format!("Only {} peers connected, need at least {}", current, required)
|
format!(
|
||||||
|
"Only {} peers connected, need at least {}",
|
||||||
|
current, required
|
||||||
|
)
|
||||||
}
|
}
|
||||||
PartitionReason::InsufficientOutbound { current, required } => {
|
PartitionReason::InsufficientOutbound { current, required } => {
|
||||||
format!("Only {} outbound peers, need at least {}", current, required)
|
format!(
|
||||||
|
"Only {} outbound peers, need at least {}",
|
||||||
|
current, required
|
||||||
|
)
|
||||||
}
|
}
|
||||||
PartitionReason::LowSubnetDiversity { current, required } => {
|
PartitionReason::LowSubnetDiversity { current, required } => {
|
||||||
format!("Only {} unique subnets, need at least {}", current, required)
|
format!(
|
||||||
|
"Only {} unique subnets, need at least {}",
|
||||||
|
current, required
|
||||||
|
)
|
||||||
}
|
}
|
||||||
PartitionReason::SubnetConcentration { subnet, percentage } => {
|
PartitionReason::SubnetConcentration { subnet, percentage } => {
|
||||||
format!(
|
format!(
|
||||||
"Subnet {:X} has {}% of peers, max allowed 50%",
|
"Subnet {:X} has {}% of peers, max allowed 50%",
|
||||||
subnet,
|
subnet, percentage
|
||||||
percentage
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PartitionReason::BlockProductionStalled { duration_secs } => {
|
PartitionReason::BlockProductionStalled { duration_secs } => {
|
||||||
format!("No new blocks for {} seconds", duration_secs)
|
format!("No new blocks for {} seconds", duration_secs)
|
||||||
}
|
}
|
||||||
PartitionReason::LowBlockRate { current_rate_millibps, expected_rate_millibps } => {
|
PartitionReason::LowBlockRate {
|
||||||
|
current_rate_millibps,
|
||||||
|
expected_rate_millibps,
|
||||||
|
} => {
|
||||||
format!(
|
format!(
|
||||||
"Block rate {:.2}/s, expected {:.2}/s",
|
"Block rate {:.2}/s, expected {:.2}/s",
|
||||||
*current_rate_millibps as f64 / 1000.0,
|
*current_rate_millibps as f64 / 1000.0,
|
||||||
*expected_rate_millibps as f64 / 1000.0
|
*expected_rate_millibps as f64 / 1000.0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PartitionReason::TipDivergence { matching_peers_pct, threshold_pct } => {
|
PartitionReason::TipDivergence {
|
||||||
|
matching_peers_pct,
|
||||||
|
threshold_pct,
|
||||||
|
} => {
|
||||||
format!(
|
format!(
|
||||||
"Only {}% of peers agree on tips, need {}%",
|
"Only {}% of peers agree on tips, need {}%",
|
||||||
matching_peers_pct,
|
matching_peers_pct, threshold_pct
|
||||||
threshold_pct
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PartitionReason::StaleTip { age_secs, max_age_secs } => {
|
PartitionReason::StaleTip {
|
||||||
format!("Best tip is {} seconds old, max allowed {} seconds", age_secs, max_age_secs)
|
age_secs,
|
||||||
|
max_age_secs,
|
||||||
|
} => {
|
||||||
|
format!(
|
||||||
|
"Best tip is {} seconds old, max allowed {} seconds",
|
||||||
|
age_secs, max_age_secs
|
||||||
|
)
|
||||||
}
|
}
|
||||||
PartitionReason::ProtocolVersionSkew {
|
PartitionReason::ProtocolVersionSkew {
|
||||||
our_version,
|
our_version,
|
||||||
|
|
@ -404,12 +432,7 @@ impl PartitionDetector {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records a peer connection.
|
/// Records a peer connection.
|
||||||
pub fn record_peer_connected(
|
pub fn record_peer_connected(&self, peer_id: PeerId, ip: Option<IpAddr>, is_outbound: bool) {
|
||||||
&self,
|
|
||||||
peer_id: PeerId,
|
|
||||||
ip: Option<IpAddr>,
|
|
||||||
is_outbound: bool,
|
|
||||||
) {
|
|
||||||
let info = PeerPartitionInfo::new(peer_id, ip, is_outbound);
|
let info = PeerPartitionInfo::new(peer_id, ip, is_outbound);
|
||||||
self.peers.write().insert(peer_id, info);
|
self.peers.write().insert(peer_id, info);
|
||||||
|
|
||||||
|
|
@ -743,7 +766,9 @@ impl PartitionDetector {
|
||||||
// Any critical reason means we're partitioned
|
// Any critical reason means we're partitioned
|
||||||
let mut all_reasons = critical_reasons;
|
let mut all_reasons = critical_reasons;
|
||||||
all_reasons.extend(warning_reasons);
|
all_reasons.extend(warning_reasons);
|
||||||
PartitionStatus::Partitioned { reasons: all_reasons }
|
PartitionStatus::Partitioned {
|
||||||
|
reasons: all_reasons,
|
||||||
|
}
|
||||||
} else if !warning_reasons.is_empty() {
|
} else if !warning_reasons.is_empty() {
|
||||||
PartitionStatus::Degraded {
|
PartitionStatus::Degraded {
|
||||||
reasons: warning_reasons,
|
reasons: warning_reasons,
|
||||||
|
|
@ -801,11 +826,7 @@ impl PartitionDetector {
|
||||||
|
|
||||||
let (status_str, warning_count, critical_count) = match &status {
|
let (status_str, warning_count, critical_count) = match &status {
|
||||||
PartitionStatus::Connected => ("Connected".to_string(), 0, 0),
|
PartitionStatus::Connected => ("Connected".to_string(), 0, 0),
|
||||||
PartitionStatus::Degraded { reasons } => (
|
PartitionStatus::Degraded { reasons } => ("Degraded".to_string(), reasons.len(), 0),
|
||||||
"Degraded".to_string(),
|
|
||||||
reasons.len(),
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
PartitionStatus::Partitioned { reasons } => {
|
PartitionStatus::Partitioned { reasons } => {
|
||||||
let critical = reasons.iter().filter(|r| r.is_critical()).count();
|
let critical = reasons.iter().filter(|r| r.is_critical()).count();
|
||||||
(
|
(
|
||||||
|
|
@ -966,10 +987,9 @@ mod tests {
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
PartitionStatus::Degraded { reasons } => {
|
PartitionStatus::Degraded { reasons } => {
|
||||||
assert!(reasons.iter().any(|r| matches!(
|
assert!(reasons
|
||||||
r,
|
.iter()
|
||||||
PartitionReason::InsufficientPeers { .. }
|
.any(|r| matches!(r, PartitionReason::InsufficientPeers { .. })));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
_ => panic!("Expected degraded status"),
|
_ => panic!("Expected degraded status"),
|
||||||
}
|
}
|
||||||
|
|
@ -993,10 +1013,9 @@ mod tests {
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
PartitionStatus::Partitioned { reasons } => {
|
PartitionStatus::Partitioned { reasons } => {
|
||||||
assert!(reasons.iter().any(|r| matches!(
|
assert!(reasons
|
||||||
r,
|
.iter()
|
||||||
PartitionReason::NoOutboundConnections
|
.any(|r| matches!(r, PartitionReason::NoOutboundConnections)));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
_ => panic!("Expected partitioned status"),
|
_ => panic!("Expected partitioned status"),
|
||||||
}
|
}
|
||||||
|
|
@ -1098,9 +1117,7 @@ mod tests {
|
||||||
required: 3,
|
required: 3,
|
||||||
},
|
},
|
||||||
PartitionReason::NoOutboundConnections,
|
PartitionReason::NoOutboundConnections,
|
||||||
PartitionReason::BlockProductionStalled {
|
PartitionReason::BlockProductionStalled { duration_secs: 60 },
|
||||||
duration_secs: 60,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for reason in reasons {
|
for reason in reasons {
|
||||||
|
|
|
||||||
|
|
@ -334,10 +334,9 @@ impl PeerManager {
|
||||||
|
|
||||||
// Ban after releasing the peers lock to avoid deadlock
|
// Ban after releasing the peers lock to avoid deadlock
|
||||||
if should_ban {
|
if should_ban {
|
||||||
self.banned.write().insert(
|
self.banned
|
||||||
*peer_id,
|
.write()
|
||||||
Instant::now() + self.ban_duration,
|
.insert(*peer_id, Instant::now() + self.ban_duration);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
@ -436,8 +435,14 @@ impl PeerManager {
|
||||||
PeerStats {
|
PeerStats {
|
||||||
total: peers.len(),
|
total: peers.len(),
|
||||||
active: active.len(),
|
active: active.len(),
|
||||||
inbound: active.iter().filter(|p| p.direction == Direction::Inbound).count(),
|
inbound: active
|
||||||
outbound: active.iter().filter(|p| p.direction == Direction::Outbound).count(),
|
.iter()
|
||||||
|
.filter(|p| p.direction == Direction::Inbound)
|
||||||
|
.count(),
|
||||||
|
outbound: active
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.direction == Direction::Outbound)
|
||||||
|
.count(),
|
||||||
banned: self.banned.read().len(),
|
banned: self.banned.read().len(),
|
||||||
avg_latency: active
|
avg_latency: active
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -269,13 +269,9 @@ impl Codec for SynorCodec {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a request-response behaviour for the Synor protocol.
|
/// Creates a request-response behaviour for the Synor protocol.
|
||||||
pub fn create_request_response_behaviour(
|
pub fn create_request_response_behaviour() -> request_response::Behaviour<SynorCodec> {
|
||||||
) -> request_response::Behaviour<SynorCodec> {
|
|
||||||
request_response::Behaviour::new(
|
request_response::Behaviour::new(
|
||||||
[(
|
[(StreamProtocol::new(SYNOR_PROTOCOL), ProtocolSupport::Full)],
|
||||||
StreamProtocol::new(SYNOR_PROTOCOL),
|
|
||||||
ProtocolSupport::Full,
|
|
||||||
)],
|
|
||||||
request_response::Config::default(),
|
request_response::Config::default(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -371,7 +371,9 @@ impl PerPeerLimiter {
|
||||||
pub fn cleanup(&self, connected_peers: &[PeerId]) {
|
pub fn cleanup(&self, connected_peers: &[PeerId]) {
|
||||||
let connected: std::collections::HashSet<_> = connected_peers.iter().collect();
|
let connected: std::collections::HashSet<_> = connected_peers.iter().collect();
|
||||||
self.peers.write().retain(|id, _| connected.contains(id));
|
self.peers.write().retain(|id, _| connected.contains(id));
|
||||||
self.cooldowns.write().retain(|id, _| connected.contains(id));
|
self.cooldowns
|
||||||
|
.write()
|
||||||
|
.retain(|id, _| connected.contains(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of tracked peers.
|
/// Returns the number of tracked peers.
|
||||||
|
|
@ -522,7 +524,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
let limiter = PerPeerLimiter::with_violation_config(
|
let limiter = PerPeerLimiter::with_violation_config(
|
||||||
config,
|
config,
|
||||||
3, // max_violations
|
3, // max_violations
|
||||||
Duration::from_millis(100), // short cooldown for test
|
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.
|
/// Returns the number of requests made by a peer in the current window.
|
||||||
pub fn request_count(&self, peer_id: &PeerId) -> u32 {
|
pub fn request_count(&self, peer_id: &PeerId) -> u32 {
|
||||||
let peers = self.peers.read();
|
let peers = self.peers.read();
|
||||||
peers.get(peer_id).map(|s| s.requests.len() as u32).unwrap_or(0)
|
peers
|
||||||
|
.get(peer_id)
|
||||||
|
.map(|s| s.requests.len() as u32)
|
||||||
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of violations for a peer.
|
/// Returns the number of violations for a peer.
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,10 @@ impl PeerReputation {
|
||||||
pub fn record_success(&mut self) {
|
pub fn record_success(&mut self) {
|
||||||
self.successes += 1;
|
self.successes += 1;
|
||||||
self.last_seen = Instant::now();
|
self.last_seen = Instant::now();
|
||||||
self.score = self.score.saturating_add(Self::SUCCESS_BONUS).min(Self::MAX_SCORE);
|
self.score = self
|
||||||
|
.score
|
||||||
|
.saturating_add(Self::SUCCESS_BONUS)
|
||||||
|
.min(Self::MAX_SCORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records a violation and returns true if the peer should be banned.
|
/// Records a violation and returns true if the peer should be banned.
|
||||||
|
|
@ -602,7 +605,10 @@ mod tests {
|
||||||
|
|
||||||
rep.record_success();
|
rep.record_success();
|
||||||
assert_eq!(rep.successes, 1);
|
assert_eq!(rep.successes, 1);
|
||||||
assert_eq!(rep.score, PeerReputation::INITIAL_SCORE + PeerReputation::SUCCESS_BONUS);
|
assert_eq!(
|
||||||
|
rep.score,
|
||||||
|
PeerReputation::INITIAL_SCORE + PeerReputation::SUCCESS_BONUS
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -690,7 +696,10 @@ mod tests {
|
||||||
manager.record_success(&peer_id);
|
manager.record_success(&peer_id);
|
||||||
|
|
||||||
let score = manager.get_score(&peer_id).unwrap();
|
let score = manager.get_score(&peer_id).unwrap();
|
||||||
assert_eq!(score, PeerReputation::INITIAL_SCORE + PeerReputation::SUCCESS_BONUS);
|
assert_eq!(
|
||||||
|
score,
|
||||||
|
PeerReputation::INITIAL_SCORE + PeerReputation::SUCCESS_BONUS
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use crate::behaviour::SynorBehaviour;
|
use crate::behaviour::SynorBehaviour;
|
||||||
use crate::config::NetworkConfig;
|
use crate::config::NetworkConfig;
|
||||||
use crate::message::{BlockAnnouncement, TransactionAnnouncement};
|
use crate::message::{BlockAnnouncement, TransactionAnnouncement};
|
||||||
use crate::peer::{Direction, PeerInfo, PeerManager, reputation};
|
use crate::peer::{reputation, Direction, PeerInfo, PeerManager};
|
||||||
use crate::protocol::{SynorRequest, SynorResponse};
|
use crate::protocol::{SynorRequest, SynorResponse};
|
||||||
use crate::rate_limit::{PerPeerLimiter, RateLimitConfig as TokenBucketConfig};
|
use crate::rate_limit::{PerPeerLimiter, RateLimitConfig as TokenBucketConfig};
|
||||||
use crate::ratelimit::{RateLimitResult, RateLimiters};
|
use crate::ratelimit::{RateLimitResult, RateLimiters};
|
||||||
|
|
@ -12,14 +12,8 @@ use crate::sync::{SyncManager, SyncStatus};
|
||||||
use crate::topics;
|
use crate::topics;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use libp2p::{
|
use libp2p::{
|
||||||
gossipsub,
|
gossipsub, identify, kad, mdns, ping, request_response, swarm::SwarmEvent, Multiaddr, PeerId,
|
||||||
identify,
|
Swarm, SwarmBuilder,
|
||||||
kad,
|
|
||||||
mdns,
|
|
||||||
ping,
|
|
||||||
request_response,
|
|
||||||
swarm::SwarmEvent,
|
|
||||||
Multiaddr, PeerId, Swarm, SwarmBuilder,
|
|
||||||
};
|
};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -125,7 +119,10 @@ impl NetworkHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Broadcasts a block to the network.
|
/// Broadcasts a block to the network.
|
||||||
pub async fn broadcast_block(&self, announcement: BlockAnnouncement) -> Result<(), NetworkError> {
|
pub async fn broadcast_block(
|
||||||
|
&self,
|
||||||
|
announcement: BlockAnnouncement,
|
||||||
|
) -> Result<(), NetworkError> {
|
||||||
self.command_tx
|
self.command_tx
|
||||||
.send(NetworkCommand::BroadcastBlock(announcement))
|
.send(NetworkCommand::BroadcastBlock(announcement))
|
||||||
.await
|
.await
|
||||||
|
|
@ -133,7 +130,10 @@ impl NetworkHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Broadcasts a transaction to the network.
|
/// Broadcasts a transaction to the network.
|
||||||
pub async fn broadcast_transaction(&self, announcement: TransactionAnnouncement) -> Result<(), NetworkError> {
|
pub async fn broadcast_transaction(
|
||||||
|
&self,
|
||||||
|
announcement: TransactionAnnouncement,
|
||||||
|
) -> Result<(), NetworkError> {
|
||||||
self.command_tx
|
self.command_tx
|
||||||
.send(NetworkCommand::BroadcastTransaction(announcement))
|
.send(NetworkCommand::BroadcastTransaction(announcement))
|
||||||
.await
|
.await
|
||||||
|
|
@ -278,10 +278,8 @@ pub struct NetworkService {
|
||||||
/// Event sender.
|
/// Event sender.
|
||||||
event_tx: broadcast::Sender<NetworkEvent>,
|
event_tx: broadcast::Sender<NetworkEvent>,
|
||||||
/// Pending requests.
|
/// Pending requests.
|
||||||
pending_requests: RwLock<std::collections::HashMap<
|
pending_requests:
|
||||||
request_response::OutboundRequestId,
|
RwLock<std::collections::HashMap<request_response::OutboundRequestId, PendingRequest>>,
|
||||||
PendingRequest,
|
|
||||||
>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PendingRequest {
|
struct PendingRequest {
|
||||||
|
|
@ -290,7 +288,9 @@ struct PendingRequest {
|
||||||
|
|
||||||
impl NetworkService {
|
impl NetworkService {
|
||||||
/// Creates a new network service.
|
/// Creates a new network service.
|
||||||
pub async fn new(config: NetworkConfig) -> Result<(Self, NetworkHandle), Box<dyn std::error::Error>> {
|
pub async fn new(
|
||||||
|
config: NetworkConfig,
|
||||||
|
) -> Result<(Self, NetworkHandle), Box<dyn std::error::Error>> {
|
||||||
// Generate keypair
|
// Generate keypair
|
||||||
let local_keypair = libp2p::identity::Keypair::generate_ed25519();
|
let local_keypair = libp2p::identity::Keypair::generate_ed25519();
|
||||||
let local_peer_id = PeerId::from(local_keypair.public());
|
let local_peer_id = PeerId::from(local_keypair.public());
|
||||||
|
|
@ -310,9 +310,7 @@ impl NetworkService {
|
||||||
.with_behaviour(|key| {
|
.with_behaviour(|key| {
|
||||||
SynorBehaviour::new(key, &config).expect("Behaviour creation failed")
|
SynorBehaviour::new(key, &config).expect("Behaviour creation failed")
|
||||||
})?
|
})?
|
||||||
.with_swarm_config(|cfg| {
|
.with_swarm_config(|cfg| cfg.with_idle_connection_timeout(config.idle_timeout))
|
||||||
cfg.with_idle_connection_timeout(config.idle_timeout)
|
|
||||||
})
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Create peer, sync, and reputation managers
|
// Create peer, sync, and reputation managers
|
||||||
|
|
@ -397,13 +395,18 @@ impl NetworkService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a swarm event.
|
/// Handles a swarm event.
|
||||||
async fn handle_swarm_event(&mut self, event: SwarmEvent<crate::behaviour::SynorBehaviourEvent>) {
|
async fn handle_swarm_event(
|
||||||
|
&mut self,
|
||||||
|
event: SwarmEvent<crate::behaviour::SynorBehaviourEvent>,
|
||||||
|
) {
|
||||||
match event {
|
match event {
|
||||||
SwarmEvent::NewListenAddr { address, .. } => {
|
SwarmEvent::NewListenAddr { address, .. } => {
|
||||||
info!("Listening on {}", address);
|
info!("Listening on {}", address);
|
||||||
}
|
}
|
||||||
|
|
||||||
SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } => {
|
SwarmEvent::ConnectionEstablished {
|
||||||
|
peer_id, endpoint, ..
|
||||||
|
} => {
|
||||||
// Check if peer is banned via reputation system
|
// Check if peer is banned via reputation system
|
||||||
if self.reputation_manager.is_banned(&peer_id) {
|
if self.reputation_manager.is_banned(&peer_id) {
|
||||||
warn!("Rejecting connection from banned peer: {}", peer_id);
|
warn!("Rejecting connection from banned peer: {}", peer_id);
|
||||||
|
|
@ -458,22 +461,26 @@ impl NetworkService {
|
||||||
message_id: _,
|
message_id: _,
|
||||||
message,
|
message,
|
||||||
}) => {
|
}) => {
|
||||||
self.handle_gossip_message(propagation_source, message).await;
|
self.handle_gossip_message(propagation_source, message)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
SynorBehaviourEvent::RequestResponse(request_response::Event::Message {
|
SynorBehaviourEvent::RequestResponse(request_response::Event::Message {
|
||||||
peer,
|
peer,
|
||||||
message,
|
message,
|
||||||
}) => {
|
}) => match message {
|
||||||
match message {
|
request_response::Message::Request {
|
||||||
request_response::Message::Request { request, channel, .. } => {
|
request, channel, ..
|
||||||
self.handle_request(peer, request, channel).await;
|
} => {
|
||||||
}
|
self.handle_request(peer, request, channel).await;
|
||||||
request_response::Message::Response { request_id, response } => {
|
|
||||||
self.handle_response(peer, request_id, response).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
request_response::Message::Response {
|
||||||
|
request_id,
|
||||||
|
response,
|
||||||
|
} => {
|
||||||
|
self.handle_response(peer, request_id, response).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
SynorBehaviourEvent::RequestResponse(request_response::Event::OutboundFailure {
|
SynorBehaviourEvent::RequestResponse(request_response::Event::OutboundFailure {
|
||||||
peer,
|
peer,
|
||||||
|
|
@ -484,7 +491,9 @@ impl NetworkService {
|
||||||
self.sync_manager.on_request_failed(request_id);
|
self.sync_manager.on_request_failed(request_id);
|
||||||
self.peer_manager.update_peer(&peer, |p| p.record_failure());
|
self.peer_manager.update_peer(&peer, |p| p.record_failure());
|
||||||
// Record violation in reputation system (no response from peer)
|
// Record violation in reputation system (no response from peer)
|
||||||
let auto_banned = self.reputation_manager.record_violation(&peer, ViolationType::NoResponse);
|
let auto_banned = self
|
||||||
|
.reputation_manager
|
||||||
|
.record_violation(&peer, ViolationType::NoResponse);
|
||||||
if auto_banned {
|
if auto_banned {
|
||||||
warn!("Auto-banned peer {} for repeated failures", peer);
|
warn!("Auto-banned peer {} for repeated failures", peer);
|
||||||
let _ = self.swarm.disconnect_peer_id(peer);
|
let _ = self.swarm.disconnect_peer_id(peer);
|
||||||
|
|
@ -508,25 +517,25 @@ impl NetworkService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SynorBehaviourEvent::Ping(ping::Event { peer, result, .. }) => {
|
SynorBehaviourEvent::Ping(ping::Event { peer, result, .. }) => match result {
|
||||||
match result {
|
Ok(rtt) => {
|
||||||
Ok(rtt) => {
|
trace!("Ping to {} succeeded: {:?}", peer, rtt);
|
||||||
trace!("Ping to {} succeeded: {:?}", peer, rtt);
|
self.peer_manager.update_peer(&peer, |p| {
|
||||||
self.peer_manager.update_peer(&peer, |p| {
|
p.latency = Some(rtt);
|
||||||
p.latency = Some(rtt);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!("Ping to {} failed: {}", peer, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
Err(e) => {
|
||||||
|
debug!("Ping to {} failed: {}", peer, e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
SynorBehaviourEvent::Mdns(mdns::Event::Discovered(peers)) => {
|
SynorBehaviourEvent::Mdns(mdns::Event::Discovered(peers)) => {
|
||||||
for (peer_id, addr) in peers {
|
for (peer_id, addr) in peers {
|
||||||
debug!("mDNS discovered peer: {} at {}", peer_id, addr);
|
debug!("mDNS discovered peer: {} at {}", peer_id, addr);
|
||||||
if self.peer_manager.can_connect_outbound() {
|
if self.peer_manager.can_connect_outbound() {
|
||||||
self.swarm.behaviour_mut().add_address(&peer_id, addr.clone());
|
self.swarm
|
||||||
|
.behaviour_mut()
|
||||||
|
.add_address(&peer_id, addr.clone());
|
||||||
if let Err(e) = self.swarm.dial(addr) {
|
if let Err(e) = self.swarm.dial(addr) {
|
||||||
debug!("Failed to dial discovered peer: {}", e);
|
debug!("Failed to dial discovered peer: {}", e);
|
||||||
}
|
}
|
||||||
|
|
@ -540,19 +549,19 @@ impl NetworkService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SynorBehaviourEvent::Kademlia(kad::Event::OutboundQueryProgressed { result, .. }) => {
|
SynorBehaviourEvent::Kademlia(kad::Event::OutboundQueryProgressed {
|
||||||
match result {
|
result, ..
|
||||||
kad::QueryResult::GetClosestPeers(Ok(ok)) => {
|
}) => match result {
|
||||||
for peer in ok.peers {
|
kad::QueryResult::GetClosestPeers(Ok(ok)) => {
|
||||||
debug!("Kademlia found peer: {:?}", peer);
|
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 {
|
match topic {
|
||||||
t if t == topics::BLOCKS => {
|
t if t == topics::BLOCKS => {
|
||||||
if let Ok(announcement) = borsh::from_slice::<BlockAnnouncement>(&message.data) {
|
if let Ok(announcement) = borsh::from_slice::<BlockAnnouncement>(&message.data) {
|
||||||
debug!("Received block announcement from {}: {}", source, announcement.hash);
|
debug!(
|
||||||
|
"Received block announcement from {}: {}",
|
||||||
|
source, announcement.hash
|
||||||
|
);
|
||||||
let _ = self.event_tx.send(NetworkEvent::NewBlock(announcement));
|
let _ = self.event_tx.send(NetworkEvent::NewBlock(announcement));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t if t == topics::TRANSACTIONS => {
|
t if t == topics::TRANSACTIONS => {
|
||||||
if let Ok(announcement) = borsh::from_slice::<TransactionAnnouncement>(&message.data) {
|
if let Ok(announcement) =
|
||||||
debug!("Received tx announcement from {}: {}", source, announcement.txid);
|
borsh::from_slice::<TransactionAnnouncement>(&message.data)
|
||||||
let _ = self.event_tx.send(NetworkEvent::NewTransaction(announcement));
|
{
|
||||||
|
debug!(
|
||||||
|
"Received tx announcement from {}: {}",
|
||||||
|
source, announcement.txid
|
||||||
|
);
|
||||||
|
let _ = self
|
||||||
|
.event_tx
|
||||||
|
.send(NetworkEvent::NewTransaction(announcement));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -614,7 +633,9 @@ impl NetworkService {
|
||||||
p.add_reputation(reputation::INVALID_DATA);
|
p.add_reputation(reputation::INVALID_DATA);
|
||||||
});
|
});
|
||||||
// Record spam violation in reputation system (may auto-ban)
|
// Record spam violation in reputation system (may auto-ban)
|
||||||
let auto_banned = self.reputation_manager.record_violation(&peer, ViolationType::Spam);
|
let auto_banned = self
|
||||||
|
.reputation_manager
|
||||||
|
.record_violation(&peer, ViolationType::Spam);
|
||||||
if auto_banned {
|
if auto_banned {
|
||||||
warn!("Auto-banned peer {} for repeated spam violations", peer);
|
warn!("Auto-banned peer {} for repeated spam violations", peer);
|
||||||
let _ = self.swarm.disconnect_peer_id(peer);
|
let _ = self.swarm.disconnect_peer_id(peer);
|
||||||
|
|
@ -640,14 +661,15 @@ impl NetworkService {
|
||||||
p.add_reputation(reputation::TIMEOUT);
|
p.add_reputation(reputation::TIMEOUT);
|
||||||
});
|
});
|
||||||
// Record spam violation in reputation system
|
// Record spam violation in reputation system
|
||||||
let auto_banned = self.reputation_manager.record_violation(&peer, ViolationType::Spam);
|
let auto_banned = self
|
||||||
|
.reputation_manager
|
||||||
|
.record_violation(&peer, ViolationType::Spam);
|
||||||
if auto_banned {
|
if auto_banned {
|
||||||
warn!("Auto-banned peer {} for spam violations", peer);
|
warn!("Auto-banned peer {} for spam violations", peer);
|
||||||
let _ = self.swarm.disconnect_peer_id(peer);
|
let _ = self.swarm.disconnect_peer_id(peer);
|
||||||
}
|
}
|
||||||
let response = SynorResponse::Error(
|
let response =
|
||||||
"Rate limited. Too many requests per second.".to_string()
|
SynorResponse::Error("Rate limited. Too many requests per second.".to_string());
|
||||||
);
|
|
||||||
if let Err(e) = self.swarm.behaviour_mut().send_response(channel, response) {
|
if let Err(e) = self.swarm.behaviour_mut().send_response(channel, response) {
|
||||||
warn!("Failed to send rate limit response to {}: {:?}", peer, e);
|
warn!("Failed to send rate limit response to {}: {:?}", peer, e);
|
||||||
}
|
}
|
||||||
|
|
@ -688,7 +710,8 @@ impl NetworkService {
|
||||||
p.add_reputation(reputation::TIMEOUT);
|
p.add_reputation(reputation::TIMEOUT);
|
||||||
});
|
});
|
||||||
// Record slow response violation
|
// Record slow response violation
|
||||||
self.reputation_manager.record_violation(&peer, ViolationType::SlowResponse);
|
self.reputation_manager
|
||||||
|
.record_violation(&peer, ViolationType::SlowResponse);
|
||||||
let response = SynorResponse::Error(format!(
|
let response = SynorResponse::Error(format!(
|
||||||
"Rate limited. Retry after {} seconds.",
|
"Rate limited. Retry after {} seconds.",
|
||||||
retry_after.as_secs()
|
retry_after.as_secs()
|
||||||
|
|
@ -710,9 +733,14 @@ impl NetworkService {
|
||||||
p.add_reputation(reputation::INVALID_DATA);
|
p.add_reputation(reputation::INVALID_DATA);
|
||||||
});
|
});
|
||||||
// Record spam violation (may auto-ban)
|
// Record spam violation (may auto-ban)
|
||||||
let auto_banned = self.reputation_manager.record_violation(&peer, ViolationType::Spam);
|
let auto_banned = self
|
||||||
|
.reputation_manager
|
||||||
|
.record_violation(&peer, ViolationType::Spam);
|
||||||
if auto_banned {
|
if auto_banned {
|
||||||
warn!("Auto-banned peer {} for repeated rate limit violations", peer);
|
warn!(
|
||||||
|
"Auto-banned peer {} for repeated rate limit violations",
|
||||||
|
peer
|
||||||
|
);
|
||||||
let _ = self.swarm.disconnect_peer_id(peer);
|
let _ = self.swarm.disconnect_peer_id(peer);
|
||||||
}
|
}
|
||||||
let response = SynorResponse::Error(format!(
|
let response = SynorResponse::Error(format!(
|
||||||
|
|
@ -794,7 +822,8 @@ impl NetworkService {
|
||||||
let _ = self.event_tx.send(NetworkEvent::HeadersReceived(headers));
|
let _ = self.event_tx.send(NetworkEvent::HeadersReceived(headers));
|
||||||
}
|
}
|
||||||
SynorResponse::Blocks(blocks) => {
|
SynorResponse::Blocks(blocks) => {
|
||||||
self.sync_manager.on_blocks_response(request_id, blocks.clone());
|
self.sync_manager
|
||||||
|
.on_blocks_response(request_id, blocks.clone());
|
||||||
let _ = self.event_tx.send(NetworkEvent::BlocksReceived(blocks));
|
let _ = self.event_tx.send(NetworkEvent::BlocksReceived(blocks));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -848,17 +877,30 @@ impl NetworkService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkCommand::RequestBlocks { peer, block_ids, response } => {
|
NetworkCommand::RequestBlocks {
|
||||||
|
peer,
|
||||||
|
block_ids,
|
||||||
|
response,
|
||||||
|
} => {
|
||||||
let request = SynorRequest::GetBlocks(block_ids);
|
let request = SynorRequest::GetBlocks(block_ids);
|
||||||
let _request_id = self.swarm.behaviour_mut().send_request(&peer, request);
|
let _request_id = self.swarm.behaviour_mut().send_request(&peer, request);
|
||||||
// Would need to track the response sender
|
// Would need to track the response sender
|
||||||
let _ = response.send(Err(NetworkError::RequestFailed("Not implemented".to_string())));
|
let _ = response.send(Err(NetworkError::RequestFailed(
|
||||||
|
"Not implemented".to_string(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkCommand::RequestHeaders { peer, start, max_count, response } => {
|
NetworkCommand::RequestHeaders {
|
||||||
|
peer,
|
||||||
|
start,
|
||||||
|
max_count,
|
||||||
|
response,
|
||||||
|
} => {
|
||||||
let request = SynorRequest::GetHeaders { start, max_count };
|
let request = SynorRequest::GetHeaders { start, max_count };
|
||||||
let _request_id = self.swarm.behaviour_mut().send_request(&peer, request);
|
let _request_id = self.swarm.behaviour_mut().send_request(&peer, request);
|
||||||
let _ = response.send(Err(NetworkError::RequestFailed("Not implemented".to_string())));
|
let _ = response.send(Err(NetworkError::RequestFailed(
|
||||||
|
"Not implemented".to_string(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkCommand::GetPeerCount(response) => {
|
NetworkCommand::GetPeerCount(response) => {
|
||||||
|
|
|
||||||
|
|
@ -188,11 +188,7 @@ pub struct SyncManager {
|
||||||
|
|
||||||
impl SyncManager {
|
impl SyncManager {
|
||||||
/// Creates a new sync manager.
|
/// Creates a new sync manager.
|
||||||
pub fn new(
|
pub fn new(config: SyncConfig, peer_manager: Arc<PeerManager>, genesis_hash: Hash256) -> Self {
|
||||||
config: SyncConfig,
|
|
||||||
peer_manager: Arc<PeerManager>,
|
|
||||||
genesis_hash: Hash256,
|
|
||||||
) -> Self {
|
|
||||||
SyncManager {
|
SyncManager {
|
||||||
config,
|
config,
|
||||||
status: RwLock::new(SyncStatus::default()),
|
status: RwLock::new(SyncStatus::default()),
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,8 @@ pub trait UtxoApi {
|
||||||
|
|
||||||
/// Gets balances by addresses.
|
/// Gets balances by addresses.
|
||||||
#[method(name = "getBalancesByAddresses")]
|
#[method(name = "getBalancesByAddresses")]
|
||||||
async fn get_balances_by_addresses(&self, addresses: Vec<String>) -> RpcResult<Vec<RpcBalance>>;
|
async fn get_balances_by_addresses(&self, addresses: Vec<String>)
|
||||||
|
-> RpcResult<Vec<RpcBalance>>;
|
||||||
|
|
||||||
/// Gets coin supply.
|
/// Gets coin supply.
|
||||||
#[method(name = "getCoinSupply")]
|
#[method(name = "getCoinSupply")]
|
||||||
|
|
@ -194,7 +195,10 @@ pub trait MiningApi {
|
||||||
|
|
||||||
/// Submits a mined block.
|
/// Submits a mined block.
|
||||||
#[method(name = "submitBlock")]
|
#[method(name = "submitBlock")]
|
||||||
async fn submit_block(&self, request: RpcSubmitBlockRequest) -> RpcResult<RpcSubmitBlockResponse>;
|
async fn submit_block(
|
||||||
|
&self,
|
||||||
|
request: RpcSubmitBlockRequest,
|
||||||
|
) -> RpcResult<RpcSubmitBlockResponse>;
|
||||||
|
|
||||||
/// Gets current network hashrate.
|
/// Gets current network hashrate.
|
||||||
#[method(name = "getNetworkHashrate")]
|
#[method(name = "getNetworkHashrate")]
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,13 @@ pub mod server;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub use api::{
|
pub use api::{
|
||||||
BlockApiServer, MiningApiServer, NetworkApiServer, TransactionApiServer, UtxoApiServer,
|
BlockApiServer, ContractApiServer, MiningApiServer, NetworkApiServer, SubscriptionApiServer,
|
||||||
ContractApiServer, SubscriptionApiServer,
|
TransactionApiServer, UtxoApiServer,
|
||||||
};
|
};
|
||||||
pub use pool::{
|
pub use pool::{
|
||||||
ConnectionPool, ConnectionPoolExt, PoolConfig, PoolError, PoolStats, PoolStatsSnapshot,
|
global_pool, init_global_pool, ConnectionPool, ConnectionPoolExt, PoolConfig, PoolError,
|
||||||
PooledHttpClient, PooledHttpClientGuard, PooledWsClient, PooledWsClientGuard,
|
PoolStats, PoolStatsSnapshot, PooledHttpClient, PooledHttpClientGuard, PooledWsClient,
|
||||||
global_pool, init_global_pool,
|
PooledWsClientGuard,
|
||||||
};
|
};
|
||||||
pub use server::{RpcConfig, RpcServer};
|
pub use server::{RpcConfig, RpcServer};
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,9 @@ impl PoolStats {
|
||||||
PoolStatsSnapshot {
|
PoolStatsSnapshot {
|
||||||
connections_created: self.connections_created.load(Ordering::Relaxed),
|
connections_created: self.connections_created.load(Ordering::Relaxed),
|
||||||
connections_closed: self.connections_closed.load(Ordering::Relaxed),
|
connections_closed: self.connections_closed.load(Ordering::Relaxed),
|
||||||
connections_active: self.connections_created.load(Ordering::Relaxed)
|
connections_active: self
|
||||||
|
.connections_created
|
||||||
|
.load(Ordering::Relaxed)
|
||||||
.saturating_sub(self.connections_closed.load(Ordering::Relaxed)),
|
.saturating_sub(self.connections_closed.load(Ordering::Relaxed)),
|
||||||
requests_total: self.requests_total.load(Ordering::Relaxed),
|
requests_total: self.requests_total.load(Ordering::Relaxed),
|
||||||
requests_failed: self.requests_failed.load(Ordering::Relaxed),
|
requests_failed: self.requests_failed.load(Ordering::Relaxed),
|
||||||
|
|
@ -289,7 +291,9 @@ impl ConnectionPool {
|
||||||
|
|
||||||
// Try to get an existing client
|
// Try to get an existing client
|
||||||
if let Some(client) = entry_lock.http_clients.pop() {
|
if let Some(client) = entry_lock.http_clients.pop() {
|
||||||
self.stats.connections_reused.fetch_add(1, Ordering::Relaxed);
|
self.stats
|
||||||
|
.connections_reused
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
client.touch();
|
client.touch();
|
||||||
return Ok(client);
|
return Ok(client);
|
||||||
}
|
}
|
||||||
|
|
@ -332,7 +336,9 @@ impl ConnectionPool {
|
||||||
|
|
||||||
// Try to get an existing client
|
// Try to get an existing client
|
||||||
if let Some(client) = entry_lock.ws_clients.pop() {
|
if let Some(client) = entry_lock.ws_clients.pop() {
|
||||||
self.stats.connections_reused.fetch_add(1, Ordering::Relaxed);
|
self.stats
|
||||||
|
.connections_reused
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
client.touch();
|
client.touch();
|
||||||
return Ok(client);
|
return Ok(client);
|
||||||
}
|
}
|
||||||
|
|
@ -355,7 +361,9 @@ impl ConnectionPool {
|
||||||
{
|
{
|
||||||
entry_lock.http_clients.push(client);
|
entry_lock.http_clients.push(client);
|
||||||
} else {
|
} else {
|
||||||
self.stats.connections_closed.fetch_add(1, Ordering::Relaxed);
|
self.stats
|
||||||
|
.connections_closed
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -373,7 +381,9 @@ impl ConnectionPool {
|
||||||
{
|
{
|
||||||
entry_lock.ws_clients.push(client);
|
entry_lock.ws_clients.push(client);
|
||||||
} else {
|
} else {
|
||||||
self.stats.connections_closed.fetch_add(1, Ordering::Relaxed);
|
self.stats
|
||||||
|
.connections_closed
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -394,7 +404,9 @@ impl ConnectionPool {
|
||||||
entry_lock.consecutive_failures += 1;
|
entry_lock.consecutive_failures += 1;
|
||||||
if entry_lock.consecutive_failures >= self.config.max_retries {
|
if entry_lock.consecutive_failures >= self.config.max_retries {
|
||||||
entry_lock.healthy = false;
|
entry_lock.healthy = false;
|
||||||
self.stats.health_check_failures.fetch_add(1, Ordering::Relaxed);
|
self.stats
|
||||||
|
.health_check_failures
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -509,14 +521,17 @@ impl ConnectionPool {
|
||||||
consecutive_failures: 0,
|
consecutive_failures: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
endpoints.insert(endpoint.to_string(), Mutex::new(EndpointEntry {
|
endpoints.insert(
|
||||||
http_clients: Vec::new(),
|
endpoint.to_string(),
|
||||||
ws_clients: Vec::new(),
|
Mutex::new(EndpointEntry {
|
||||||
semaphore: Arc::new(Semaphore::new(self.config.max_connections_per_endpoint)),
|
http_clients: Vec::new(),
|
||||||
last_health_check: Instant::now(),
|
ws_clients: Vec::new(),
|
||||||
healthy: true,
|
semaphore: Arc::new(Semaphore::new(self.config.max_connections_per_endpoint)),
|
||||||
consecutive_failures: 0,
|
last_health_check: Instant::now(),
|
||||||
}));
|
healthy: true,
|
||||||
|
consecutive_failures: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
entry
|
entry
|
||||||
}
|
}
|
||||||
|
|
@ -533,7 +548,9 @@ impl ConnectionPool {
|
||||||
|
|
||||||
match self.try_create_http_client(endpoint).await {
|
match self.try_create_http_client(endpoint).await {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
self.stats.connections_created.fetch_add(1, Ordering::Relaxed);
|
self.stats
|
||||||
|
.connections_created
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
self.mark_healthy(endpoint);
|
self.mark_healthy(endpoint);
|
||||||
return Ok(client);
|
return Ok(client);
|
||||||
}
|
}
|
||||||
|
|
@ -545,9 +562,7 @@ impl ConnectionPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
|
self.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
|
||||||
Err(last_error.unwrap_or(PoolError::ConnectionFailed(
|
Err(last_error.unwrap_or(PoolError::ConnectionFailed("Unknown error".to_string())))
|
||||||
"Unknown error".to_string(),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new WebSocket client with retries.
|
/// Creates a new WebSocket client with retries.
|
||||||
|
|
@ -562,7 +577,9 @@ impl ConnectionPool {
|
||||||
|
|
||||||
match self.try_create_ws_client(endpoint).await {
|
match self.try_create_ws_client(endpoint).await {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
self.stats.connections_created.fetch_add(1, Ordering::Relaxed);
|
self.stats
|
||||||
|
.connections_created
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
self.mark_healthy(endpoint);
|
self.mark_healthy(endpoint);
|
||||||
return Ok(client);
|
return Ok(client);
|
||||||
}
|
}
|
||||||
|
|
@ -574,9 +591,7 @@ impl ConnectionPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
|
self.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
|
||||||
Err(last_error.unwrap_or(PoolError::ConnectionFailed(
|
Err(last_error.unwrap_or(PoolError::ConnectionFailed("Unknown error".to_string())))
|
||||||
"Unknown error".to_string(),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to create an HTTP client.
|
/// Attempts to create an HTTP client.
|
||||||
|
|
@ -599,10 +614,7 @@ impl ConnectionPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to create a WebSocket client.
|
/// Attempts to create a WebSocket client.
|
||||||
async fn try_create_ws_client(
|
async fn try_create_ws_client(&self, endpoint: &str) -> Result<Arc<PooledWsClient>, PoolError> {
|
||||||
&self,
|
|
||||||
endpoint: &str,
|
|
||||||
) -> Result<Arc<PooledWsClient>, PoolError> {
|
|
||||||
let client = WsClientBuilder::default()
|
let client = WsClientBuilder::default()
|
||||||
.request_timeout(self.config.request_timeout)
|
.request_timeout(self.config.request_timeout)
|
||||||
.connection_timeout(self.config.connect_timeout)
|
.connection_timeout(self.config.connect_timeout)
|
||||||
|
|
@ -674,7 +686,10 @@ impl<'a> PooledHttpClientGuard<'a> {
|
||||||
pub fn success(&self) {
|
pub fn success(&self) {
|
||||||
if let Some(ref client) = self.client {
|
if let Some(ref client) = self.client {
|
||||||
client.touch();
|
client.touch();
|
||||||
self.pool.stats.requests_total.fetch_add(1, Ordering::Relaxed);
|
self.pool
|
||||||
|
.stats
|
||||||
|
.requests_total
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -682,14 +697,20 @@ impl<'a> PooledHttpClientGuard<'a> {
|
||||||
pub fn failed(&self) {
|
pub fn failed(&self) {
|
||||||
if let Some(ref client) = self.client {
|
if let Some(ref client) = self.client {
|
||||||
self.pool.mark_unhealthy(client.endpoint());
|
self.pool.mark_unhealthy(client.endpoint());
|
||||||
self.pool.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
|
self.pool
|
||||||
|
.stats
|
||||||
|
.requests_failed
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Discards the connection (don't return to pool).
|
/// Discards the connection (don't return to pool).
|
||||||
pub fn discard(mut self) {
|
pub fn discard(mut self) {
|
||||||
if let Some(client) = self.client.take() {
|
if let Some(client) = self.client.take() {
|
||||||
self.pool.stats.connections_closed.fetch_add(1, Ordering::Relaxed);
|
self.pool
|
||||||
|
.stats
|
||||||
|
.connections_closed
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
drop(client);
|
drop(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -727,7 +748,10 @@ impl<'a> PooledWsClientGuard<'a> {
|
||||||
pub fn success(&self) {
|
pub fn success(&self) {
|
||||||
if let Some(ref client) = self.client {
|
if let Some(ref client) = self.client {
|
||||||
client.touch();
|
client.touch();
|
||||||
self.pool.stats.requests_total.fetch_add(1, Ordering::Relaxed);
|
self.pool
|
||||||
|
.stats
|
||||||
|
.requests_total
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -735,14 +759,20 @@ impl<'a> PooledWsClientGuard<'a> {
|
||||||
pub fn failed(&self) {
|
pub fn failed(&self) {
|
||||||
if let Some(ref client) = self.client {
|
if let Some(ref client) = self.client {
|
||||||
self.pool.mark_unhealthy(client.endpoint());
|
self.pool.mark_unhealthy(client.endpoint());
|
||||||
self.pool.stats.requests_failed.fetch_add(1, Ordering::Relaxed);
|
self.pool
|
||||||
|
.stats
|
||||||
|
.requests_failed
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Discards the connection (don't return to pool).
|
/// Discards the connection (don't return to pool).
|
||||||
pub fn discard(mut self) {
|
pub fn discard(mut self) {
|
||||||
if let Some(client) = self.client.take() {
|
if let Some(client) = self.client.take() {
|
||||||
self.pool.stats.connections_closed.fetch_add(1, Ordering::Relaxed);
|
self.pool
|
||||||
|
.stats
|
||||||
|
.connections_closed
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
drop(client);
|
drop(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -760,7 +790,10 @@ impl<'a> Drop for PooledWsClientGuard<'a> {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait ConnectionPoolExt {
|
pub trait ConnectionPoolExt {
|
||||||
/// Acquires an HTTP client and returns a guard.
|
/// Acquires an HTTP client and returns a guard.
|
||||||
async fn acquire_http_guard(&self, endpoint: &str) -> Result<PooledHttpClientGuard<'_>, PoolError>;
|
async fn acquire_http_guard(
|
||||||
|
&self,
|
||||||
|
endpoint: &str,
|
||||||
|
) -> Result<PooledHttpClientGuard<'_>, PoolError>;
|
||||||
|
|
||||||
/// Acquires a WebSocket client and returns a guard.
|
/// Acquires a WebSocket client and returns a guard.
|
||||||
async fn acquire_ws_guard(&self, endpoint: &str) -> Result<PooledWsClientGuard<'_>, PoolError>;
|
async fn acquire_ws_guard(&self, endpoint: &str) -> Result<PooledWsClientGuard<'_>, PoolError>;
|
||||||
|
|
@ -768,7 +801,10 @@ pub trait ConnectionPoolExt {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl ConnectionPoolExt for ConnectionPool {
|
impl ConnectionPoolExt for ConnectionPool {
|
||||||
async fn acquire_http_guard(&self, endpoint: &str) -> Result<PooledHttpClientGuard<'_>, PoolError> {
|
async fn acquire_http_guard(
|
||||||
|
&self,
|
||||||
|
endpoint: &str,
|
||||||
|
) -> Result<PooledHttpClientGuard<'_>, PoolError> {
|
||||||
let client = self.acquire_http(endpoint).await?;
|
let client = self.acquire_http(endpoint).await?;
|
||||||
Ok(PooledHttpClientGuard::new(self, client))
|
Ok(PooledHttpClientGuard::new(self, client))
|
||||||
}
|
}
|
||||||
|
|
@ -806,7 +842,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pool_config_high_throughput() {
|
fn test_pool_config_high_throughput() {
|
||||||
let config = PoolConfig::high_throughput();
|
let config = PoolConfig::high_throughput();
|
||||||
assert!(config.max_connections_per_endpoint > PoolConfig::default().max_connections_per_endpoint);
|
assert!(
|
||||||
|
config.max_connections_per_endpoint
|
||||||
|
> PoolConfig::default().max_connections_per_endpoint
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,9 @@ impl RpcServer {
|
||||||
.await
|
.await
|
||||||
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
||||||
|
|
||||||
let addr = server.local_addr().map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
let addr = server
|
||||||
|
.local_addr()
|
||||||
|
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
||||||
tracing::info!("HTTP RPC server listening on {}", addr);
|
tracing::info!("HTTP RPC server listening on {}", addr);
|
||||||
|
|
||||||
self.http_handle = Some(server.start(module));
|
self.http_handle = Some(server.start(module));
|
||||||
|
|
@ -150,7 +152,9 @@ impl RpcServer {
|
||||||
.await
|
.await
|
||||||
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
||||||
|
|
||||||
let addr = server.local_addr().map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
let addr = server
|
||||||
|
.local_addr()
|
||||||
|
.map_err(|e: std::io::Error| RpcServerError::StartFailed(e.to_string()))?;
|
||||||
tracing::info!("WebSocket RPC server listening on {}", addr);
|
tracing::info!("WebSocket RPC server listening on {}", addr);
|
||||||
|
|
||||||
self.ws_handle = Some(server.start(module));
|
self.ws_handle = Some(server.start(module));
|
||||||
|
|
@ -235,9 +239,9 @@ impl RpcModuleBuilder {
|
||||||
F: Fn(jsonrpsee::types::Params<'_>) -> R + Send + Sync + Clone + 'static,
|
F: Fn(jsonrpsee::types::Params<'_>) -> R + Send + Sync + Clone + 'static,
|
||||||
R: jsonrpsee::IntoResponse + Send + 'static,
|
R: jsonrpsee::IntoResponse + Send + 'static,
|
||||||
{
|
{
|
||||||
let _ = self.module.register_method(name, move |params, _| {
|
let _ = self
|
||||||
callback(params)
|
.module
|
||||||
});
|
.register_method(name, move |params, _| callback(params));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,10 @@ impl fmt::Display for Error {
|
||||||
Error::Failed(msg) => write!(f, "Failed: {}", msg),
|
Error::Failed(msg) => write!(f, "Failed: {}", msg),
|
||||||
Error::InvalidMethod => write!(f, "Invalid method"),
|
Error::InvalidMethod => write!(f, "Invalid method"),
|
||||||
Error::InvalidArgs(msg) => write!(f, "Invalid arguments: {}", msg),
|
Error::InvalidArgs(msg) => write!(f, "Invalid arguments: {}", msg),
|
||||||
Error::InsufficientBalance { required, available } => {
|
Error::InsufficientBalance {
|
||||||
|
required,
|
||||||
|
available,
|
||||||
|
} => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Insufficient balance: required {}, available {}",
|
"Insufficient balance: required {}, available {}",
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,13 @@ pub fn storage_read(key: &[u8; 32], out: &mut [u8]) -> i32 {
|
||||||
/// Writes to storage.
|
/// Writes to storage.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn storage_write(key: &[u8; 32], value: &[u8]) -> i32 {
|
pub fn storage_write(key: &[u8; 32], value: &[u8]) -> i32 {
|
||||||
unsafe { synor_storage_write(key.as_ptr() as i32, value.as_ptr() as i32, value.len() as i32) }
|
unsafe {
|
||||||
|
synor_storage_write(
|
||||||
|
key.as_ptr() as i32,
|
||||||
|
value.as_ptr() as i32,
|
||||||
|
value.len() as i32,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes from storage. Returns 1 if key existed, 0 otherwise.
|
/// Deletes from storage. Returns 1 if key existed, 0 otherwise.
|
||||||
|
|
@ -152,7 +158,13 @@ pub fn get_chain_id() -> u64 {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn sha3(data: &[u8]) -> [u8; 32] {
|
pub fn sha3(data: &[u8]) -> [u8; 32] {
|
||||||
let mut out = [0u8; 32];
|
let mut out = [0u8; 32];
|
||||||
unsafe { synor_sha3(data.as_ptr() as i32, data.len() as i32, out.as_mut_ptr() as i32) };
|
unsafe {
|
||||||
|
synor_sha3(
|
||||||
|
data.as_ptr() as i32,
|
||||||
|
data.len() as i32,
|
||||||
|
out.as_mut_ptr() as i32,
|
||||||
|
)
|
||||||
|
};
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +172,13 @@ pub fn sha3(data: &[u8]) -> [u8; 32] {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn blake3(data: &[u8]) -> [u8; 32] {
|
pub fn blake3(data: &[u8]) -> [u8; 32] {
|
||||||
let mut out = [0u8; 32];
|
let mut out = [0u8; 32];
|
||||||
unsafe { synor_blake3(data.as_ptr() as i32, data.len() as i32, out.as_mut_ptr() as i32) };
|
unsafe {
|
||||||
|
synor_blake3(
|
||||||
|
data.as_ptr() as i32,
|
||||||
|
data.len() as i32,
|
||||||
|
out.as_mut_ptr() as i32,
|
||||||
|
)
|
||||||
|
};
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,9 +186,7 @@ macro_rules! entry_point {
|
||||||
$crate::host::return_data(&data);
|
$crate::host::return_data(&data);
|
||||||
data.len() as i32
|
data.len() as i32
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => $crate::host::revert(&e.to_string()),
|
||||||
$crate::host::revert(&e.to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,16 @@
|
||||||
//!
|
//!
|
||||||
//! Run with: cargo bench -p synor-storage --bench storage_bench
|
//! Run with: cargo bench -p synor-storage --bench storage_bench
|
||||||
|
|
||||||
use criterion::{
|
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||||
black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use synor_storage::{
|
use synor_storage::{
|
||||||
cache::{CacheConfig, LruCache, StorageCache},
|
cache::{CacheConfig, LruCache, StorageCache},
|
||||||
cf, Database, DatabaseConfig,
|
cf,
|
||||||
stores::{
|
stores::{
|
||||||
ChainState, GhostdagStore, HeaderStore, MetadataStore, RelationsStore,
|
ChainState, GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredGhostdagData,
|
||||||
StoredGhostdagData, StoredRelations, StoredUtxo, UtxoStore,
|
StoredRelations, StoredUtxo, UtxoStore,
|
||||||
},
|
},
|
||||||
|
Database, DatabaseConfig,
|
||||||
};
|
};
|
||||||
use synor_types::{BlockHeader, BlockId, BlueScore, Hash256, Timestamp, TransactionId};
|
use synor_types::{BlockHeader, BlockId, BlueScore, Hash256, Timestamp, TransactionId};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
@ -83,16 +82,22 @@ fn make_ghostdag_data(n: u64) -> StoredGhostdagData {
|
||||||
StoredGhostdagData {
|
StoredGhostdagData {
|
||||||
blue_score: n * 10,
|
blue_score: n * 10,
|
||||||
selected_parent: make_block_id(n.saturating_sub(1)),
|
selected_parent: make_block_id(n.saturating_sub(1)),
|
||||||
merge_set_blues: (0..5).map(|i| make_block_id(n.saturating_sub(i + 1))).collect(),
|
merge_set_blues: (0..5)
|
||||||
|
.map(|i| make_block_id(n.saturating_sub(i + 1)))
|
||||||
|
.collect(),
|
||||||
merge_set_reds: (0..2).map(|i| make_block_id(n + i + 100)).collect(),
|
merge_set_reds: (0..2).map(|i| make_block_id(n + i + 100)).collect(),
|
||||||
blues_anticone_sizes: (0..3).map(|i| (make_block_id(n.saturating_sub(i + 1)), i + 1)).collect(),
|
blues_anticone_sizes: (0..3)
|
||||||
|
.map(|i| (make_block_id(n.saturating_sub(i + 1)), i + 1))
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a test relations entry.
|
/// Creates a test relations entry.
|
||||||
fn make_relations(n: u64) -> StoredRelations {
|
fn make_relations(n: u64) -> StoredRelations {
|
||||||
StoredRelations {
|
StoredRelations {
|
||||||
parents: (1..=3).map(|i| make_block_id(n.saturating_sub(i))).collect(),
|
parents: (1..=3)
|
||||||
|
.map(|i| make_block_id(n.saturating_sub(i)))
|
||||||
|
.collect(),
|
||||||
children: (1..=2).map(|i| make_block_id(n + i)).collect(),
|
children: (1..=2).map(|i| make_block_id(n + i)).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -157,25 +162,21 @@ fn header_write_batch(c: &mut Criterion) {
|
||||||
let headers: Vec<BlockHeader> = (0..count).map(|i| make_header(i as u64)).collect();
|
let headers: Vec<BlockHeader> = (0..count).map(|i| make_header(i as u64)).collect();
|
||||||
|
|
||||||
group.throughput(Throughput::Elements(count as u64));
|
group.throughput(Throughput::Elements(count as u64));
|
||||||
group.bench_with_input(
|
group.bench_with_input(BenchmarkId::from_parameter(count), &headers, |b, hdrs| {
|
||||||
BenchmarkId::from_parameter(count),
|
b.iter_batched(
|
||||||
&headers,
|
|| {
|
||||||
|b, hdrs| {
|
let (dir, db) = setup_db();
|
||||||
b.iter_batched(
|
let store = HeaderStore::new(db);
|
||||||
|| {
|
(dir, store)
|
||||||
let (dir, db) = setup_db();
|
},
|
||||||
let store = HeaderStore::new(db);
|
|(_dir, store)| {
|
||||||
(dir, store)
|
for header in hdrs {
|
||||||
},
|
store.put(header).unwrap();
|
||||||
|(_dir, store)| {
|
}
|
||||||
for header in hdrs {
|
},
|
||||||
store.put(header).unwrap();
|
criterion::BatchSize::SmallInput,
|
||||||
}
|
)
|
||||||
},
|
});
|
||||||
criterion::BatchSize::SmallInput,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
|
|
@ -189,9 +190,7 @@ fn header_read_single(c: &mut Criterion) {
|
||||||
let store = populate_headers(&db, 1000);
|
let store = populate_headers(&db, 1000);
|
||||||
let target_hash = make_header(500).block_id();
|
let target_hash = make_header(500).block_id();
|
||||||
|
|
||||||
group.bench_function("single", |b| {
|
group.bench_function("single", |b| b.iter(|| black_box(store.get(&target_hash))));
|
||||||
b.iter(|| black_box(store.get(&target_hash)))
|
|
||||||
});
|
|
||||||
|
|
||||||
drop(dir);
|
drop(dir);
|
||||||
group.finish();
|
group.finish();
|
||||||
|
|
@ -208,9 +207,7 @@ fn header_read_varying_db_sizes(c: &mut Criterion) {
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::from_parameter(db_size),
|
BenchmarkId::from_parameter(db_size),
|
||||||
&target_hash,
|
&target_hash,
|
||||||
|b, hash| {
|
|b, hash| b.iter(|| black_box(store.get(hash))),
|
||||||
b.iter(|| black_box(store.get(hash)))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
drop(dir);
|
drop(dir);
|
||||||
|
|
@ -231,13 +228,9 @@ fn header_multi_get(c: &mut Criterion) {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
group.throughput(Throughput::Elements(count as u64));
|
group.throughput(Throughput::Elements(count as u64));
|
||||||
group.bench_with_input(
|
group.bench_with_input(BenchmarkId::from_parameter(count), &hashes, |b, h| {
|
||||||
BenchmarkId::from_parameter(count),
|
b.iter(|| black_box(store.multi_get(h)))
|
||||||
&hashes,
|
});
|
||||||
|b, h| {
|
|
||||||
b.iter(|| black_box(store.multi_get(h)))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(dir);
|
drop(dir);
|
||||||
|
|
@ -304,9 +297,7 @@ fn utxo_lookup(c: &mut Criterion) {
|
||||||
group.bench_with_input(
|
group.bench_with_input(
|
||||||
BenchmarkId::new("size", db_size),
|
BenchmarkId::new("size", db_size),
|
||||||
&target_txid,
|
&target_txid,
|
||||||
|b, txid| {
|
|b, txid| b.iter(|| black_box(store.get(txid, 0))),
|
||||||
b.iter(|| black_box(store.get(txid, 0)))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
drop(dir);
|
drop(dir);
|
||||||
|
|
@ -320,26 +311,22 @@ fn utxo_delete(c: &mut Criterion) {
|
||||||
group.throughput(Throughput::Elements(1));
|
group.throughput(Throughput::Elements(1));
|
||||||
|
|
||||||
for db_size in [1000, 10000] {
|
for db_size in [1000, 10000] {
|
||||||
group.bench_with_input(
|
group.bench_with_input(BenchmarkId::new("size", db_size), &db_size, |b, &size| {
|
||||||
BenchmarkId::new("size", db_size),
|
b.iter_batched(
|
||||||
&db_size,
|
|| {
|
||||||
|b, &size| {
|
let (dir, db) = setup_db();
|
||||||
b.iter_batched(
|
let store = populate_utxos(&db, size);
|
||||||
|| {
|
let target_txid = make_txid((size / 2) as u64);
|
||||||
let (dir, db) = setup_db();
|
(dir, store, target_txid)
|
||||||
let store = populate_utxos(&db, size);
|
},
|
||||||
let target_txid = make_txid((size / 2) as u64);
|
|(dir, store, txid)| {
|
||||||
(dir, store, target_txid)
|
let result = store.delete(&txid, 0);
|
||||||
},
|
drop(dir);
|
||||||
|(dir, store, txid)| {
|
black_box(result)
|
||||||
let result = store.delete(&txid, 0);
|
},
|
||||||
drop(dir);
|
criterion::BatchSize::SmallInput,
|
||||||
black_box(result)
|
)
|
||||||
},
|
});
|
||||||
criterion::BatchSize::SmallInput,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
|
|
@ -474,19 +461,15 @@ fn iterator_full_scan(c: &mut Criterion) {
|
||||||
let _ = populate_headers(&db, db_size);
|
let _ = populate_headers(&db, db_size);
|
||||||
|
|
||||||
group.throughput(Throughput::Elements(db_size as u64));
|
group.throughput(Throughput::Elements(db_size as u64));
|
||||||
group.bench_with_input(
|
group.bench_with_input(BenchmarkId::from_parameter(db_size), &db, |b, database| {
|
||||||
BenchmarkId::from_parameter(db_size),
|
b.iter(|| {
|
||||||
&db,
|
let mut count = 0;
|
||||||
|b, database| {
|
for _ in database.iter(cf::HEADERS).unwrap() {
|
||||||
b.iter(|| {
|
count += 1;
|
||||||
let mut count = 0;
|
}
|
||||||
for _ in database.iter(cf::HEADERS).unwrap() {
|
black_box(count)
|
||||||
count += 1;
|
})
|
||||||
}
|
});
|
||||||
black_box(count)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
drop(dir);
|
drop(dir);
|
||||||
}
|
}
|
||||||
|
|
@ -502,7 +485,13 @@ fn iterator_prefix_scan(c: &mut Criterion) {
|
||||||
for tx_num in 0..100 {
|
for tx_num in 0..100 {
|
||||||
let txid = make_txid(tx_num);
|
let txid = make_txid(tx_num);
|
||||||
for output_idx in 0..10 {
|
for output_idx in 0..10 {
|
||||||
store.put(&txid, output_idx, &make_utxo(tx_num * 10 + output_idx as u64)).unwrap();
|
store
|
||||||
|
.put(
|
||||||
|
&txid,
|
||||||
|
output_idx,
|
||||||
|
&make_utxo(tx_num * 10 + output_idx as u64),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -535,9 +524,7 @@ fn ghostdag_operations(c: &mut Criterion) {
|
||||||
|
|
||||||
// Read
|
// Read
|
||||||
let target = make_block_id(500);
|
let target = make_block_id(500);
|
||||||
group.bench_function("get", |b| {
|
group.bench_function("get", |b| b.iter(|| black_box(store.get(&target))));
|
||||||
b.iter(|| black_box(store.get(&target)))
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("get_blue_score", |b| {
|
group.bench_function("get_blue_score", |b| {
|
||||||
b.iter(|| black_box(store.get_blue_score(&target)))
|
b.iter(|| black_box(store.get_blue_score(&target)))
|
||||||
|
|
@ -581,9 +568,7 @@ fn relations_operations(c: &mut Criterion) {
|
||||||
|
|
||||||
let target = make_block_id(500);
|
let target = make_block_id(500);
|
||||||
|
|
||||||
group.bench_function("get", |b| {
|
group.bench_function("get", |b| b.iter(|| black_box(store.get(&target))));
|
||||||
b.iter(|| black_box(store.get(&target)))
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("get_parents", |b| {
|
group.bench_function("get_parents", |b| {
|
||||||
b.iter(|| black_box(store.get_parents(&target)))
|
b.iter(|| black_box(store.get_parents(&target)))
|
||||||
|
|
@ -621,18 +606,14 @@ fn metadata_operations(c: &mut Criterion) {
|
||||||
|
|
||||||
let mut group = c.benchmark_group("metadata_store");
|
let mut group = c.benchmark_group("metadata_store");
|
||||||
|
|
||||||
group.bench_function("get_tips", |b| {
|
group.bench_function("get_tips", |b| b.iter(|| black_box(store.get_tips())));
|
||||||
b.iter(|| black_box(store.get_tips()))
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("set_tips", |b| {
|
group.bench_function("set_tips", |b| {
|
||||||
let new_tips = vec![make_block_id(200), make_block_id(201)];
|
let new_tips = vec![make_block_id(200), make_block_id(201)];
|
||||||
b.iter(|| black_box(store.set_tips(&new_tips)))
|
b.iter(|| black_box(store.set_tips(&new_tips)))
|
||||||
});
|
});
|
||||||
|
|
||||||
group.bench_function("get_genesis", |b| {
|
group.bench_function("get_genesis", |b| b.iter(|| black_box(store.get_genesis())));
|
||||||
b.iter(|| black_box(store.get_genesis()))
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("get_chain_state", |b| {
|
group.bench_function("get_chain_state", |b| {
|
||||||
b.iter(|| black_box(store.get_chain_state()))
|
b.iter(|| black_box(store.get_chain_state()))
|
||||||
|
|
@ -747,9 +728,7 @@ fn storage_cache_operations(c: &mut Criterion) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stats access
|
// Stats access
|
||||||
group.bench_function("stats", |b| {
|
group.bench_function("stats", |b| b.iter(|| black_box(cache.stats())));
|
||||||
b.iter(|| black_box(cache.stats()))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Total entries
|
// Total entries
|
||||||
group.bench_function("total_entries", |b| {
|
group.bench_function("total_entries", |b| {
|
||||||
|
|
@ -812,17 +791,9 @@ criterion_group!(
|
||||||
utxo_get_by_tx,
|
utxo_get_by_tx,
|
||||||
);
|
);
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(batch_benches, batch_write_mixed, batch_headers_and_utxos,);
|
||||||
batch_benches,
|
|
||||||
batch_write_mixed,
|
|
||||||
batch_headers_and_utxos,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(iterator_benches, iterator_full_scan, iterator_prefix_scan,);
|
||||||
iterator_benches,
|
|
||||||
iterator_full_scan,
|
|
||||||
iterator_prefix_scan,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(
|
||||||
store_benches,
|
store_benches,
|
||||||
|
|
@ -837,10 +808,7 @@ criterion_group!(
|
||||||
storage_cache_operations,
|
storage_cache_operations,
|
||||||
);
|
);
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(db_benches, database_creation,);
|
||||||
db_benches,
|
|
||||||
database_creation,
|
|
||||||
);
|
|
||||||
|
|
||||||
criterion_main!(
|
criterion_main!(
|
||||||
header_benches,
|
header_benches,
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,7 @@
|
||||||
//! Provides a typed interface to the underlying RocksDB instance.
|
//! Provides a typed interface to the underlying RocksDB instance.
|
||||||
|
|
||||||
use crate::cf;
|
use crate::cf;
|
||||||
use rocksdb::{
|
use rocksdb::{ColumnFamily, ColumnFamilyDescriptor, DBCompressionType, Options, WriteBatch, DB};
|
||||||
ColumnFamily, ColumnFamilyDescriptor, DBCompressionType, Options, WriteBatch, DB,
|
|
||||||
};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -182,7 +180,10 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens a database in read-only mode.
|
/// Opens a database in read-only mode.
|
||||||
pub fn open_read_only<P: AsRef<Path>>(path: P, config: &DatabaseConfig) -> Result<Self, DbError> {
|
pub fn open_read_only<P: AsRef<Path>>(
|
||||||
|
path: P,
|
||||||
|
config: &DatabaseConfig,
|
||||||
|
) -> Result<Self, DbError> {
|
||||||
let path_str = path.as_ref().to_string_lossy().to_string();
|
let path_str = path.as_ref().to_string_lossy().to_string();
|
||||||
let opts = config.to_options();
|
let opts = config.to_options();
|
||||||
|
|
||||||
|
|
@ -241,7 +242,10 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterates over all keys in a column family.
|
/// Iterates over all keys in a column family.
|
||||||
pub fn iter(&self, cf_name: &str) -> Result<impl Iterator<Item = (Box<[u8]>, Box<[u8]>)> + '_, DbError> {
|
pub fn iter(
|
||||||
|
&self,
|
||||||
|
cf_name: &str,
|
||||||
|
) -> Result<impl Iterator<Item = (Box<[u8]>, Box<[u8]>)> + '_, DbError> {
|
||||||
let cf = self.get_cf(cf_name)?;
|
let cf = self.get_cf(cf_name)?;
|
||||||
let iter = self.db.iterator_cf(cf, rocksdb::IteratorMode::Start);
|
let iter = self.db.iterator_cf(cf, rocksdb::IteratorMode::Start);
|
||||||
Ok(iter.map(|r| r.unwrap()))
|
Ok(iter.map(|r| r.unwrap()))
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ pub use cache::{CacheConfig, CacheSizeInfo, CacheStats, StorageCache};
|
||||||
pub use db::{Database, DatabaseConfig, DbError};
|
pub use db::{Database, DatabaseConfig, DbError};
|
||||||
pub use stores::{
|
pub use stores::{
|
||||||
BatchStore, BlockBody, BlockStore, ChainState, ContractStateStore, ContractStore,
|
BatchStore, BlockBody, BlockStore, ChainState, ContractStateStore, ContractStore,
|
||||||
GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredContract,
|
GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredContract, StoredGhostdagData,
|
||||||
StoredGhostdagData, StoredRelations, StoredUtxo, TransactionStore, UtxoStore,
|
StoredRelations, StoredUtxo, TransactionStore, UtxoStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Column family names.
|
/// Column family names.
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,7 @@ impl HeaderStore {
|
||||||
/// Stores a header.
|
/// Stores a header.
|
||||||
pub fn put(&self, header: &BlockHeader) -> Result<(), DbError> {
|
pub fn put(&self, header: &BlockHeader) -> Result<(), DbError> {
|
||||||
let hash = header.block_id();
|
let hash = header.block_id();
|
||||||
let bytes = borsh::to_vec(header)
|
let bytes = borsh::to_vec(header).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::HEADERS, hash.as_bytes(), &bytes)
|
self.db.put(cf::HEADERS, hash.as_bytes(), &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,8 +132,7 @@ impl BlockStore {
|
||||||
|
|
||||||
/// Stores a block body.
|
/// Stores a block body.
|
||||||
pub fn put(&self, hash: &Hash256, body: &BlockBody) -> Result<(), DbError> {
|
pub fn put(&self, hash: &Hash256, body: &BlockBody) -> Result<(), DbError> {
|
||||||
let bytes = borsh::to_vec(body)
|
let bytes = borsh::to_vec(body).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::BLOCKS, hash.as_bytes(), &bytes)
|
self.db.put(cf::BLOCKS, hash.as_bytes(), &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,8 +179,7 @@ impl TransactionStore {
|
||||||
/// Stores a transaction.
|
/// Stores a transaction.
|
||||||
pub fn put(&self, tx: &Transaction) -> Result<(), DbError> {
|
pub fn put(&self, tx: &Transaction) -> Result<(), DbError> {
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
let bytes = borsh::to_vec(tx)
|
let bytes = borsh::to_vec(tx).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::TRANSACTIONS, txid.as_bytes(), &bytes)
|
self.db.put(cf::TRANSACTIONS, txid.as_bytes(), &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,8 +265,7 @@ impl UtxoStore {
|
||||||
/// Stores a UTXO.
|
/// Stores a UTXO.
|
||||||
pub fn put(&self, txid: &TransactionId, index: u32, utxo: &StoredUtxo) -> Result<(), DbError> {
|
pub fn put(&self, txid: &TransactionId, index: u32, utxo: &StoredUtxo) -> Result<(), DbError> {
|
||||||
let key = Self::make_key(txid, index);
|
let key = Self::make_key(txid, index);
|
||||||
let bytes = borsh::to_vec(utxo)
|
let bytes = borsh::to_vec(utxo).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::UTXOS, &key, &bytes)
|
self.db.put(cf::UTXOS, &key, &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,8 +348,7 @@ impl RelationsStore {
|
||||||
|
|
||||||
/// Stores relations for a block.
|
/// Stores relations for a block.
|
||||||
pub fn put(&self, block_id: &BlockId, relations: &StoredRelations) -> Result<(), DbError> {
|
pub fn put(&self, block_id: &BlockId, relations: &StoredRelations) -> Result<(), DbError> {
|
||||||
let bytes = borsh::to_vec(relations)
|
let bytes = borsh::to_vec(relations).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::RELATIONS, block_id.as_bytes(), &bytes)
|
self.db.put(cf::RELATIONS, block_id.as_bytes(), &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -429,8 +424,7 @@ impl GhostdagStore {
|
||||||
|
|
||||||
/// Stores GHOSTDAG data for a block.
|
/// Stores GHOSTDAG data for a block.
|
||||||
pub fn put(&self, block_id: &BlockId, data: &StoredGhostdagData) -> Result<(), DbError> {
|
pub fn put(&self, block_id: &BlockId, data: &StoredGhostdagData) -> Result<(), DbError> {
|
||||||
let bytes = borsh::to_vec(data)
|
let bytes = borsh::to_vec(data).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::GHOSTDAG, block_id.as_bytes(), &bytes)
|
self.db.put(cf::GHOSTDAG, block_id.as_bytes(), &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,8 +494,7 @@ impl MetadataStore {
|
||||||
|
|
||||||
/// Sets the current DAG tips.
|
/// Sets the current DAG tips.
|
||||||
pub fn set_tips(&self, tips: &[BlockId]) -> Result<(), DbError> {
|
pub fn set_tips(&self, tips: &[BlockId]) -> Result<(), DbError> {
|
||||||
let bytes = borsh::to_vec(tips)
|
let bytes = borsh::to_vec(tips).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::METADATA, Self::KEY_TIPS, &bytes)
|
self.db.put(cf::METADATA, Self::KEY_TIPS, &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -519,8 +512,7 @@ impl MetadataStore {
|
||||||
|
|
||||||
/// Sets the pruning point.
|
/// Sets the pruning point.
|
||||||
pub fn set_pruning_point(&self, point: &BlockId) -> Result<(), DbError> {
|
pub fn set_pruning_point(&self, point: &BlockId) -> Result<(), DbError> {
|
||||||
let bytes = borsh::to_vec(point)
|
let bytes = borsh::to_vec(point).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::METADATA, Self::KEY_PRUNING_POINT, &bytes)
|
self.db.put(cf::METADATA, Self::KEY_PRUNING_POINT, &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -538,8 +530,7 @@ impl MetadataStore {
|
||||||
|
|
||||||
/// Sets the chain state.
|
/// Sets the chain state.
|
||||||
pub fn set_chain_state(&self, state: &ChainState) -> Result<(), DbError> {
|
pub fn set_chain_state(&self, state: &ChainState) -> Result<(), DbError> {
|
||||||
let bytes = borsh::to_vec(state)
|
let bytes = borsh::to_vec(state).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::METADATA, Self::KEY_CHAIN_STATE, &bytes)
|
self.db.put(cf::METADATA, Self::KEY_CHAIN_STATE, &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -557,8 +548,7 @@ impl MetadataStore {
|
||||||
|
|
||||||
/// Sets the genesis block ID.
|
/// Sets the genesis block ID.
|
||||||
pub fn set_genesis(&self, genesis: &BlockId) -> Result<(), DbError> {
|
pub fn set_genesis(&self, genesis: &BlockId) -> Result<(), DbError> {
|
||||||
let bytes = borsh::to_vec(genesis)
|
let bytes = borsh::to_vec(genesis).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::METADATA, Self::KEY_GENESIS, &bytes)
|
self.db.put(cf::METADATA, Self::KEY_GENESIS, &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -631,8 +621,7 @@ impl ContractStore {
|
||||||
|
|
||||||
/// Stores a contract.
|
/// Stores a contract.
|
||||||
pub fn put(&self, contract: &StoredContract) -> Result<(), DbError> {
|
pub fn put(&self, contract: &StoredContract) -> Result<(), DbError> {
|
||||||
let bytes = borsh::to_vec(contract)
|
let bytes = borsh::to_vec(contract).map_err(|e| DbError::Serialization(e.to_string()))?;
|
||||||
.map_err(|e| DbError::Serialization(e.to_string()))?;
|
|
||||||
self.db.put(cf::CONTRACTS, &contract.code_hash, &bytes)
|
self.db.put(cf::CONTRACTS, &contract.code_hash, &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -694,11 +683,7 @@ impl ContractStateStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a storage value.
|
/// Deletes a storage value.
|
||||||
pub fn delete(
|
pub fn delete(&self, contract_id: &[u8; 32], storage_key: &[u8; 32]) -> Result<(), DbError> {
|
||||||
&self,
|
|
||||||
contract_id: &[u8; 32],
|
|
||||||
storage_key: &[u8; 32],
|
|
||||||
) -> Result<(), DbError> {
|
|
||||||
let key = Self::make_key(contract_id, storage_key);
|
let key = Self::make_key(contract_id, storage_key);
|
||||||
self.db.delete(cf::CONTRACT_STATE, &key)
|
self.db.delete(cf::CONTRACT_STATE, &key)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,11 +95,7 @@ impl Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an address from raw components.
|
/// Creates an address from raw components.
|
||||||
pub fn from_parts(
|
pub fn from_parts(network: Network, addr_type: AddressType, payload: [u8; 32]) -> Self {
|
||||||
network: Network,
|
|
||||||
addr_type: AddressType,
|
|
||||||
payload: [u8; 32],
|
|
||||||
) -> Self {
|
|
||||||
Address {
|
Address {
|
||||||
network,
|
network,
|
||||||
addr_type,
|
addr_type,
|
||||||
|
|
@ -110,8 +106,8 @@ impl Address {
|
||||||
/// Parses an address from a Bech32m string.
|
/// Parses an address from a Bech32m string.
|
||||||
pub fn from_str(s: &str) -> Result<Self, AddressError> {
|
pub fn from_str(s: &str) -> Result<Self, AddressError> {
|
||||||
// Decode Bech32m
|
// Decode Bech32m
|
||||||
let (hrp, data) = bech32::decode(s)
|
let (hrp, data) =
|
||||||
.map_err(|e| AddressError::Bech32Error(e.to_string()))?;
|
bech32::decode(s).map_err(|e| AddressError::Bech32Error(e.to_string()))?;
|
||||||
|
|
||||||
// Determine network from HRP
|
// Determine network from HRP
|
||||||
let network = match hrp.as_str() {
|
let network = match hrp.as_str() {
|
||||||
|
|
@ -169,10 +165,7 @@ impl Address {
|
||||||
|
|
||||||
/// Returns true if this is a post-quantum address.
|
/// Returns true if this is a post-quantum address.
|
||||||
pub fn is_post_quantum(&self) -> bool {
|
pub fn is_post_quantum(&self) -> bool {
|
||||||
matches!(
|
matches!(self.addr_type, AddressType::P2pkhPqc | AddressType::P2shPqc)
|
||||||
self.addr_type,
|
|
||||||
AddressType::P2pkhPqc | AddressType::P2shPqc
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a short representation of the address for display.
|
/// Returns a short representation of the address for display.
|
||||||
|
|
@ -258,9 +251,8 @@ impl borsh::BorshDeserialize for Address {
|
||||||
|
|
||||||
let mut type_byte = [0u8; 1];
|
let mut type_byte = [0u8; 1];
|
||||||
reader.read_exact(&mut type_byte)?;
|
reader.read_exact(&mut type_byte)?;
|
||||||
let addr_type = AddressType::from_byte(type_byte[0]).map_err(|e| {
|
let addr_type = AddressType::from_byte(type_byte[0])
|
||||||
std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut payload = [0u8; 32];
|
let mut payload = [0u8; 32];
|
||||||
reader.read_exact(&mut payload)?;
|
reader.read_exact(&mut payload)?;
|
||||||
|
|
@ -324,8 +316,7 @@ mod tests {
|
||||||
let ed_pubkey = [1u8; 32];
|
let ed_pubkey = [1u8; 32];
|
||||||
let dilithium_pubkey = vec![2u8; 1312]; // Dilithium3 public key size
|
let dilithium_pubkey = vec![2u8; 1312]; // Dilithium3 public key size
|
||||||
|
|
||||||
let addr =
|
let addr = Address::from_hybrid_pubkey(Network::Mainnet, &ed_pubkey, &dilithium_pubkey);
|
||||||
Address::from_hybrid_pubkey(Network::Mainnet, &ed_pubkey, &dilithium_pubkey);
|
|
||||||
|
|
||||||
assert!(addr.is_post_quantum());
|
assert!(addr.is_post_quantum());
|
||||||
assert_eq!(addr.addr_type(), AddressType::P2pkhPqc);
|
assert_eq!(addr.addr_type(), AddressType::P2pkhPqc);
|
||||||
|
|
|
||||||
|
|
@ -159,11 +159,7 @@ impl BlockBody {
|
||||||
|
|
||||||
/// Computes the Merkle root of transactions.
|
/// Computes the Merkle root of transactions.
|
||||||
pub fn merkle_root(&self) -> Hash256 {
|
pub fn merkle_root(&self) -> Hash256 {
|
||||||
let tx_hashes: Vec<Hash256> = self
|
let tx_hashes: Vec<Hash256> = self.transactions.iter().map(|tx| tx.txid()).collect();
|
||||||
.transactions
|
|
||||||
.iter()
|
|
||||||
.map(|tx| tx.txid())
|
|
||||||
.collect();
|
|
||||||
Hash256::merkle_root(&tx_hashes)
|
Hash256::merkle_root(&tx_hashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,10 +248,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hash_display() {
|
fn test_hash_display() {
|
||||||
let hash = Hash256::from_hex(
|
let hash =
|
||||||
"abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
|
Hash256::from_hex("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")
|
||||||
)
|
.unwrap();
|
||||||
.unwrap();
|
|
||||||
let display = format!("{}", hash);
|
let display = format!("{}", hash);
|
||||||
assert!(display.contains("..."));
|
assert!(display.contains("..."));
|
||||||
assert!(display.starts_with("abcdef12"));
|
assert!(display.starts_with("abcdef12"));
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ pub use hash::{Hash256, HashError};
|
||||||
pub use transaction::{Transaction, TransactionId, TxInput, TxOutput};
|
pub use transaction::{Transaction, TransactionId, TxInput, TxOutput};
|
||||||
|
|
||||||
/// Network identifier for Synor chains.
|
/// Network identifier for Synor chains.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
|
||||||
|
)]
|
||||||
pub enum Network {
|
pub enum Network {
|
||||||
/// Main production network
|
/// Main production network
|
||||||
Mainnet,
|
Mainnet,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,16 @@ pub type TransactionId = Hash256;
|
||||||
|
|
||||||
/// Outpoint - references a specific output of a previous transaction.
|
/// Outpoint - references a specific output of a previous transaction.
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
)]
|
)]
|
||||||
pub struct Outpoint {
|
pub struct Outpoint {
|
||||||
/// The transaction ID containing the output.
|
/// The transaction ID containing the output.
|
||||||
|
|
@ -184,7 +193,16 @@ impl ScriptPubKey {
|
||||||
|
|
||||||
/// Script type enumeration.
|
/// Script type enumeration.
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
)]
|
)]
|
||||||
#[borsh(use_discriminant = true)]
|
#[borsh(use_discriminant = true)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
|
@ -222,10 +240,7 @@ pub struct Transaction {
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
/// Creates a new transaction.
|
/// Creates a new transaction.
|
||||||
pub fn new(
|
pub fn new(inputs: Vec<TxInput>, outputs: Vec<TxOutput>) -> Self {
|
||||||
inputs: Vec<TxInput>,
|
|
||||||
outputs: Vec<TxOutput>,
|
|
||||||
) -> Self {
|
|
||||||
Transaction {
|
Transaction {
|
||||||
version: 1,
|
version: 1,
|
||||||
inputs,
|
inputs,
|
||||||
|
|
@ -263,11 +278,9 @@ impl Transaction {
|
||||||
|
|
||||||
/// Computes the total output value.
|
/// Computes the total output value.
|
||||||
pub fn total_output(&self) -> Amount {
|
pub fn total_output(&self) -> Amount {
|
||||||
self.outputs
|
self.outputs.iter().fold(Amount::ZERO, |acc, output| {
|
||||||
.iter()
|
acc.saturating_add(output.amount)
|
||||||
.fold(Amount::ZERO, |acc, output| {
|
})
|
||||||
acc.saturating_add(output.amount)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the transaction weight (for fee calculation).
|
/// Returns the transaction weight (for fee calculation).
|
||||||
|
|
@ -303,7 +316,16 @@ impl fmt::Display for Transaction {
|
||||||
|
|
||||||
/// Subnetwork identifier.
|
/// Subnetwork identifier.
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
)]
|
)]
|
||||||
pub struct SubnetworkId([u8; 20]);
|
pub struct SubnetworkId([u8; 20]);
|
||||||
|
|
||||||
|
|
@ -312,10 +334,12 @@ impl SubnetworkId {
|
||||||
pub const NATIVE: SubnetworkId = SubnetworkId([0u8; 20]);
|
pub const NATIVE: SubnetworkId = SubnetworkId([0u8; 20]);
|
||||||
|
|
||||||
/// Coinbase subnetwork.
|
/// Coinbase subnetwork.
|
||||||
pub const COINBASE: SubnetworkId = SubnetworkId([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
pub const COINBASE: SubnetworkId =
|
||||||
|
SubnetworkId([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
|
|
||||||
/// Registry subnetwork (for registrations).
|
/// Registry subnetwork (for registrations).
|
||||||
pub const REGISTRY: SubnetworkId = SubnetworkId([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
pub const REGISTRY: SubnetworkId =
|
||||||
|
SubnetworkId([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
|
|
||||||
/// Creates a new subnetwork ID from bytes.
|
/// Creates a new subnetwork ID from bytes.
|
||||||
pub fn from_bytes(bytes: [u8; 20]) -> Self {
|
pub fn from_bytes(bytes: [u8; 20]) -> Self {
|
||||||
|
|
@ -362,10 +386,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_coinbase_transaction() {
|
fn test_coinbase_transaction() {
|
||||||
let output = TxOutput::new(
|
let output = TxOutput::new(Amount::from_synor(500), ScriptPubKey::p2pkh(&[0u8; 32]));
|
||||||
Amount::from_synor(500),
|
|
||||||
ScriptPubKey::p2pkh(&[0u8; 32]),
|
|
||||||
);
|
|
||||||
let tx = Transaction::coinbase(vec![output], b"Synor Genesis".to_vec());
|
let tx = Transaction::coinbase(vec![output], b"Synor Genesis".to_vec());
|
||||||
|
|
||||||
assert!(tx.is_coinbase());
|
assert!(tx.is_coinbase());
|
||||||
|
|
@ -375,14 +396,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transaction_id() {
|
fn test_transaction_id() {
|
||||||
let input = TxInput::new(
|
let input = TxInput::new(Outpoint::new(Hash256::blake3(b"prev_tx"), 0), vec![]);
|
||||||
Outpoint::new(Hash256::blake3(b"prev_tx"), 0),
|
let output = TxOutput::new(Amount::from_synor(10), ScriptPubKey::p2pkh(&[1u8; 32]));
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
let output = TxOutput::new(
|
|
||||||
Amount::from_synor(10),
|
|
||||||
ScriptPubKey::p2pkh(&[1u8; 32]),
|
|
||||||
);
|
|
||||||
let tx = Transaction::new(vec![input], vec![output]);
|
let tx = Transaction::new(vec![input], vec![output]);
|
||||||
|
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,9 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use wasmtime::{
|
use wasmtime::{
|
||||||
Config, Engine, Instance, Linker, Memory, Module, Store, StoreLimits,
|
Config, Engine, Instance, Linker, Memory, Module, Store, StoreLimits, StoreLimitsBuilder,
|
||||||
StoreLimitsBuilder,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
use crate::context::ExecutionContext;
|
use crate::context::ExecutionContext;
|
||||||
use crate::gas::GasMeter;
|
use crate::gas::GasMeter;
|
||||||
use crate::storage::{ContractStorage, MemoryStorage};
|
use crate::storage::{ContractStorage, MemoryStorage};
|
||||||
|
|
@ -243,7 +241,11 @@ impl VmEngine {
|
||||||
// Read from storage
|
// Read from storage
|
||||||
let data = caller.data_mut();
|
let data = caller.data_mut();
|
||||||
let storage_key = crate::storage::StorageKey::new(key);
|
let storage_key = crate::storage::StorageKey::new(key);
|
||||||
match data.context.storage.get(&data.context.call.contract, &storage_key) {
|
match data
|
||||||
|
.context
|
||||||
|
.storage
|
||||||
|
.get(&data.context.call.contract, &storage_key)
|
||||||
|
{
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
let len = value.0.len();
|
let len = value.0.len();
|
||||||
// Write to output buffer
|
// Write to output buffer
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,10 @@ impl GasEstimator {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute init
|
// Execute init
|
||||||
match self.engine.execute(&module, "__synor_init", init_params, context, self.max_gas) {
|
match self
|
||||||
|
.engine
|
||||||
|
.execute(&module, "__synor_init", init_params, context, self.max_gas)
|
||||||
|
{
|
||||||
Ok(result) => GasEstimate::success(result.gas_used, result.return_data),
|
Ok(result) => GasEstimate::success(result.gas_used, result.return_data),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Try to extract gas used from error
|
// Try to extract gas used from error
|
||||||
|
|
@ -141,7 +144,10 @@ impl GasEstimator {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute call
|
// Execute call
|
||||||
match self.engine.execute(module, "__synor_call", &call_data, context, self.max_gas) {
|
match self
|
||||||
|
.engine
|
||||||
|
.execute(module, "__synor_call", &call_data, context, self.max_gas)
|
||||||
|
{
|
||||||
Ok(result) => GasEstimate::success(result.gas_used, result.return_data),
|
Ok(result) => GasEstimate::success(result.gas_used, result.return_data),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let gas = match &e {
|
let gas = match &e {
|
||||||
|
|
@ -167,7 +173,14 @@ impl GasEstimator {
|
||||||
storage: MemoryStorage,
|
storage: MemoryStorage,
|
||||||
) -> GasEstimate {
|
) -> GasEstimate {
|
||||||
// First, try with max gas to see if it succeeds
|
// First, try with max gas to see if it succeeds
|
||||||
let initial = self.estimate_call(module, method, params, caller.clone(), value, storage.clone());
|
let initial = self.estimate_call(
|
||||||
|
module,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
caller.clone(),
|
||||||
|
value,
|
||||||
|
storage.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
if !initial.success {
|
if !initial.success {
|
||||||
return initial;
|
return initial;
|
||||||
|
|
@ -197,7 +210,10 @@ impl GasEstimator {
|
||||||
1, // chain_id
|
1, // chain_id
|
||||||
);
|
);
|
||||||
|
|
||||||
match self.engine.execute(module, "__synor_call", &call_data, context, mid) {
|
match self
|
||||||
|
.engine
|
||||||
|
.execute(module, "__synor_call", &call_data, context, mid)
|
||||||
|
{
|
||||||
Ok(_) => high = mid,
|
Ok(_) => high = mid,
|
||||||
Err(_) => low = mid + 1,
|
Err(_) => low = mid + 1,
|
||||||
}
|
}
|
||||||
|
|
@ -334,7 +350,10 @@ mod tests {
|
||||||
fn test_calldata_cost() {
|
fn test_calldata_cost() {
|
||||||
let data = [0u8, 1, 0, 2, 0, 3]; // 3 zeros, 3 non-zeros
|
let data = [0u8, 1, 0, 2, 0, 3]; // 3 zeros, 3 non-zeros
|
||||||
let cost = costs::calldata_cost(&data);
|
let cost = costs::calldata_cost(&data);
|
||||||
assert_eq!(cost, 3 * costs::CALLDATA_ZERO_BYTE + 3 * costs::CALLDATA_BYTE);
|
assert_eq!(
|
||||||
|
cost,
|
||||||
|
3 * costs::CALLDATA_ZERO_BYTE + 3 * costs::CALLDATA_BYTE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,10 @@ impl HostFunctions {
|
||||||
// Add refund for deletion
|
// Add refund for deletion
|
||||||
ctx.gas.add_refund(ctx.gas.config().storage_delete_refund);
|
ctx.gas.add_refund(ctx.gas.config().storage_delete_refund);
|
||||||
|
|
||||||
let existed = ctx.storage.delete(&ctx.call.contract, &storage_key).is_some();
|
let existed = ctx
|
||||||
|
.storage
|
||||||
|
.delete(&ctx.call.contract, &storage_key)
|
||||||
|
.is_some();
|
||||||
Ok(existed)
|
Ok(existed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ use synor_types::Hash256;
|
||||||
use crate::ContractId;
|
use crate::ContractId;
|
||||||
|
|
||||||
/// Storage key (256-bit).
|
/// Storage key (256-bit).
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, BorshSerialize, BorshDeserialize)]
|
#[derive(
|
||||||
|
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, BorshSerialize, BorshDeserialize,
|
||||||
|
)]
|
||||||
pub struct StorageKey(pub [u8; 32]);
|
pub struct StorageKey(pub [u8; 32]);
|
||||||
|
|
||||||
impl StorageKey {
|
impl StorageKey {
|
||||||
|
|
@ -209,10 +211,7 @@ impl MemoryStorage {
|
||||||
for (contract, pending_changes) in self.pending.drain() {
|
for (contract, pending_changes) in self.pending.drain() {
|
||||||
for (key, new_value) in pending_changes {
|
for (key, new_value) in pending_changes {
|
||||||
// Get old value from committed data
|
// Get old value from committed data
|
||||||
let old_value = self.data
|
let old_value = self.data.get(&contract).and_then(|m| m.get(&key)).cloned();
|
||||||
.get(&contract)
|
|
||||||
.and_then(|m| m.get(&key))
|
|
||||||
.cloned();
|
|
||||||
|
|
||||||
changes.push(crate::StorageChange {
|
changes.push(crate::StorageChange {
|
||||||
contract,
|
contract,
|
||||||
|
|
@ -233,10 +232,7 @@ impl MemoryStorage {
|
||||||
for (contract, pending_changes) in &self.pending {
|
for (contract, pending_changes) in &self.pending {
|
||||||
for (key, new_value) in pending_changes {
|
for (key, new_value) in pending_changes {
|
||||||
// Get old value from committed data
|
// Get old value from committed data
|
||||||
let old_value = self.data
|
let old_value = self.data.get(contract).and_then(|m| m.get(key)).cloned();
|
||||||
.get(contract)
|
|
||||||
.and_then(|m| m.get(key))
|
|
||||||
.cloned();
|
|
||||||
|
|
||||||
changes.push(crate::StorageChange {
|
changes.push(crate::StorageChange {
|
||||||
contract: *contract,
|
contract: *contract,
|
||||||
|
|
@ -261,10 +257,7 @@ impl ContractStorage for MemoryStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then check committed
|
// Then check committed
|
||||||
self.data
|
self.data.get(contract).and_then(|m| m.get(key)).cloned()
|
||||||
.get(contract)
|
|
||||||
.and_then(|m| m.get(key))
|
|
||||||
.cloned()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(&mut self, contract: &ContractId, key: StorageKey, value: StorageValue) {
|
fn set(&mut self, contract: &ContractId, key: StorageKey, value: StorageValue) {
|
||||||
|
|
@ -514,13 +507,21 @@ mod tests {
|
||||||
|
|
||||||
let root1 = storage.root(&contract);
|
let root1 = storage.root(&contract);
|
||||||
|
|
||||||
storage.set(&contract, StorageKey::from_str("a"), StorageValue::from_u64(1));
|
storage.set(
|
||||||
|
&contract,
|
||||||
|
StorageKey::from_str("a"),
|
||||||
|
StorageValue::from_u64(1),
|
||||||
|
);
|
||||||
storage.commit();
|
storage.commit();
|
||||||
|
|
||||||
let root2 = storage.root(&contract);
|
let root2 = storage.root(&contract);
|
||||||
assert_ne!(root1, root2);
|
assert_ne!(root1, root2);
|
||||||
|
|
||||||
storage.set(&contract, StorageKey::from_str("b"), StorageValue::from_u64(2));
|
storage.set(
|
||||||
|
&contract,
|
||||||
|
StorageKey::from_str("b"),
|
||||||
|
StorageValue::from_u64(2),
|
||||||
|
);
|
||||||
storage.commit();
|
storage.commit();
|
||||||
|
|
||||||
let root3 = storage.root(&contract);
|
let root3 = storage.root(&contract);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue