From dcd1cccc673123f0a86e5965aca696da76a3f239 Mon Sep 17 00:00:00 2001 From: Gulshan Yadav Date: Mon, 2 Feb 2026 05:58:22 +0530 Subject: [PATCH] style: apply cargo fmt formatting --- apps/cli/src/commands/compiler.rs | 24 +- apps/cli/src/commands/deploy.rs | 45 +-- apps/cli/src/commands/dex.rs | 33 +- apps/cli/src/commands/zk.rs | 47 ++- apps/cli/src/main.rs | 32 +- apps/explorer/src/main.rs | 30 +- apps/synord/src/config.rs | 26 +- apps/synord/src/node.rs | 11 +- apps/synord/src/services/miner.rs | 13 +- apps/synord/tests/byzantine_fault_tests.rs | 183 ++++++---- crates/synor-bridge/src/ethereum.rs | 88 +++-- crates/synor-bridge/src/lib.rs | 12 +- crates/synor-bridge/src/transfer.rs | 77 +++-- crates/synor-bridge/src/vault.rs | 140 +++++--- crates/synor-compute/src/device/mod.rs | 17 +- crates/synor-compute/src/lib.rs | 27 +- crates/synor-compute/src/market/mod.rs | 49 ++- crates/synor-compute/src/memory/mod.rs | 78 +++-- crates/synor-compute/src/model/mod.rs | 156 +++++++-- .../src/processor/capabilities.rs | 10 +- crates/synor-compute/src/processor/mod.rs | 20 +- .../synor-compute/src/processor/operation.rs | 33 +- .../synor-compute/src/processor/profiles.rs | 39 +-- crates/synor-compute/src/processor/types.rs | 5 +- .../src/scheduler/load_balancer.rs | 319 ++++++++++-------- crates/synor-compute/src/scheduler/mod.rs | 32 +- .../synor-compute/src/scheduler/work_queue.rs | 15 +- crates/synor-compute/src/task/mod.rs | 11 +- crates/synor-consensus/src/network.rs | 54 +-- crates/synor-crypto/src/falcon.rs | 16 +- crates/synor-crypto/src/negotiation.rs | 28 +- crates/synor-crypto/src/sphincs.rs | 14 +- crates/synor-dag/src/dagknight.rs | 83 +++-- crates/synor-dag/src/latency.rs | 12 +- crates/synor-dag/src/lib.rs | 22 +- crates/synor-database/src/document.rs | 121 ++++--- crates/synor-database/src/gateway/auth.rs | 2 +- crates/synor-database/src/gateway/handlers.rs | 10 +- crates/synor-database/src/gateway/router.rs | 140 ++++++-- crates/synor-database/src/gateway/server.rs | 5 +- crates/synor-database/src/graph/edge.rs | 34 +- crates/synor-database/src/graph/node.rs | 9 +- crates/synor-database/src/graph/path.rs | 114 +++++-- crates/synor-database/src/graph/query.rs | 185 +++++++--- crates/synor-database/src/graph/store.rs | 77 +++-- crates/synor-database/src/graph/traversal.rs | 80 ++++- crates/synor-database/src/index.rs | 66 ++-- crates/synor-database/src/keyvalue.rs | 29 +- crates/synor-database/src/lib.rs | 6 +- crates/synor-database/src/query.rs | 54 +-- .../src/replication/election.rs | 9 +- crates/synor-database/src/replication/log.rs | 18 +- crates/synor-database/src/replication/raft.rs | 56 ++- .../src/replication/snapshot.rs | 5 +- .../synor-database/src/replication/state.rs | 54 ++- crates/synor-database/src/schema.rs | 4 +- crates/synor-database/src/sql/executor.rs | 66 ++-- crates/synor-database/src/sql/parser.rs | 175 ++++++---- crates/synor-database/src/sql/row.rs | 5 +- crates/synor-database/src/sql/table.rs | 36 +- crates/synor-database/src/sql/transaction.rs | 29 +- crates/synor-database/src/sql/types.rs | 8 +- crates/synor-database/src/timeseries.rs | 6 +- crates/synor-database/src/vector.rs | 61 ++-- crates/synor-economics/src/billing/credit.rs | 47 ++- crates/synor-economics/src/billing/invoice.rs | 21 +- crates/synor-economics/src/billing/mod.rs | 41 ++- crates/synor-economics/src/billing/payment.rs | 19 +- .../src/calculator/estimator.rs | 5 +- crates/synor-economics/src/error.rs | 10 +- crates/synor-economics/src/lib.rs | 8 +- crates/synor-economics/src/metering/mod.rs | 66 ++-- crates/synor-economics/src/oracle/anomaly.rs | 172 +++++++--- .../src/oracle/circuit_breaker.rs | 252 +++++++++----- .../synor-economics/src/oracle/cross_chain.rs | 115 ++++--- .../src/oracle/decentralized.rs | 139 +++++--- .../synor-economics/src/oracle/derivatives.rs | 130 ++++--- .../synor-economics/src/oracle/liquidation.rs | 174 +++++++--- crates/synor-economics/src/oracle/mod.rs | 36 +- .../synor-economics/src/oracle/price_feed.rs | 15 +- crates/synor-economics/src/oracle/twap.rs | 14 +- .../synor-economics/src/pricing/discounts.rs | 37 +- crates/synor-economics/src/pricing/mod.rs | 28 +- crates/synor-economics/src/pricing/tiers.rs | 12 +- crates/synor-gateway/src/auth.rs | 40 +-- crates/synor-gateway/src/config.rs | 24 +- crates/synor-gateway/src/error.rs | 16 +- crates/synor-gateway/src/middleware.rs | 38 +-- crates/synor-gateway/src/routes/compiler.rs | 70 ++-- crates/synor-gateway/src/routes/dex.rs | 56 +-- crates/synor-gateway/src/routes/ibc.rs | 34 +- crates/synor-gateway/src/routes/rpc.rs | 12 +- crates/synor-gateway/src/routes/storage.rs | 14 +- crates/synor-gateway/src/routes/wallet.rs | 20 +- crates/synor-gateway/src/routes/zk.rs | 18 +- crates/synor-gateway/src/server.rs | 10 +- crates/synor-gateway/src/versioning.rs | 10 +- crates/synor-gateway/src/websocket.rs | 12 +- .../synor-hosting/src/bin/hosting-gateway.rs | 14 +- crates/synor-hosting/src/compute.rs | 6 +- crates/synor-hosting/src/config.rs | 25 +- crates/synor-hosting/src/domain.rs | 85 +++-- crates/synor-hosting/src/lib.rs | 62 +++- crates/synor-hosting/src/registry.rs | 25 +- crates/synor-hosting/src/router.rs | 12 +- crates/synor-hosting/src/server/handler.rs | 36 +- crates/synor-hosting/src/server/middleware.rs | 11 +- crates/synor-hosting/src/server/mod.rs | 13 +- crates/synor-ibc/src/channel.rs | 80 +++-- crates/synor-ibc/src/client.rs | 15 +- crates/synor-ibc/src/commitment.rs | 18 +- crates/synor-ibc/src/connection.rs | 24 +- crates/synor-ibc/src/handler.rs | 88 ++--- crates/synor-ibc/src/packet.rs | 22 +- crates/synor-ibc/src/swap.rs | 24 +- crates/synor-ibc/src/types.rs | 17 +- crates/synor-mining/src/lib.rs | 5 +- crates/synor-privacy/src/bulletproofs.rs | 22 +- crates/synor-privacy/src/confidential.rs | 62 ++-- crates/synor-privacy/src/lib.rs | 14 +- crates/synor-privacy/src/pedersen.rs | 23 +- crates/synor-privacy/src/ring.rs | 106 +++--- crates/synor-privacy/src/stealth.rs | 55 +-- crates/synor-sharding/src/leader.rs | 11 +- crates/synor-sharding/src/lib.rs | 13 +- crates/synor-sharding/src/messaging.rs | 29 +- crates/synor-sharding/src/proof_agg.rs | 5 +- crates/synor-sharding/src/reshard.rs | 28 +- crates/synor-sharding/src/routing.rs | 6 +- crates/synor-sharding/src/state.rs | 7 +- crates/synor-storage/src/bin/storage-node.rs | 11 +- crates/synor-storage/src/car.rs | 24 +- crates/synor-storage/src/chunker.rs | 40 +-- crates/synor-storage/src/cid.rs | 35 +- crates/synor-storage/src/deal.rs | 8 +- crates/synor-storage/src/erasure.rs | 49 ++- crates/synor-storage/src/gateway/cache.rs | 2 +- crates/synor-storage/src/gateway/handler.rs | 29 +- crates/synor-storage/src/gateway/mod.rs | 30 +- crates/synor-storage/src/gateway/resolver.rs | 25 +- crates/synor-storage/src/lib.rs | 20 +- crates/synor-storage/src/node/mod.rs | 23 +- crates/synor-storage/src/node/network.rs | 20 +- crates/synor-storage/src/node/prover.rs | 37 +- crates/synor-storage/src/node/store.rs | 18 +- crates/synor-storage/src/pinning.rs | 46 ++- crates/synor-storage/src/proof.rs | 26 +- crates/synor-storage/src/stores.rs | 2 +- crates/synor-verifier/src/ast.rs | 10 +- crates/synor-verifier/src/checker.rs | 49 +-- crates/synor-verifier/src/lib.rs | 11 +- crates/synor-verifier/src/parser.rs | 9 +- crates/synor-verifier/src/prover.rs | 8 +- crates/synor-verifier/src/smt.rs | 15 +- crates/synor-verifier/src/symbolic.rs | 45 ++- crates/synor-vm/src/compression.rs | 4 +- crates/synor-vm/src/compute.rs | 17 +- crates/synor-vm/src/lib.rs | 18 +- crates/synor-vm/src/scheduler.rs | 5 +- crates/synor-vm/src/speculation.rs | 30 +- crates/synor-vm/src/tiered.rs | 48 +-- crates/synor-zk/src/bin/zk-sequencer.rs | 4 +- crates/synor-zk/src/circuit.rs | 112 ++---- crates/synor-zk/src/lib.rs | 4 +- crates/synor-zk/src/proof.rs | 76 ++--- crates/synor-zk/src/rollup/mod.rs | 90 ++++- crates/synor-zk/src/state.rs | 31 +- src/lib.rs | 6 +- tests/cross_crate_integration.rs | 308 +++++++++++------ tests/phase13_integration.rs | 12 +- 170 files changed, 4463 insertions(+), 2837 deletions(-) diff --git a/apps/cli/src/commands/compiler.rs b/apps/cli/src/commands/compiler.rs index f35cd75..9ae2257 100644 --- a/apps/cli/src/commands/compiler.rs +++ b/apps/cli/src/commands/compiler.rs @@ -256,7 +256,11 @@ pub async fn handle( Ok(()) } - CompilerCommands::Encode { function, args, abi } => { + CompilerCommands::Encode { + function, + args, + abi, + } => { output::print_info(&format!("Encoding call to: {}", function)); output::print_kv("Arguments", &args); if let Some(a) = abi { @@ -268,7 +272,11 @@ pub async fn handle( Ok(()) } - CompilerCommands::Decode { data, function, abi } => { + CompilerCommands::Decode { + data, + function, + abi, + } => { output::print_info(&format!("Decoding result for: {}", function)); output::print_kv("Data", &data); if let Some(a) = abi { @@ -314,7 +322,11 @@ pub async fn handle( Ok(()) } - CompilerCommands::SecurityScan { wasm, min_severity, format: _ } => { + CompilerCommands::SecurityScan { + wasm, + min_severity, + format: _, + } => { output::print_info(&format!("Security scan: {}", wasm.display())); output::print_kv("Min severity", &min_severity); @@ -344,7 +356,11 @@ pub async fn handle( Ok(()) } - CompilerCommands::Validate { wasm, exports, max_memory } => { + CompilerCommands::Validate { + wasm, + exports, + max_memory, + } => { output::print_info(&format!("Validating: {}", wasm.display())); if let Some(e) = exports { diff --git a/apps/cli/src/commands/deploy.rs b/apps/cli/src/commands/deploy.rs index 6a2a863..4709abc 100644 --- a/apps/cli/src/commands/deploy.rs +++ b/apps/cli/src/commands/deploy.rs @@ -196,8 +196,7 @@ pub async fn deploy( } // Determine output directory - let output_path = output_dir - .unwrap_or_else(|| cwd.join(config.output_dir())); + let output_path = output_dir.unwrap_or_else(|| cwd.join(config.output_dir())); if !output_path.exists() { return Err(anyhow!( @@ -270,7 +269,10 @@ fn validate_name(name: &str) -> Result<()> { if name.len() > 63 { return Err(anyhow!("Name must be 63 characters or less")); } - if !name.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') { + if !name + .chars() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') + { return Err(anyhow!( "Name must contain only lowercase letters, numbers, and hyphens" )); @@ -281,8 +283,8 @@ fn validate_name(name: &str) -> Result<()> { // Reserved names const RESERVED: &[&str] = &[ - "www", "api", "app", "admin", "mail", "ftp", "ssh", "cdn", - "storage", "gateway", "hosting", "node", "synor", + "www", "api", "app", "admin", "mail", "ftp", "ssh", "cdn", "storage", "gateway", "hosting", + "node", "synor", ]; if RESERVED.contains(&name) { return Err(anyhow!("Name '{}' is reserved", name)); @@ -397,11 +399,7 @@ fn guess_content_type(path: &Path) -> String { } /// Upload files to Synor Storage. -async fn upload_files( - base_dir: &Path, - files: &[DeployFile], - gateway_url: &str, -) -> Result { +async fn upload_files(base_dir: &Path, files: &[DeployFile], gateway_url: &str) -> Result { let client = reqwest::Client::new(); // Create a multipart form with all files @@ -445,11 +443,7 @@ async fn upload_files( } /// Register the deployment with the hosting gateway. -async fn register_deployment( - name: &str, - cid: &str, - gateway_url: &str, -) -> Result { +async fn register_deployment(name: &str, cid: &str, gateway_url: &str) -> Result { let client = reqwest::Client::new(); #[derive(Serialize)] @@ -662,7 +656,11 @@ pub async fn delete(name: &str, gateway_url: &str, format: OutputFormat) -> Resu if !response.status().is_success() { let status = response.status(); let body = response.text().await.unwrap_or_default(); - return Err(anyhow!("Failed to delete deployment: {} - {}", status, body)); + return Err(anyhow!( + "Failed to delete deployment: {} - {}", + status, + body + )); } match format { @@ -707,22 +705,13 @@ mod tests { #[test] fn test_guess_content_type() { - assert_eq!( - guess_content_type(Path::new("index.html")), - "text/html" - ); - assert_eq!( - guess_content_type(Path::new("style.css")), - "text/css" - ); + assert_eq!(guess_content_type(Path::new("index.html")), "text/html"); + assert_eq!(guess_content_type(Path::new("style.css")), "text/css"); assert_eq!( guess_content_type(Path::new("app.js")), "application/javascript" ); - assert_eq!( - guess_content_type(Path::new("image.png")), - "image/png" - ); + assert_eq!(guess_content_type(Path::new("image.png")), "image/png"); assert_eq!( guess_content_type(Path::new("data.wasm")), "application/wasm" diff --git a/apps/cli/src/commands/dex.rs b/apps/cli/src/commands/dex.rs index 60b7333..b87a1ff 100644 --- a/apps/cli/src/commands/dex.rs +++ b/apps/cli/src/commands/dex.rs @@ -246,7 +246,13 @@ pub async fn handle( Ok(()) } - DexCommands::PlaceOrder { market, side, price, quantity, wallet } => { + DexCommands::PlaceOrder { + market, + side, + price, + quantity, + wallet, + } => { output::print_info("Placing limit order..."); output::print_kv("Market", &market); output::print_kv("Side", &side); @@ -257,7 +263,12 @@ pub async fn handle( Ok(()) } - DexCommands::MarketOrder { market, side, quantity, wallet } => { + DexCommands::MarketOrder { + market, + side, + quantity, + wallet, + } => { output::print_info("Placing market order..."); output::print_kv("Market", &market); output::print_kv("Side", &side); @@ -275,7 +286,10 @@ pub async fn handle( DexCommands::CancelAll { market, wallet } => { let scope = market.unwrap_or_else(|| "all markets".to_string()); - output::print_info(&format!("Cancelling all orders in {} for {}", scope, wallet)); + output::print_info(&format!( + "Cancelling all orders in {} for {}", + scope, wallet + )); output::print_success("3 orders cancelled"); Ok(()) } @@ -317,7 +331,12 @@ pub async fn handle( Ok(()) } - DexCommands::AddLiquidity { pool_id, amount_a, amount_b, wallet } => { + DexCommands::AddLiquidity { + pool_id, + amount_a, + amount_b, + wallet, + } => { output::print_info("Adding liquidity..."); output::print_kv("Pool", &pool_id); output::print_kv("Amount A", &amount_a); @@ -327,7 +346,11 @@ pub async fn handle( Ok(()) } - DexCommands::RemoveLiquidity { pool_id, lp_amount, wallet } => { + DexCommands::RemoveLiquidity { + pool_id, + lp_amount, + wallet, + } => { output::print_info("Removing liquidity..."); output::print_kv("Pool", &pool_id); output::print_kv("LP Amount", &lp_amount); diff --git a/apps/cli/src/commands/zk.rs b/apps/cli/src/commands/zk.rs index 798d054..09208ce 100644 --- a/apps/cli/src/commands/zk.rs +++ b/apps/cli/src/commands/zk.rs @@ -169,11 +169,7 @@ pub enum ZkCommands { } /// Handle ZK commands. -pub async fn handle( - _client: &RpcClient, - command: ZkCommands, - _format: OutputFormat, -) -> Result<()> { +pub async fn handle(_client: &RpcClient, command: ZkCommands, _format: OutputFormat) -> Result<()> { match command { ZkCommands::Compile { circuit, output } => { output::print_info(&format!("Compiling circuit: {}", circuit.display())); @@ -211,8 +207,16 @@ pub async fn handle( Ok(()) } - ZkCommands::ProveGroth16 { circuit, witness, proving_key: _, output } => { - output::print_info(&format!("Generating Groth16 proof for circuit: {}", circuit)); + ZkCommands::ProveGroth16 { + circuit, + witness, + proving_key: _, + output, + } => { + output::print_info(&format!( + "Generating Groth16 proof for circuit: {}", + circuit + )); output::print_info(&format!("Witness: {}", witness.display())); output::print_info("Computing witness..."); output::print_info("Generating proof..."); @@ -226,7 +230,11 @@ pub async fn handle( Ok(()) } - ZkCommands::ProvePlonk { circuit, witness, output } => { + ZkCommands::ProvePlonk { + circuit, + witness, + output, + } => { output::print_info(&format!("Generating PLONK proof for circuit: {}", circuit)); output::print_info(&format!("Witness: {}", witness.display())); output::print_info("Computing witness..."); @@ -240,7 +248,11 @@ pub async fn handle( Ok(()) } - ZkCommands::ProveStark { circuit, witness, output } => { + ZkCommands::ProveStark { + circuit, + witness, + output, + } => { output::print_info(&format!("Generating STARK proof for circuit: {}", circuit)); output::print_info(&format!("Witness: {}", witness.display())); output::print_info("Computing execution trace..."); @@ -256,7 +268,11 @@ pub async fn handle( Ok(()) } - ZkCommands::Verify { proof, verification_key: _, public_inputs: _ } => { + ZkCommands::Verify { + proof, + verification_key: _, + public_inputs: _, + } => { output::print_info(&format!("Verifying proof: {}", proof.display())); output::print_info("Loading proof..."); output::print_info("Verifying..."); @@ -265,8 +281,15 @@ pub async fn handle( Ok(()) } - ZkCommands::Setup { circuit, system, output } => { - output::print_info(&format!("Generating {} keys for circuit: {}", system, circuit)); + ZkCommands::Setup { + circuit, + system, + output, + } => { + output::print_info(&format!( + "Generating {} keys for circuit: {}", + system, circuit + )); output::print_info("This may take a while for large circuits..."); output::print_info("Generating proving key..."); output::print_info("Deriving verification key..."); diff --git a/apps/cli/src/main.rs b/apps/cli/src/main.rs index 48efbf8..e296286 100644 --- a/apps/cli/src/main.rs +++ b/apps/cli/src/main.rs @@ -469,7 +469,11 @@ enum DeployCommands { output: Option, /// Hosting gateway URL - #[arg(long, env = "SYNOR_HOSTING_URL", default_value = "http://127.0.0.1:8280")] + #[arg( + long, + env = "SYNOR_HOSTING_URL", + default_value = "http://127.0.0.1:8280" + )] gateway: String, /// Skip running the build command @@ -495,7 +499,11 @@ enum DeployCommands { /// List deployments List { /// Hosting gateway URL - #[arg(long, env = "SYNOR_HOSTING_URL", default_value = "http://127.0.0.1:8280")] + #[arg( + long, + env = "SYNOR_HOSTING_URL", + default_value = "http://127.0.0.1:8280" + )] gateway: String, }, @@ -505,7 +513,11 @@ enum DeployCommands { name: String, /// Hosting gateway URL - #[arg(long, env = "SYNOR_HOSTING_URL", default_value = "http://127.0.0.1:8280")] + #[arg( + long, + env = "SYNOR_HOSTING_URL", + default_value = "http://127.0.0.1:8280" + )] gateway: String, }, @@ -515,7 +527,11 @@ enum DeployCommands { name: String, /// Hosting gateway URL - #[arg(long, env = "SYNOR_HOSTING_URL", default_value = "http://127.0.0.1:8280")] + #[arg( + long, + env = "SYNOR_HOSTING_URL", + default_value = "http://127.0.0.1:8280" + )] gateway: String, }, } @@ -591,9 +607,11 @@ async fn main() { gateway, skip_build, } => commands::deploy::deploy(name, out_dir, &gateway, skip_build, output).await, - DeployCommands::Init { name, spa, output: out_dir } => { - commands::deploy::init(name, spa, out_dir, output) - } + DeployCommands::Init { + name, + spa, + output: out_dir, + } => commands::deploy::init(name, spa, out_dir, output), DeployCommands::List { gateway } => commands::deploy::list(&gateway, output).await, DeployCommands::Delete { name, gateway } => { commands::deploy::delete(&name, &gateway, output).await diff --git a/apps/explorer/src/main.rs b/apps/explorer/src/main.rs index 257889f..8414d6f 100644 --- a/apps/explorer/src/main.rs +++ b/apps/explorer/src/main.rs @@ -676,7 +676,9 @@ async fn get_blocks( } // Fetch blocks by blue score (most recent first) - let start_score = score.blue_score.saturating_sub((params.page.saturating_sub(1) * limit) as u64); + let start_score = score + .blue_score + .saturating_sub((params.page.saturating_sub(1) * limit) as u64); let blocks_data: Vec = state .rpc_call("synor_getBlocksByBlueScore", (start_score, true)) .await @@ -697,17 +699,28 @@ async fn get_blocks( parent_hashes: header .get("parents") .and_then(|p| p.as_array()) - .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect()) + .map(|a| { + a.iter() + .filter_map(|v| v.as_str().map(String::from)) + .collect() + }) .unwrap_or_default(), timestamp, timestamp_human: format_timestamp(timestamp), bits: header.get("bits")?.as_u64()? as u32, nonce: header.get("nonce")?.as_u64()?, - daa_score: header.get("blueScore").and_then(|v| v.as_u64()).unwrap_or(0), - blue_score: header.get("blueScore").and_then(|v| v.as_u64()).unwrap_or(0), + daa_score: header + .get("blueScore") + .and_then(|v| v.as_u64()) + .unwrap_or(0), + blue_score: header + .get("blueScore") + .and_then(|v| v.as_u64()) + .unwrap_or(0), blue_work: String::new(), difficulty: 0.0, - transaction_count: b.get("transactions") + transaction_count: b + .get("transactions") .and_then(|t| t.as_array()) .map(|a| a.len()) .unwrap_or(0), @@ -1102,9 +1115,7 @@ async fn estimate_gas( }; // Call the node's contract_estimateGas RPC method - let gas_used: u64 = state - .rpc_call("contract_estimateGas", rpc_request) - .await?; + let gas_used: u64 = state.rpc_call("contract_estimateGas", rpc_request).await?; // Calculate recommended gas limit with 20% safety margin let gas_limit_recommended = ((gas_used as f64) * 1.2).ceil() as u64; @@ -1494,8 +1505,7 @@ async fn main() -> anyhow::Result<()> { let app = if let Some(ref static_dir) = config.static_dir { // Serve static files with SPA fallback (index.html for client-side routing) let index_path = format!("{}/index.html", static_dir); - let serve_dir = ServeDir::new(static_dir) - .not_found_service(ServeFile::new(&index_path)); + let serve_dir = ServeDir::new(static_dir).not_found_service(ServeFile::new(&index_path)); api_router .fallback_service(serve_dir) diff --git a/apps/synord/src/config.rs b/apps/synord/src/config.rs index da07285..d2107ef 100644 --- a/apps/synord/src/config.rs +++ b/apps/synord/src/config.rs @@ -684,10 +684,12 @@ mod tests { #[test] fn test_all_paths_are_distinct() { let config = NodeConfig::for_network("mainnet").unwrap(); - let paths = [config.blocks_path(), + let paths = [ + config.blocks_path(), config.chainstate_path(), config.contracts_path(), - config.keys_path()]; + config.keys_path(), + ]; for i in 0..paths.len() { for j in (i + 1)..paths.len() { @@ -794,9 +796,11 @@ mod tests { #[test] fn test_with_mining_enabled() { - let config = NodeConfig::for_network("mainnet") - .unwrap() - .with_mining(true, Some("synor:test_address".to_string()), 4); + let config = NodeConfig::for_network("mainnet").unwrap().with_mining( + true, + Some("synor:test_address".to_string()), + 4, + ); assert!(config.mining.enabled); assert_eq!( @@ -828,9 +832,10 @@ mod tests { #[test] fn test_with_p2p() { let seeds = vec!["seed1.example.com:30303".to_string()]; - let config = NodeConfig::for_network("mainnet") - .unwrap() - .with_p2p("0.0.0.0", 30303, seeds.clone()); + let config = + NodeConfig::for_network("mainnet") + .unwrap() + .with_p2p("0.0.0.0", 30303, seeds.clone()); assert_eq!(config.p2p.listen_addr, "0.0.0.0:30303"); assert_eq!(config.p2p.seeds, seeds); @@ -1027,7 +1032,10 @@ mod tests { let loaded = NodeConfig::load(&path).unwrap(); assert_eq!(loaded.mining.enabled, config.mining.enabled); - assert_eq!(loaded.mining.coinbase_address, config.mining.coinbase_address); + assert_eq!( + loaded.mining.coinbase_address, + config.mining.coinbase_address + ); assert_eq!(loaded.mining.threads, config.mining.threads); assert_eq!(loaded.storage.cache_size_mb, config.storage.cache_size_mb); assert_eq!(loaded.logging.level, config.logging.level); diff --git a/apps/synord/src/node.rs b/apps/synord/src/node.rs index ba4122f..a352d16 100644 --- a/apps/synord/src/node.rs +++ b/apps/synord/src/node.rs @@ -425,11 +425,13 @@ mod tests { #[test] fn test_node_state_all_variants_are_distinct() { - let states = [NodeState::Starting, + let states = [ + NodeState::Starting, NodeState::Syncing, NodeState::Running, NodeState::Stopping, - NodeState::Stopped]; + NodeState::Stopped, + ]; for i in 0..states.len() { for j in (i + 1)..states.len() { @@ -605,7 +607,10 @@ mod tests { .with_mining(true, Some("synor:test".to_string()), 4); assert!(config.mining.enabled); - assert_eq!(config.mining.coinbase_address, Some("synor:test".to_string())); + assert_eq!( + config.mining.coinbase_address, + Some("synor:test".to_string()) + ); assert_eq!(config.mining.threads, 4); } diff --git a/apps/synord/src/services/miner.rs b/apps/synord/src/services/miner.rs index 7108967..7957332 100644 --- a/apps/synord/src/services/miner.rs +++ b/apps/synord/src/services/miner.rs @@ -12,9 +12,12 @@ use synor_mining::{ MinerCommand, MinerConfig, MinerEvent, MiningResult, MiningStats as CrateMiningStats, TemplateTransaction, }; -use synor_types::{Address, Amount, Block, BlockHeader, BlockId, BlueScore, Hash256, Network, Timestamp, Transaction, TxOutput}; use synor_types::block::BlockBody; use synor_types::transaction::ScriptPubKey; +use synor_types::{ + Address, Amount, Block, BlockHeader, BlockId, BlueScore, Hash256, Network, Timestamp, + Transaction, TxOutput, +}; use crate::config::NodeConfig; use crate::services::{ConsensusService, MempoolService}; @@ -473,10 +476,7 @@ impl MinerService { extra_data.extend_from_slice(&result.nonce.to_le_bytes()); extra_data.extend_from_slice(&template.coinbase_data.extra_data); - let coinbase_tx = Transaction::coinbase( - vec![coinbase_output], - extra_data, - ); + let coinbase_tx = Transaction::coinbase(vec![coinbase_output], extra_data); // Start with coinbase transaction let mut transactions = vec![coinbase_tx]; @@ -522,8 +522,7 @@ impl MinerService { let block = Block { header, body }; // Serialize with Borsh - borsh::to_vec(&block) - .map_err(|e| anyhow::anyhow!("Failed to serialize block: {}", e)) + borsh::to_vec(&block).map_err(|e| anyhow::anyhow!("Failed to serialize block: {}", e)) } /// Submits a mined block (for external submission via RPC). diff --git a/apps/synord/tests/byzantine_fault_tests.rs b/apps/synord/tests/byzantine_fault_tests.rs index 1964f9a..ceede02 100644 --- a/apps/synord/tests/byzantine_fault_tests.rs +++ b/apps/synord/tests/byzantine_fault_tests.rs @@ -234,7 +234,10 @@ mod network_partition_tests { // Node 0 should have fewer peers after isolation let isolated_peers = network.nodes[0].network().peer_count().await; - info!(isolated_peers = isolated_peers, "Node 0 peers after isolation"); + info!( + isolated_peers = isolated_peers, + "Node 0 peers after isolation" + ); assert!( isolated_peers < initial_peer_counts[0] || initial_peer_counts[0] == 0, @@ -271,7 +274,10 @@ mod network_partition_tests { // After healing, nodes should have peers let total_peers = network.total_peer_count().await; - info!(total_peers = total_peers, "Total peers after partition recovery"); + info!( + total_peers = total_peers, + "Total peers after partition recovery" + ); // Consensus state should converge let consensus0 = network.nodes[0].consensus(); @@ -287,7 +293,10 @@ mod network_partition_tests { ); // Both should have some consensus state - assert!(vsp0.is_some() || vsp1.is_some(), "At least one node should have VSP"); + assert!( + vsp0.is_some() || vsp1.is_some(), + "At least one node should have VSP" + ); network.stop_all().await.unwrap(); } @@ -351,10 +360,12 @@ mod network_partition_tests { // Record blue scores from each partition let scores_before: Vec = futures::future::join_all( - network.nodes.iter().map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; info!(scores_before = ?scores_before, "Blue scores before healing"); @@ -368,10 +379,12 @@ mod network_partition_tests { // Blue scores should converge let scores_after: Vec = futures::future::join_all( - network.nodes.iter().map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; info!(scores_after = ?scores_after, "Blue scores after healing"); @@ -380,7 +393,9 @@ mod network_partition_tests { assert!( after >= before, "Node {} blue score should not decrease: {} -> {}", - i, before, after + i, + before, + after ); } @@ -407,10 +422,7 @@ mod double_spend_tests { let mempool = network.nodes[0].mempool(); let initial_size = mempool.size().await; - info!( - initial_mempool_size = initial_size, - "Initial mempool state" - ); + info!(initial_mempool_size = initial_size, "Initial mempool state"); // In production, we would: // 1. Create two transactions spending the same UTXO @@ -420,7 +432,7 @@ mod double_spend_tests { // For now, verify mempool API is working // and handles empty/invalid data gracefully let _invalid_tx = vec![0u8; 50]; // Invalid transaction bytes (for future use) - // Submitting invalid tx should fail gracefully + // Submitting invalid tx should fail gracefully // Mempool should maintain integrity let final_size = mempool.size().await; @@ -653,7 +665,11 @@ mod invalid_block_rejection_tests { // All valid tips should have known parents in the DAG for tip in &tips { - let has_parents = consensus.get_block_info(tip).await.map(|info| !info.parents.is_empty()).unwrap_or(false); + let has_parents = consensus + .get_block_info(tip) + .await + .map(|info| !info.parents.is_empty()) + .unwrap_or(false); info!( block = hex::encode(&tip[..8]), has_parents = has_parents, @@ -687,16 +703,22 @@ mod sybil_attack_tests { // Track blue scores - honest nodes should maintain correct view let honest_scores: Vec = futures::future::join_all( - network.nodes.iter().take(3).map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .take(3) + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; let sybil_scores: Vec = futures::future::join_all( - network.nodes.iter().skip(3).map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .skip(3) + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; info!( honest_scores = ?honest_scores, @@ -805,7 +827,10 @@ mod eclipse_attack_tests { sleep(Duration::from_secs(1)).await; let after_eclipse_peers = victim_network.peer_count().await; - info!(after_eclipse_peers = after_eclipse_peers, "Peers after eclipse attempt"); + info!( + after_eclipse_peers = after_eclipse_peers, + "Peers after eclipse attempt" + ); // In a real implementation, the node would: // 1. Detect low peer diversity @@ -863,7 +888,10 @@ mod eclipse_attack_tests { sleep(Duration::from_secs(1)).await; let eclipsed_peers = network.nodes[0].network().peer_count().await; - info!(eclipsed_peers = eclipsed_peers, "Node 0 peers during eclipse"); + info!( + eclipsed_peers = eclipsed_peers, + "Node 0 peers during eclipse" + ); // Manually reconnect (simulating recovery mechanism) network.connect_nodes(0, 1).await.unwrap(); @@ -871,7 +899,10 @@ mod eclipse_attack_tests { sleep(Duration::from_secs(2)).await; let recovered_peers = network.nodes[0].network().peer_count().await; - info!(recovered_peers = recovered_peers, "Node 0 peers after recovery"); + info!( + recovered_peers = recovered_peers, + "Node 0 peers after recovery" + ); // Should have reconnected assert!( @@ -1038,10 +1069,12 @@ mod dag_reorg_tests { // Record divergent states let states_before: Vec = futures::future::join_all( - network.nodes.iter().map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; info!(states_before = ?states_before, "States before reconnection"); @@ -1052,10 +1085,12 @@ mod dag_reorg_tests { // Get converged states let states_after: Vec = futures::future::join_all( - network.nodes.iter().map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; info!(states_after = ?states_after, "States after reconnection"); @@ -1064,7 +1099,9 @@ mod dag_reorg_tests { assert!( after >= before, "Node {} blue score regression: {} -> {}", - i, before, after + i, + before, + after ); } @@ -1192,10 +1229,12 @@ mod parallel_blocks_tests { // Collect blue scores from all nodes let blue_scores: Vec = futures::future::join_all( - network.nodes.iter().map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; info!(blue_scores = ?blue_scores, "Blue scores across nodes"); @@ -1206,7 +1245,8 @@ mod parallel_blocks_tests { assert!( max_score - min_score <= 2, "Blue scores should be consistent: {} - {} > 2", - max_score, min_score + max_score, + min_score ); network.stop_all().await.unwrap(); @@ -1264,10 +1304,12 @@ mod parallel_blocks_tests { // Get selected chains from all nodes let chains: Vec> = futures::future::join_all( - network.nodes.iter().map(|n| async { - n.consensus().get_selected_chain(10).await - }) - ).await; + network + .nodes + .iter() + .map(|n| async { n.consensus().get_selected_chain(10).await }), + ) + .await; info!( chain_lengths = ?chains.iter().map(|c| c.len()).collect::>(), @@ -1276,7 +1318,8 @@ mod parallel_blocks_tests { // All nodes should have the same selected chain (after sync) // Check that genesis (first block) matches - let genesis_blocks: Vec<_> = chains.iter() + let genesis_blocks: Vec<_> = chains + .iter() .filter(|c| !c.is_empty()) .map(|c| c[0]) .collect(); @@ -1353,10 +1396,13 @@ mod bft_threshold_tests { // Honest nodes (0, 1, 2) should maintain consensus let honest_scores: Vec = futures::future::join_all( - network.nodes.iter().take(3).map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .take(3) + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; info!(honest_scores = ?honest_scores, "Honest node blue scores"); @@ -1399,10 +1445,7 @@ mod bft_threshold_tests { // Blue score should not decrease let final_blue = network.nodes[0].consensus().current_blue_score().await; - assert!( - final_blue >= initial_blue, - "Blue score should not decrease" - ); + assert!(final_blue >= initial_blue, "Blue score should not decrease"); // Stop remaining nodes for node in network.nodes.iter().take(3) { @@ -1615,10 +1658,12 @@ mod integration_tests { // Record initial state let initial_scores: Vec = futures::future::join_all( - network.nodes.iter().map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; info!(initial_scores = ?initial_scores, "Initial blue scores"); info!("Phase 2: Simulate 2 Byzantine nodes (partition)"); @@ -1640,18 +1685,24 @@ mod integration_tests { info!("Phase 4: Verify convergence"); let final_scores: Vec = futures::future::join_all( - network.nodes.iter().map(|n| async { - n.consensus().current_blue_score().await - }) - ).await; + network + .nodes + .iter() + .map(|n| async { n.consensus().current_blue_score().await }), + ) + .await; info!(final_scores = ?final_scores, "Final blue scores"); // All nodes should have non-decreasing blue scores - for (i, (&initial, &final_score)) in initial_scores.iter().zip(final_scores.iter()).enumerate() { + for (i, (&initial, &final_score)) in + initial_scores.iter().zip(final_scores.iter()).enumerate() + { assert!( final_score >= initial, "Node {} score regression: {} -> {}", - i, initial, final_score + i, + initial, + final_score ); } diff --git a/crates/synor-bridge/src/ethereum.rs b/crates/synor-bridge/src/ethereum.rs index 5509edb..bf2cbfa 100644 --- a/crates/synor-bridge/src/ethereum.rs +++ b/crates/synor-bridge/src/ethereum.rs @@ -17,8 +17,8 @@ //! 3. Vault contract verifies proof and unlocks original tokens use crate::{ - AssetId, Bridge, BridgeAddress, BridgeError, BridgeResult, BridgeTransfer, ChainType, TransferId, TransferManager, TransferStatus, VaultManager, - ETH_MIN_CONFIRMATIONS, + AssetId, Bridge, BridgeAddress, BridgeError, BridgeResult, BridgeTransfer, ChainType, + TransferId, TransferManager, TransferStatus, VaultManager, ETH_MIN_CONFIRMATIONS, }; use alloy_primitives::{Address, B256, U256}; use alloy_sol_types::sol; @@ -281,9 +281,9 @@ impl EthereumBridge { // Check for replay let event_hash = event.hash(); if self.processed_events.read().contains_key(&event_hash) { - return Err(BridgeError::TransferAlreadyExists( - hex::encode(event_hash.as_slice()), - )); + return Err(BridgeError::TransferAlreadyExists(hex::encode( + event_hash.as_slice(), + ))); } // Verify token is supported @@ -393,18 +393,15 @@ impl EthereumBridge { // Collect matching transfer IDs first let matching_transfer_id = { let transfers = self.transfers.read(); - transfers - .pending_transfers() - .iter() - .find_map(|transfer| { - transfer.source_tx_hash.as_ref().and_then(|tx_hash| { - if tx_hash.as_slice() == event_hash.as_slice() { - Some(transfer.id.clone()) - } else { - None - } - }) + transfers.pending_transfers().iter().find_map(|transfer| { + transfer.source_tx_hash.as_ref().and_then(|tx_hash| { + if tx_hash.as_slice() == event_hash.as_slice() { + Some(transfer.id.clone()) + } else { + None + } }) + }) }; // Now update the transfer if found @@ -457,7 +454,9 @@ impl EthereumBridge { .map_err(|e| BridgeError::InvalidAddress(e.to_string()))?; if bytes.len() != 20 { - return Err(BridgeError::InvalidAddress("invalid address length".to_string())); + return Err(BridgeError::InvalidAddress( + "invalid address length".to_string(), + )); } Address::from_slice(&bytes) }; @@ -801,7 +800,10 @@ mod tests { wrapped.mint(1000); let result = wrapped.burn(1500); - assert!(matches!(result, Err(BridgeError::InsufficientBalance { .. }))); + assert!(matches!( + result, + Err(BridgeError::InsufficientBalance { .. }) + )); } #[test] @@ -896,7 +898,9 @@ mod tests { let current_time = 1700000000; let event = create_lock_event(0); - bridge.process_lock_event(event.clone(), current_time).unwrap(); + bridge + .process_lock_event(event.clone(), current_time) + .unwrap(); let result = bridge.process_lock_event(event, current_time + 100); assert!(matches!(result, Err(BridgeError::TransferAlreadyExists(_)))); @@ -949,8 +953,12 @@ mod tests { let event_hash = B256::from([0x11; 32]); let unauthorized_relayer = Address::from([0x99; 20]); - let result = bridge.submit_relayer_signature(event_hash, unauthorized_relayer, vec![0x00; 65]); - assert!(matches!(result, Err(BridgeError::SignatureVerificationFailed(_)))); + let result = + bridge.submit_relayer_signature(event_hash, unauthorized_relayer, vec![0x00; 65]); + assert!(matches!( + result, + Err(BridgeError::SignatureVerificationFailed(_)) + )); } #[test] @@ -964,7 +972,9 @@ mod tests { }); let event_hash = B256::from([0x11; 32]); - let result = bridge.submit_relayer_signature(event_hash, relayer, vec![0x00; 65]).unwrap(); + let result = bridge + .submit_relayer_signature(event_hash, relayer, vec![0x00; 65]) + .unwrap(); assert!(result); } @@ -983,7 +993,9 @@ mod tests { .update_confirmations(&transfer_id, 12, current_time + 100) .unwrap(); - bridge.mint_wrapped_tokens(&transfer_id, current_time + 200).unwrap(); + bridge + .mint_wrapped_tokens(&transfer_id, current_time + 200) + .unwrap(); let wrapped = bridge.get_wrapped_token(Address::ZERO).unwrap(); assert_eq!(wrapped.total_supply, 1000); @@ -1022,13 +1034,7 @@ mod tests { let asset = AssetId::wrapped(&AssetId::eth()); let transfer_id = bridge - .initiate_burn( - asset, - 1000, - test_recipient(), - test_sender(), - current_time, - ) + .initiate_burn(asset, 1000, test_recipient(), test_sender(), current_time) .unwrap(); let transfers = bridge.transfers.read(); @@ -1053,15 +1059,13 @@ mod tests { drop(wrapped_tokens); let asset = AssetId::wrapped(&AssetId::eth()); - let result = bridge.initiate_burn( - asset, - 1000, - test_recipient(), - test_sender(), - current_time, - ); + let result = + bridge.initiate_burn(asset, 1000, test_recipient(), test_sender(), current_time); - assert!(matches!(result, Err(BridgeError::InsufficientBalance { .. }))); + assert!(matches!( + result, + Err(BridgeError::InsufficientBalance { .. }) + )); } #[test] @@ -1069,13 +1073,7 @@ mod tests { let bridge = EthereumBridge::new(EthereumBridgeConfig::default()); let asset = AssetId::wrapped(&AssetId::eth()); - let result = bridge.initiate_burn( - asset, - 1000, - test_recipient(), - test_sender(), - 0, - ); + let result = bridge.initiate_burn(asset, 1000, test_recipient(), test_sender(), 0); assert!(matches!(result, Err(BridgeError::AssetNotSupported(_)))); } diff --git a/crates/synor-bridge/src/lib.rs b/crates/synor-bridge/src/lib.rs index 6d1b0c2..0470e15 100644 --- a/crates/synor-bridge/src/lib.rs +++ b/crates/synor-bridge/src/lib.rs @@ -79,7 +79,9 @@ pub const ETH_MIN_CONFIRMATIONS: u64 = 12; pub const BTC_MIN_CONFIRMATIONS: u64 = 6; /// Bridge chain identifier -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub enum ChainType { /// Synor native chain Synor, @@ -128,7 +130,9 @@ impl fmt::Display for ChainType { } /// Asset identifier across chains -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub struct AssetId { /// Chain where the asset originates pub chain: ChainType, @@ -199,7 +203,9 @@ impl fmt::Display for AssetId { } /// Bridge address (unified format for cross-chain addresses) -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub struct BridgeAddress { /// Chain type pub chain: ChainType, diff --git a/crates/synor-bridge/src/transfer.rs b/crates/synor-bridge/src/transfer.rs index e8ce011..7dccda9 100644 --- a/crates/synor-bridge/src/transfer.rs +++ b/crates/synor-bridge/src/transfer.rs @@ -14,7 +14,9 @@ use std::collections::HashMap; use std::fmt; /// Unique transfer identifier -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub struct TransferId(pub String); impl TransferId { @@ -48,7 +50,9 @@ impl fmt::Display for TransferId { } /// Transfer direction -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub enum TransferDirection { /// From external chain to Synor (Lock → Mint) Inbound, @@ -66,7 +70,9 @@ impl fmt::Display for TransferDirection { } /// Transfer status -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub enum TransferStatus { /// Transfer initiated, awaiting lock confirmation Pending, @@ -1030,7 +1036,10 @@ mod tests { transfer.fail("Proof verification failed", current_time + 50); assert_eq!(transfer.status, TransferStatus::Failed); - assert_eq!(transfer.error, Some("Proof verification failed".to_string())); + assert_eq!( + transfer.error, + Some("Proof verification failed".to_string()) + ); } #[test] @@ -1331,8 +1340,12 @@ mod tests { assert_eq!(manager.pending_transfers().len(), 1); - manager.confirm_lock(&id, vec![0x11; 32], 100, current_time + 10).unwrap(); - manager.update_confirmations(&id, 12, current_time + 120).unwrap(); + manager + .confirm_lock(&id, vec![0x11; 32], 100, current_time + 10) + .unwrap(); + manager + .update_confirmations(&id, 12, current_time + 120) + .unwrap(); assert_eq!(manager.pending_transfers().len(), 0); } @@ -1356,8 +1369,12 @@ mod tests { assert_eq!(manager.ready_for_confirmation().len(), 0); - manager.confirm_lock(&id, vec![0x11; 32], 100, current_time + 10).unwrap(); - manager.update_confirmations(&id, 12, current_time + 120).unwrap(); + manager + .confirm_lock(&id, vec![0x11; 32], 100, current_time + 10) + .unwrap(); + manager + .update_confirmations(&id, 12, current_time + 120) + .unwrap(); assert_eq!(manager.ready_for_confirmation().len(), 1); } @@ -1410,7 +1427,9 @@ mod tests { ) .unwrap(); - manager.fail_transfer(&id, "Verification failed", current_time + 50).unwrap(); + manager + .fail_transfer(&id, "Verification failed", current_time + 50) + .unwrap(); let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Failed); @@ -1452,9 +1471,15 @@ mod tests { ) .unwrap(); - manager.confirm_lock(&id1, vec![0x11; 32], 100, current_time).unwrap(); - manager.update_confirmations(&id1, 12, current_time).unwrap(); - manager.confirm_mint(&id1, vec![0x22; 32], current_time).unwrap(); + manager + .confirm_lock(&id1, vec![0x11; 32], 100, current_time) + .unwrap(); + manager + .update_confirmations(&id1, 12, current_time) + .unwrap(); + manager + .confirm_mint(&id1, vec![0x22; 32], current_time) + .unwrap(); let stats = manager.stats(); assert_eq!(stats.total_count, 2); @@ -1484,19 +1509,27 @@ mod tests { let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Pending); - manager.confirm_lock(&id, vec![0x11; 32], 100, current_time + 60).unwrap(); + manager + .confirm_lock(&id, vec![0x11; 32], 100, current_time + 60) + .unwrap(); let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Locked); - manager.update_confirmations(&id, 6, current_time + 120).unwrap(); + manager + .update_confirmations(&id, 6, current_time + 120) + .unwrap(); let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Locked); - manager.update_confirmations(&id, 12, current_time + 180).unwrap(); + manager + .update_confirmations(&id, 12, current_time + 180) + .unwrap(); let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Confirmed); - manager.confirm_mint(&id, vec![0x22; 32], current_time + 240).unwrap(); + manager + .confirm_mint(&id, vec![0x22; 32], current_time + 240) + .unwrap(); let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Completed); } @@ -1521,15 +1554,21 @@ mod tests { let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Pending); - manager.confirm_lock(&id, vec![0x11; 32], 100, current_time + 60).unwrap(); + manager + .confirm_lock(&id, vec![0x11; 32], 100, current_time + 60) + .unwrap(); let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Locked); - manager.update_confirmations(&id, 6, current_time + 120).unwrap(); + manager + .update_confirmations(&id, 6, current_time + 120) + .unwrap(); let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Confirmed); - manager.confirm_unlock(&id, vec![0x33; 32], current_time + 180).unwrap(); + manager + .confirm_unlock(&id, vec![0x33; 32], current_time + 180) + .unwrap(); let transfer = manager.get(&id).unwrap(); assert_eq!(transfer.status, TransferStatus::Completed); } diff --git a/crates/synor-bridge/src/vault.rs b/crates/synor-bridge/src/vault.rs index 5b8060b..14c1194 100644 --- a/crates/synor-bridge/src/vault.rs +++ b/crates/synor-bridge/src/vault.rs @@ -12,7 +12,9 @@ use std::collections::HashMap; use std::fmt; /// Unique vault identifier -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub struct VaultId(pub String); impl VaultId { @@ -198,13 +200,7 @@ impl Vault { return Err(BridgeError::TransferAlreadyExists(lock_id)); } - let locked = LockedAsset::new( - self.asset.clone(), - amount, - owner, - recipient, - current_time, - ); + let locked = LockedAsset::new(self.asset.clone(), amount, owner, recipient, current_time); self.locked_assets.insert(lock_id, locked); self.total_locked += amount; @@ -283,7 +279,10 @@ impl Vault { } /// Get expired locked assets - pub fn expired_locked(&self, current_time: u64) -> impl Iterator { + pub fn expired_locked( + &self, + current_time: u64, + ) -> impl Iterator { self.locked_assets .iter() .filter(move |(_, l)| !l.released && l.is_expired(current_time)) @@ -511,14 +510,8 @@ mod tests { #[test] fn test_locked_asset_expiry() { - let locked = LockedAsset::new( - AssetId::eth(), - 1000, - test_owner(), - test_recipient(), - 1000, - ) - .with_expiry(2000); + let locked = LockedAsset::new(AssetId::eth(), 1000, test_owner(), test_recipient(), 1000) + .with_expiry(2000); assert!(!locked.is_expired(1500)); assert!(locked.is_expired(2000)); @@ -624,11 +617,7 @@ mod tests { #[test] fn test_lock_unlock() { - let mut vault = Vault::new( - VaultId::new("test"), - ChainType::Ethereum, - AssetId::eth(), - ); + let mut vault = Vault::new(VaultId::new("test"), ChainType::Ethereum, AssetId::eth()); let current_time = 1700000000; @@ -654,20 +643,28 @@ mod tests { ); let current_time = 1700000000; - vault.lock("lock-1", 1000, test_owner(), test_recipient(), current_time).unwrap(); - vault.lock("lock-2", 2000, test_owner(), test_recipient(), current_time).unwrap(); - vault.lock("lock-3", 500, test_owner_alt(), test_recipient(), current_time).unwrap(); + vault + .lock("lock-1", 1000, test_owner(), test_recipient(), current_time) + .unwrap(); + vault + .lock("lock-2", 2000, test_owner(), test_recipient(), current_time) + .unwrap(); + vault + .lock( + "lock-3", + 500, + test_owner_alt(), + test_recipient(), + current_time, + ) + .unwrap(); assert_eq!(vault.total_locked, 3500); } #[test] fn test_duplicate_lock() { - let mut vault = Vault::new( - VaultId::new("test"), - ChainType::Ethereum, - AssetId::eth(), - ); + let mut vault = Vault::new(VaultId::new("test"), ChainType::Ethereum, AssetId::eth()); vault .lock("lock1", 1000, test_owner(), test_recipient(), 0) @@ -697,20 +694,21 @@ mod tests { AssetId::eth(), ); - vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap(); + vault + .lock("lock-1", 1000, test_owner(), test_recipient(), 0) + .unwrap(); vault.unlock("lock-1").unwrap(); let result = vault.unlock("lock-1"); - assert!(matches!(result, Err(BridgeError::TransferAlreadyCompleted(_)))); + assert!(matches!( + result, + Err(BridgeError::TransferAlreadyCompleted(_)) + )); } #[test] fn test_vault_pause() { - let mut vault = Vault::new( - VaultId::new("test"), - ChainType::Ethereum, - AssetId::eth(), - ); + let mut vault = Vault::new(VaultId::new("test"), ChainType::Ethereum, AssetId::eth()); vault.pause(); @@ -730,7 +728,9 @@ mod tests { vault.resume(); assert_eq!(vault.state, VaultState::Active); - vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap(); + vault + .lock("lock-1", 1000, test_owner(), test_recipient(), 0) + .unwrap(); } #[test] @@ -750,12 +750,8 @@ mod tests { #[test] fn test_daily_limit() { - let mut vault = Vault::new( - VaultId::new("test"), - ChainType::Ethereum, - AssetId::eth(), - ) - .with_daily_limit(1000); + let mut vault = Vault::new(VaultId::new("test"), ChainType::Ethereum, AssetId::eth()) + .with_daily_limit(1000); let current_time = 86400 * 100; @@ -781,8 +777,24 @@ mod tests { ); let current_time = 0; - vault.lock("lock-1", 1000000000, test_owner(), test_recipient(), current_time).unwrap(); - vault.lock("lock-2", 1000000000, test_owner(), test_recipient(), current_time).unwrap(); + vault + .lock( + "lock-1", + 1000000000, + test_owner(), + test_recipient(), + current_time, + ) + .unwrap(); + vault + .lock( + "lock-2", + 1000000000, + test_owner(), + test_recipient(), + current_time, + ) + .unwrap(); assert_eq!(vault.total_locked, 2000000000); } @@ -795,7 +807,9 @@ mod tests { AssetId::eth(), ); - vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap(); + vault + .lock("lock-1", 1000, test_owner(), test_recipient(), 0) + .unwrap(); assert!(vault.get_locked("lock-1").is_some()); assert!(vault.get_locked("nonexistent").is_none()); @@ -809,8 +823,12 @@ mod tests { AssetId::eth(), ); - vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap(); - vault.lock("lock-2", 2000, test_owner(), test_recipient(), 0).unwrap(); + vault + .lock("lock-1", 1000, test_owner(), test_recipient(), 0) + .unwrap(); + vault + .lock("lock-2", 2000, test_owner(), test_recipient(), 0) + .unwrap(); let all: Vec<_> = vault.all_locked().collect(); assert_eq!(all.len(), 2); @@ -824,8 +842,12 @@ mod tests { AssetId::eth(), ); - vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap(); - vault.lock("lock-2", 2000, test_owner(), test_recipient(), 0).unwrap(); + vault + .lock("lock-1", 1000, test_owner(), test_recipient(), 0) + .unwrap(); + vault + .lock("lock-2", 2000, test_owner(), test_recipient(), 0) + .unwrap(); vault.unlock("lock-1").unwrap(); let active: Vec<_> = vault.active_locked().collect(); @@ -858,7 +880,9 @@ mod tests { assert!(manager.find_vault(&ChainType::Ethereum, ð).is_some()); let vault = manager.get_or_create_vault(ChainType::Ethereum, eth.clone()); - vault.lock("lock1", 100, test_owner(), test_recipient(), 0).unwrap(); + vault + .lock("lock1", 100, test_owner(), test_recipient(), 0) + .unwrap(); assert_eq!(manager.total_locked(), 100); } @@ -881,7 +905,9 @@ mod tests { { let vault = manager.get_vault_mut(&vault_id).unwrap(); - vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap(); + vault + .lock("lock-1", 1000, test_owner(), test_recipient(), 0) + .unwrap(); } let vault = manager.get_vault(&vault_id).unwrap(); @@ -902,7 +928,9 @@ mod tests { manager.create_vault(ChainType::Ethereum, eth.clone()); let vault = manager.find_vault_mut(&ChainType::Ethereum, ð).unwrap(); - vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap(); + vault + .lock("lock-1", 1000, test_owner(), test_recipient(), 0) + .unwrap(); assert_eq!(manager.total_locked(), 1000); } @@ -913,7 +941,9 @@ mod tests { let eth = AssetId::eth(); let vault = manager.get_or_create_vault(ChainType::Ethereum, eth.clone()); - vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap(); + vault + .lock("lock-1", 1000, test_owner(), test_recipient(), 0) + .unwrap(); assert_eq!(manager.vault_ids().len(), 1); assert_eq!(manager.total_locked(), 1000); diff --git a/crates/synor-compute/src/device/mod.rs b/crates/synor-compute/src/device/mod.rs index 01c98fe..64f656a 100644 --- a/crates/synor-compute/src/device/mod.rs +++ b/crates/synor-compute/src/device/mod.rs @@ -241,7 +241,10 @@ impl DeviceRegistry { } /// Gets a processor by ID. - pub fn get_processor(&self, processor_id: ProcessorId) -> Result, ComputeError> { + pub fn get_processor( + &self, + processor_id: ProcessorId, + ) -> Result, ComputeError> { self.processors .read() .get(&processor_id) @@ -266,7 +269,10 @@ impl DeviceRegistry { /// Gets the next processor ID. pub fn next_processor_id(&self) -> ProcessorId { - ProcessorId(self.next_processor_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst)) + ProcessorId( + self.next_processor_id + .fetch_add(1, std::sync::atomic::Ordering::SeqCst), + ) } /// Gets total number of devices. @@ -309,7 +315,10 @@ impl DeviceRegistry { device.status = status; Ok(()) } else { - Err(ComputeError::Internal(format!("Device not found: {}", device_id))) + Err(ComputeError::Internal(format!( + "Device not found: {}", + device_id + ))) } } } @@ -323,7 +332,7 @@ impl Default for DeviceRegistry { #[cfg(test)] mod tests { use super::*; - use crate::processor::{CpuVariant, AvxSupport}; + use crate::processor::{AvxSupport, CpuVariant}; #[test] fn test_device_id() { diff --git a/crates/synor-compute/src/lib.rs b/crates/synor-compute/src/lib.rs index 4e9c4a2..a7e43cf 100644 --- a/crates/synor-compute/src/lib.rs +++ b/crates/synor-compute/src/lib.rs @@ -67,6 +67,10 @@ pub use market::{ ResourceType, SpotMarket, Trade, }; pub use memory::{MemoryManager, TensorHandle, TransferPath, UnifiedMemory}; +pub use model::{ + ModelCategory, ModelFormat, ModelId, ModelInfo, ModelRegistry, ModelUploadRequest, + ModelUploadResponse, +}; pub use processor::{ ComputeThroughput, CpuVariant, GpuVariant, NpuVariant, Operation, OperationType, Processor, ProcessorCapabilities, ProcessorId, ProcessorType, TpuVersion, @@ -78,10 +82,6 @@ pub use task::{ ComputeTask, DecomposedWorkload, Task, TaskDecomposer, TaskId, TaskPriority, TaskResult, TaskStatus, }; -pub use model::{ - ModelCategory, ModelFormat, ModelId, ModelInfo, ModelRegistry, ModelUploadRequest, - ModelUploadResponse, -}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -434,7 +434,10 @@ impl ComputeCluster { let jobs = self.jobs.read(); let total_nodes = nodes.len(); - let online_nodes = nodes.values().filter(|n| n.status == NodeStatus::Online).count(); + let online_nodes = nodes + .values() + .filter(|n| n.status == NodeStatus::Online) + .count(); let total_gpus: usize = nodes .values() @@ -515,16 +518,16 @@ pub enum GpuTier { impl Default for ComputePricing { fn default() -> Self { let mut gpu_hourly = HashMap::new(); - gpu_hourly.insert(GpuTier::Consumer, 100_000_000); // 0.10 SYNOR - gpu_hourly.insert(GpuTier::Professional, 300_000_000); // 0.30 SYNOR - gpu_hourly.insert(GpuTier::DataCenter, 2_000_000_000); // 2.00 SYNOR - gpu_hourly.insert(GpuTier::Premium, 4_000_000_000); // 4.00 SYNOR + gpu_hourly.insert(GpuTier::Consumer, 100_000_000); // 0.10 SYNOR + gpu_hourly.insert(GpuTier::Professional, 300_000_000); // 0.30 SYNOR + gpu_hourly.insert(GpuTier::DataCenter, 2_000_000_000); // 2.00 SYNOR + gpu_hourly.insert(GpuTier::Premium, 4_000_000_000); // 4.00 SYNOR Self { gpu_hourly, - cpu_core_hour: 20_000_000, // 0.02 SYNOR - memory_gb_hour: 5_000_000, // 0.005 SYNOR - network_egress_gb: 50_000_000, // 0.05 SYNOR + cpu_core_hour: 20_000_000, // 0.02 SYNOR + memory_gb_hour: 5_000_000, // 0.005 SYNOR + network_egress_gb: 50_000_000, // 0.05 SYNOR inference_per_million_tokens: 100_000_000, // 0.10 SYNOR } } diff --git a/crates/synor-compute/src/market/mod.rs b/crates/synor-compute/src/market/mod.rs index d6cd6ce..df2e04a 100644 --- a/crates/synor-compute/src/market/mod.rs +++ b/crates/synor-compute/src/market/mod.rs @@ -686,24 +686,24 @@ impl PricingEngine { pub fn greenest_region(&self) -> &str { self.regions .iter() - .max_by(|a, b| { - a.renewable_pct - .partial_cmp(&b.renewable_pct) - .unwrap() - }) + .max_by(|a, b| a.renewable_pct.partial_cmp(&b.renewable_pct).unwrap()) .map(|r| r.region.as_str()) .unwrap_or("eu-north") } /// Compares price to cloud providers. - pub fn compare_to_cloud(&self, resource: &ResourceType, region: Option<&str>) -> CloudComparison { + pub fn compare_to_cloud( + &self, + resource: &ResourceType, + region: Option<&str>, + ) -> CloudComparison { let our_price = self.spot_price(resource, region); // Approximate cloud provider prices (USD/hour for GPU) let (aws_price, gcp_price, azure_price) = match resource { ResourceType::GpuHours(GpuTier::DataCenter) => (3.06, 2.95, 3.10), // A100 equivalents - ResourceType::GpuHours(GpuTier::Ultra) => (5.00, 4.50, 5.20), // H100 equivalents - ResourceType::GpuHours(GpuTier::High) => (1.50, 1.40, 1.60), // T4/A10 equivalents + ResourceType::GpuHours(GpuTier::Ultra) => (5.00, 4.50, 5.20), // H100 equivalents + ResourceType::GpuHours(GpuTier::High) => (1.50, 1.40, 1.60), // T4/A10 equivalents ResourceType::CpuHours(CpuTier::Server) => (0.40, 0.35, 0.42), _ => (1.0, 1.0, 1.0), }; @@ -888,9 +888,18 @@ impl SpotMarket { ); } - order_books.insert(ResourceType::TpuHours, OrderBook::new(ResourceType::TpuHours)); - order_books.insert(ResourceType::NpuHours, OrderBook::new(ResourceType::NpuHours)); - order_books.insert(ResourceType::LpuCredits, OrderBook::new(ResourceType::LpuCredits)); + order_books.insert( + ResourceType::TpuHours, + OrderBook::new(ResourceType::TpuHours), + ); + order_books.insert( + ResourceType::NpuHours, + OrderBook::new(ResourceType::NpuHours), + ); + order_books.insert( + ResourceType::LpuCredits, + OrderBook::new(ResourceType::LpuCredits), + ); Self { order_books, @@ -1074,12 +1083,21 @@ mod tests { fn test_pricing_engine() { let engine = PricingEngine::new(); - let price = engine.spot_price(&ResourceType::GpuHours(GpuTier::DataCenter), Some("eu-north")); + let price = engine.spot_price( + &ResourceType::GpuHours(GpuTier::DataCenter), + Some("eu-north"), + ); assert!(price > 0.0); // eu-north should be cheaper (low electricity cost) - let eu_price = engine.spot_price(&ResourceType::GpuHours(GpuTier::DataCenter), Some("eu-north")); - let eu_west_price = engine.spot_price(&ResourceType::GpuHours(GpuTier::DataCenter), Some("eu-west")); + let eu_price = engine.spot_price( + &ResourceType::GpuHours(GpuTier::DataCenter), + Some("eu-north"), + ); + let eu_west_price = engine.spot_price( + &ResourceType::GpuHours(GpuTier::DataCenter), + Some("eu-west"), + ); // eu-north has cheaper electricity assert!(eu_price < eu_west_price); @@ -1089,7 +1107,8 @@ mod tests { fn test_cloud_comparison() { let engine = PricingEngine::new(); - let comparison = engine.compare_to_cloud(&ResourceType::GpuHours(GpuTier::DataCenter), None); + let comparison = + engine.compare_to_cloud(&ResourceType::GpuHours(GpuTier::DataCenter), None); // Should show significant savings assert!(comparison.aws_savings > 50.0); diff --git a/crates/synor-compute/src/memory/mod.rs b/crates/synor-compute/src/memory/mod.rs index 32786e3..8cc4d0d 100644 --- a/crates/synor-compute/src/memory/mod.rs +++ b/crates/synor-compute/src/memory/mod.rs @@ -106,11 +106,11 @@ impl TransferPath { /// Returns approximate bandwidth in GB/s. pub fn bandwidth_gbps(&self) -> f64 { match self { - TransferPath::NvLink => 900.0, // NVLink 4.0 + TransferPath::NvLink => 900.0, // NVLink 4.0 TransferPath::PciePeerToPeer => 64.0, // PCIe 5.0 x16 - TransferPath::CpuMediated => 50.0, // DDR5 + TransferPath::CpuMediated => 50.0, // DDR5 TransferPath::UnifiedMemory => 400.0, // Apple unified - TransferPath::Network => 10.0, // 100Gbps network + TransferPath::Network => 10.0, // 100Gbps network TransferPath::SameMemory => f64::INFINITY, } } @@ -154,7 +154,11 @@ impl MemoryManager { } /// Allocates a tensor. - pub fn allocate(&self, shape: Vec, dtype: DataType) -> Result { + pub fn allocate( + &self, + shape: Vec, + dtype: DataType, + ) -> Result { let handle = TensorHandle::new(shape, dtype); self.tensors.write().insert(handle.id, handle.clone()); Ok(handle) @@ -223,9 +227,13 @@ impl MemoryManager { } // Check for NVLink between NVIDIA GPUs - if matches!(from, ProcessorType::Gpu(crate::processor::GpuVariant::NvidiaCuda { .. })) - && matches!(to, ProcessorType::Gpu(crate::processor::GpuVariant::NvidiaCuda { .. })) - { + if matches!( + from, + ProcessorType::Gpu(crate::processor::GpuVariant::NvidiaCuda { .. }) + ) && matches!( + to, + ProcessorType::Gpu(crate::processor::GpuVariant::NvidiaCuda { .. }) + ) { return TransferPath::NvLink; } @@ -244,10 +252,22 @@ impl MemoryManager { match (a, b) { // Apple Silicon unified memory - (ProcessorType::Cpu(CpuVariant::Arm64 { .. }), ProcessorType::Gpu(GpuVariant::AppleMetal)) - | (ProcessorType::Gpu(GpuVariant::AppleMetal), ProcessorType::Cpu(CpuVariant::Arm64 { .. })) - | (ProcessorType::Npu(NpuVariant::AppleNeuralEngine { .. }), ProcessorType::Cpu(CpuVariant::Arm64 { .. })) - | (ProcessorType::Npu(NpuVariant::AppleNeuralEngine { .. }), ProcessorType::Gpu(GpuVariant::AppleMetal)) => true, + ( + ProcessorType::Cpu(CpuVariant::Arm64 { .. }), + ProcessorType::Gpu(GpuVariant::AppleMetal), + ) + | ( + ProcessorType::Gpu(GpuVariant::AppleMetal), + ProcessorType::Cpu(CpuVariant::Arm64 { .. }), + ) + | ( + ProcessorType::Npu(NpuVariant::AppleNeuralEngine { .. }), + ProcessorType::Cpu(CpuVariant::Arm64 { .. }), + ) + | ( + ProcessorType::Npu(NpuVariant::AppleNeuralEngine { .. }), + ProcessorType::Gpu(GpuVariant::AppleMetal), + ) => true, // Same type _ if a == b => true, _ => false, @@ -325,7 +345,9 @@ mod tests { #[test] fn test_transfer_path_bandwidth() { - assert!(TransferPath::NvLink.bandwidth_gbps() > TransferPath::PciePeerToPeer.bandwidth_gbps()); + assert!( + TransferPath::NvLink.bandwidth_gbps() > TransferPath::PciePeerToPeer.bandwidth_gbps() + ); assert!(TransferPath::SameMemory.bandwidth_gbps().is_infinite()); } @@ -333,7 +355,9 @@ mod tests { fn test_memory_manager() { let manager = MemoryManager::new(); - let handle = manager.allocate(vec![1024, 1024], DataType::Float32).unwrap(); + let handle = manager + .allocate(vec![1024, 1024], DataType::Float32) + .unwrap(); assert_eq!(manager.tensor_count(), 1); manager.free(handle.id).unwrap(); @@ -347,22 +371,26 @@ mod tests { let handle = manager.allocate(vec![1024], DataType::Float32).unwrap(); // First ensure should allocate - let path = manager.ensure_on( - handle.id, - ProcessorType::Gpu(crate::processor::GpuVariant::NvidiaCuda { - compute_capability: (8, 0), - }), - ).unwrap(); + let path = manager + .ensure_on( + handle.id, + ProcessorType::Gpu(crate::processor::GpuVariant::NvidiaCuda { + compute_capability: (8, 0), + }), + ) + .unwrap(); assert_eq!(path, TransferPath::SameMemory); // Second ensure to same location should be same memory - let path = manager.ensure_on( - handle.id, - ProcessorType::Gpu(crate::processor::GpuVariant::NvidiaCuda { - compute_capability: (8, 0), - }), - ).unwrap(); + let path = manager + .ensure_on( + handle.id, + ProcessorType::Gpu(crate::processor::GpuVariant::NvidiaCuda { + compute_capability: (8, 0), + }), + ) + .unwrap(); assert_eq!(path, TransferPath::SameMemory); } diff --git a/crates/synor-compute/src/model/mod.rs b/crates/synor-compute/src/model/mod.rs index 38d8636..32e1443 100644 --- a/crates/synor-compute/src/model/mod.rs +++ b/crates/synor-compute/src/model/mod.rs @@ -140,13 +140,7 @@ pub struct ModelInfo { impl ModelInfo { /// Creates a new LLM model info. - pub fn llm( - alias: &str, - name: &str, - cid: &str, - parameters: u64, - context_length: u32, - ) -> Self { + pub fn llm(alias: &str, name: &str, cid: &str, parameters: u64, context_length: u32) -> Self { Self { id: ModelId::from_alias(alias), name: name.to_string(), @@ -156,7 +150,12 @@ impl ModelInfo { format: ModelFormat::SafeTensors, size_bytes: parameters * 2, // ~2 bytes per param in fp16 parameters, - supported_precisions: vec![Precision::Fp16, Precision::Bf16, Precision::Int8, Precision::Int4], + supported_precisions: vec![ + Precision::Fp16, + Precision::Bf16, + Precision::Int8, + Precision::Int4, + ], recommended_processor: ProcessorType::Lpu, context_length: Some(context_length), input_schema: None, @@ -238,33 +237,123 @@ impl ModelRegistry { let default_models = vec![ // ===== LLMs ===== // Llama 3 family - ModelInfo::llm("llama-3-8b", "Llama 3 8B", "QmLlama3_8B_placeholder", 8_000_000_000, 8192), - ModelInfo::llm("llama-3-70b", "Llama 3 70B", "QmLlama3_70B_placeholder", 70_000_000_000, 8192), - ModelInfo::llm("llama-3.1-8b", "Llama 3.1 8B", "QmLlama31_8B_placeholder", 8_000_000_000, 128000), - ModelInfo::llm("llama-3.1-70b", "Llama 3.1 70B", "QmLlama31_70B_placeholder", 70_000_000_000, 128000), - ModelInfo::llm("llama-3.1-405b", "Llama 3.1 405B", "QmLlama31_405B_placeholder", 405_000_000_000, 128000), - + ModelInfo::llm( + "llama-3-8b", + "Llama 3 8B", + "QmLlama3_8B_placeholder", + 8_000_000_000, + 8192, + ), + ModelInfo::llm( + "llama-3-70b", + "Llama 3 70B", + "QmLlama3_70B_placeholder", + 70_000_000_000, + 8192, + ), + ModelInfo::llm( + "llama-3.1-8b", + "Llama 3.1 8B", + "QmLlama31_8B_placeholder", + 8_000_000_000, + 128000, + ), + ModelInfo::llm( + "llama-3.1-70b", + "Llama 3.1 70B", + "QmLlama31_70B_placeholder", + 70_000_000_000, + 128000, + ), + ModelInfo::llm( + "llama-3.1-405b", + "Llama 3.1 405B", + "QmLlama31_405B_placeholder", + 405_000_000_000, + 128000, + ), // Mistral family - ModelInfo::llm("mistral-7b", "Mistral 7B", "QmMistral7B_placeholder", 7_000_000_000, 32768), - ModelInfo::llm("mixtral-8x7b", "Mixtral 8x7B", "QmMixtral8x7B_placeholder", 46_000_000_000, 32768), - ModelInfo::llm("mixtral-8x22b", "Mixtral 8x22B", "QmMixtral8x22B_placeholder", 176_000_000_000, 65536), - + ModelInfo::llm( + "mistral-7b", + "Mistral 7B", + "QmMistral7B_placeholder", + 7_000_000_000, + 32768, + ), + ModelInfo::llm( + "mixtral-8x7b", + "Mixtral 8x7B", + "QmMixtral8x7B_placeholder", + 46_000_000_000, + 32768, + ), + ModelInfo::llm( + "mixtral-8x22b", + "Mixtral 8x22B", + "QmMixtral8x22B_placeholder", + 176_000_000_000, + 65536, + ), // Qwen family - ModelInfo::llm("qwen-2.5-7b", "Qwen 2.5 7B", "QmQwen25_7B_placeholder", 7_000_000_000, 128000), - ModelInfo::llm("qwen-2.5-72b", "Qwen 2.5 72B", "QmQwen25_72B_placeholder", 72_000_000_000, 128000), - + ModelInfo::llm( + "qwen-2.5-7b", + "Qwen 2.5 7B", + "QmQwen25_7B_placeholder", + 7_000_000_000, + 128000, + ), + ModelInfo::llm( + "qwen-2.5-72b", + "Qwen 2.5 72B", + "QmQwen25_72B_placeholder", + 72_000_000_000, + 128000, + ), // DeepSeek family - ModelInfo::llm("deepseek-v2", "DeepSeek V2", "QmDeepSeekV2_placeholder", 236_000_000_000, 128000), - ModelInfo::llm("deepseek-coder-33b", "DeepSeek Coder 33B", "QmDeepSeekCoder33B_placeholder", 33_000_000_000, 16384), - + ModelInfo::llm( + "deepseek-v2", + "DeepSeek V2", + "QmDeepSeekV2_placeholder", + 236_000_000_000, + 128000, + ), + ModelInfo::llm( + "deepseek-coder-33b", + "DeepSeek Coder 33B", + "QmDeepSeekCoder33B_placeholder", + 33_000_000_000, + 16384, + ), // Phi family (small/efficient) - ModelInfo::llm("phi-3-mini", "Phi 3 Mini", "QmPhi3Mini_placeholder", 3_800_000_000, 128000), - ModelInfo::llm("phi-3-medium", "Phi 3 Medium", "QmPhi3Medium_placeholder", 14_000_000_000, 128000), - + ModelInfo::llm( + "phi-3-mini", + "Phi 3 Mini", + "QmPhi3Mini_placeholder", + 3_800_000_000, + 128000, + ), + ModelInfo::llm( + "phi-3-medium", + "Phi 3 Medium", + "QmPhi3Medium_placeholder", + 14_000_000_000, + 128000, + ), // Code models - ModelInfo::llm("codellama-34b", "Code Llama 34B", "QmCodeLlama34B_placeholder", 34_000_000_000, 16384), - ModelInfo::llm("starcoder2-15b", "StarCoder2 15B", "QmStarCoder2_15B_placeholder", 15_000_000_000, 16384), - + ModelInfo::llm( + "codellama-34b", + "Code Llama 34B", + "QmCodeLlama34B_placeholder", + 34_000_000_000, + 16384, + ), + ModelInfo::llm( + "starcoder2-15b", + "StarCoder2 15B", + "QmStarCoder2_15B_placeholder", + 15_000_000_000, + 16384, + ), // ===== Embedding Models ===== ModelInfo { id: ModelId::from_alias("bge-large"), @@ -306,7 +395,6 @@ impl ModelRegistry { is_public: true, owner: None, }, - // ===== Vision Models ===== ModelInfo { id: ModelId::from_alias("stable-diffusion-xl"), @@ -348,7 +436,6 @@ impl ModelRegistry { is_public: true, owner: None, }, - // ===== Speech Models ===== ModelInfo { id: ModelId::from_alias("whisper-large-v3"), @@ -370,7 +457,6 @@ impl ModelRegistry { is_public: true, owner: None, }, - // ===== Multi-Modal Models ===== ModelInfo { id: ModelId::from_alias("llava-1.5-13b"), @@ -555,7 +641,9 @@ mod tests { let registry = ModelRegistry::new(); let results = registry.search("llama"); assert!(!results.is_empty()); - assert!(results.iter().all(|m| m.name.to_lowercase().contains("llama"))); + assert!(results + .iter() + .all(|m| m.name.to_lowercase().contains("llama"))); } #[test] diff --git a/crates/synor-compute/src/processor/capabilities.rs b/crates/synor-compute/src/processor/capabilities.rs index 0fa9d4d..a8ca1be 100644 --- a/crates/synor-compute/src/processor/capabilities.rs +++ b/crates/synor-compute/src/processor/capabilities.rs @@ -305,7 +305,7 @@ impl ProcessorCapabilities { }, memory: MemorySpecs { capacity_bytes: 230 * 1024 * 1024 * 1024, // 230 GB SRAM! - bandwidth_gbps: 80_000, // 80 TB/s internal + bandwidth_gbps: 80_000, // 80 TB/s internal type_: MemoryType::Sram, }, operations: Self::lpu_operations(), @@ -349,8 +349,8 @@ impl ProcessorCapabilities { /// Creates Apple Neural Engine capabilities. pub fn apple_neural_engine(cores: u32) -> Self { let int8_tops = match cores { - 16 => 18.0, // M3 - 32 => 35.0, // M3 Max + 16 => 18.0, // M3 + 32 => 35.0, // M3 Max _ => cores as f64 * 1.1, }; @@ -542,6 +542,8 @@ mod tests { fn test_lpu_capabilities() { let caps = ProcessorCapabilities::lpu(); assert!(caps.memory.bandwidth_gbps > 10000); // Very high internal bandwidth - assert!(caps.optimal_for.contains(&WorkloadCharacteristic::Sequential)); + assert!(caps + .optimal_for + .contains(&WorkloadCharacteristic::Sequential)); } } diff --git a/crates/synor-compute/src/processor/mod.rs b/crates/synor-compute/src/processor/mod.rs index 3bca36d..33fbf1f 100644 --- a/crates/synor-compute/src/processor/mod.rs +++ b/crates/synor-compute/src/processor/mod.rs @@ -253,10 +253,22 @@ impl Processor for GenericProcessor { fn shares_memory_with(&self, other: &ProcessorType) -> bool { match (&self.processor_type, other) { // Apple Silicon has unified memory - (ProcessorType::Cpu(CpuVariant::Arm64 { .. }), ProcessorType::Gpu(GpuVariant::AppleMetal)) - | (ProcessorType::Gpu(GpuVariant::AppleMetal), ProcessorType::Cpu(CpuVariant::Arm64 { .. })) - | (ProcessorType::Npu(NpuVariant::AppleNeuralEngine { .. }), ProcessorType::Cpu(CpuVariant::Arm64 { .. })) - | (ProcessorType::Npu(NpuVariant::AppleNeuralEngine { .. }), ProcessorType::Gpu(GpuVariant::AppleMetal)) => true, + ( + ProcessorType::Cpu(CpuVariant::Arm64 { .. }), + ProcessorType::Gpu(GpuVariant::AppleMetal), + ) + | ( + ProcessorType::Gpu(GpuVariant::AppleMetal), + ProcessorType::Cpu(CpuVariant::Arm64 { .. }), + ) + | ( + ProcessorType::Npu(NpuVariant::AppleNeuralEngine { .. }), + ProcessorType::Cpu(CpuVariant::Arm64 { .. }), + ) + | ( + ProcessorType::Npu(NpuVariant::AppleNeuralEngine { .. }), + ProcessorType::Gpu(GpuVariant::AppleMetal), + ) => true, // Same type always shares (a, b) if a == b => true, _ => false, diff --git a/crates/synor-compute/src/processor/operation.rs b/crates/synor-compute/src/processor/operation.rs index 41d52b3..5024a7e 100644 --- a/crates/synor-compute/src/processor/operation.rs +++ b/crates/synor-compute/src/processor/operation.rs @@ -191,10 +191,7 @@ pub enum Operation { }, /// Data loading from storage. - DataLoad { - bytes: usize, - async_: bool, - }, + DataLoad { bytes: usize, async_: bool }, /// Data preprocessing. DataPreprocess { @@ -209,16 +206,10 @@ pub enum Operation { }, /// Detokenization. - Detokenization { - tokens: usize, - vocab_size: usize, - }, + Detokenization { tokens: usize, vocab_size: usize }, /// Checkpoint save. - Checkpoint { - bytes: usize, - async_: bool, - }, + Checkpoint { bytes: usize, async_: bool }, /// All-reduce across devices. AllReduce { @@ -228,9 +219,7 @@ pub enum Operation { }, /// Backward pass for a layer. - Backward { - forward_op: Box, - }, + Backward { forward_op: Box }, /// Optimizer step. OptimizerStep { @@ -240,16 +229,10 @@ pub enum Operation { }, /// Transpose. - Transpose { - shape: Vec, - axes: Vec, - }, + Transpose { shape: Vec, axes: Vec }, /// Reshape. - Reshape { - from: Vec, - to: Vec, - }, + Reshape { from: Vec, to: Vec }, /// Concatenate tensors. Concat { @@ -378,9 +361,7 @@ impl Operation { | Operation::SiLU { elements } => *elements as f64, // Softmax: ~5 ops per element (exp, sum, div) - Operation::Softmax { - batch, seq_len, .. - } => 5.0 * (*batch as f64) * (*seq_len as f64), + Operation::Softmax { batch, seq_len, .. } => 5.0 * (*batch as f64) * (*seq_len as f64), // Embedding: just lookup, minimal FLOPS Operation::Embedding { diff --git a/crates/synor-compute/src/processor/profiles.rs b/crates/synor-compute/src/processor/profiles.rs index f61be69..d892589 100644 --- a/crates/synor-compute/src/processor/profiles.rs +++ b/crates/synor-compute/src/processor/profiles.rs @@ -39,8 +39,7 @@ impl ProcessorProfiles { bandwidth_gbps: 460, type_: MemoryType::Ddr5, }, - operations: ProcessorCapabilities::cpu(96, 2.4, false) - .operations, + operations: ProcessorCapabilities::cpu(96, 2.4, false).operations, power: PowerCharacteristics { tdp_watts: 360, efficiency: 0.85, @@ -70,8 +69,7 @@ impl ProcessorProfiles { bandwidth_gbps: 307, type_: MemoryType::Ddr5, }, - operations: ProcessorCapabilities::cpu(56, 2.9, true) - .operations, + operations: ProcessorCapabilities::cpu(56, 2.9, true).operations, power: PowerCharacteristics { tdp_watts: 350, efficiency: 0.80, @@ -101,8 +99,7 @@ impl ProcessorProfiles { bandwidth_gbps: 400, type_: MemoryType::Unified, }, - operations: ProcessorCapabilities::cpu(16, 4.0, false) - .operations, + operations: ProcessorCapabilities::cpu(16, 4.0, false).operations, power: PowerCharacteristics { tdp_watts: 40, efficiency: 0.95, @@ -141,8 +138,7 @@ impl ProcessorProfiles { bandwidth_gbps: 3350, type_: MemoryType::Hbm3, }, - operations: ProcessorCapabilities::nvidia_gpu(16896, 528, 80, 3350, (9, 0)) - .operations, + operations: ProcessorCapabilities::nvidia_gpu(16896, 528, 80, 3350, (9, 0)).operations, power: PowerCharacteristics { tdp_watts: 700, efficiency: 0.90, @@ -173,8 +169,7 @@ impl ProcessorProfiles { bandwidth_gbps: 2039, type_: MemoryType::Hbm2e, }, - operations: ProcessorCapabilities::nvidia_gpu(6912, 432, 80, 2039, (8, 0)) - .operations, + operations: ProcessorCapabilities::nvidia_gpu(6912, 432, 80, 2039, (8, 0)).operations, power: PowerCharacteristics { tdp_watts: 400, efficiency: 0.88, @@ -205,8 +200,7 @@ impl ProcessorProfiles { bandwidth_gbps: 1008, type_: MemoryType::Gddr6, }, - operations: ProcessorCapabilities::nvidia_gpu(16384, 512, 24, 1008, (8, 9)) - .operations, + operations: ProcessorCapabilities::nvidia_gpu(16384, 512, 24, 1008, (8, 9)).operations, power: PowerCharacteristics { tdp_watts: 450, efficiency: 0.85, @@ -236,8 +230,7 @@ impl ProcessorProfiles { bandwidth_gbps: 936, type_: MemoryType::Gddr6, }, - operations: ProcessorCapabilities::nvidia_gpu(10496, 328, 24, 936, (8, 6)) - .operations, + operations: ProcessorCapabilities::nvidia_gpu(10496, 328, 24, 936, (8, 6)).operations, power: PowerCharacteristics { tdp_watts: 350, efficiency: 0.82, @@ -272,8 +265,8 @@ impl ProcessorProfiles { type_: MemoryType::Hbm3, }, operations: { - let mut ops = ProcessorCapabilities::nvidia_gpu(16384, 512, 80, 5300, (9, 0)) - .operations; + let mut ops = + ProcessorCapabilities::nvidia_gpu(16384, 512, 80, 5300, (9, 0)).operations; ops.remove(&OperationType::FlashAttention); // Different implementation ops }, @@ -308,8 +301,8 @@ impl ProcessorProfiles { type_: MemoryType::Gddr6, }, operations: { - let mut ops = ProcessorCapabilities::nvidia_gpu(6144, 0, 24, 960, (8, 0)) - .operations; + let mut ops = + ProcessorCapabilities::nvidia_gpu(6144, 0, 24, 960, (8, 0)).operations; ops.remove(&OperationType::FlashAttention); ops }, @@ -318,9 +311,7 @@ impl ProcessorProfiles { efficiency: 0.80, power_tier: PowerTier::High, }, - optimal_for: vec![ - WorkloadCharacteristic::HighlyParallel, - ], + optimal_for: vec![WorkloadCharacteristic::HighlyParallel], } } @@ -429,8 +420,7 @@ impl ProcessorProfiles { bandwidth_gbps: 200, type_: MemoryType::Unified, }, - operations: ProcessorCapabilities::apple_neural_engine(16) - .operations, + operations: ProcessorCapabilities::apple_neural_engine(16).operations, power: PowerCharacteristics { tdp_watts: 8, efficiency: 0.98, @@ -465,8 +455,7 @@ impl ProcessorProfiles { bandwidth_gbps: 77, type_: MemoryType::Lpddr, }, - operations: ProcessorCapabilities::apple_neural_engine(16) - .operations, + operations: ProcessorCapabilities::apple_neural_engine(16).operations, power: PowerCharacteristics { tdp_watts: 10, efficiency: 0.95, diff --git a/crates/synor-compute/src/processor/types.rs b/crates/synor-compute/src/processor/types.rs index c6b1f50..d6d44f3 100644 --- a/crates/synor-compute/src/processor/types.rs +++ b/crates/synor-compute/src/processor/types.rs @@ -24,10 +24,7 @@ pub enum ProcessorType { /// WebAssembly runtime. Wasm, /// Custom/Unknown accelerator. - Custom { - vendor: String, - model: String, - }, + Custom { vendor: String, model: String }, } impl Default for ProcessorType { diff --git a/crates/synor-compute/src/scheduler/load_balancer.rs b/crates/synor-compute/src/scheduler/load_balancer.rs index b9d34b5..296922a 100644 --- a/crates/synor-compute/src/scheduler/load_balancer.rs +++ b/crates/synor-compute/src/scheduler/load_balancer.rs @@ -6,10 +6,10 @@ //! - Latency-aware scheduling //! - Real-time utilization metrics +use super::TaskAssignment; use crate::device::DeviceRegistry; use crate::processor::{Operation, OperationType, ProcessorId, ProcessorType}; use crate::task::{Task, TaskId, TaskPriority}; -use super::TaskAssignment; use parking_lot::RwLock; use std::collections::HashMap; use std::sync::atomic::{AtomicU64, Ordering}; @@ -127,8 +127,12 @@ impl LoadBalancer { /// Register a processor with its type. pub fn register_processor(&self, processor_id: ProcessorId, processor_type: ProcessorType) { self.loads.write().insert(processor_id, AtomicU64::new(0)); - self.metrics.write().insert(processor_id, ProcessorMetrics::default()); - self.processor_types.write().insert(processor_id, processor_type); + self.metrics + .write() + .insert(processor_id, ProcessorMetrics::default()); + self.processor_types + .write() + .insert(processor_id, processor_type); } /// Unregister a processor. @@ -150,7 +154,8 @@ impl LoadBalancer { /// Get current load for a processor. pub fn get_load(&self, processor_id: ProcessorId) -> u64 { - self.loads.read() + self.loads + .read() .get(&processor_id) .map(|l| l.load(Ordering::Relaxed)) .unwrap_or(0) @@ -179,140 +184,140 @@ impl LoadBalancer { ProcessorType::Cpu(_) => matches!( op_type, OperationType::MatMul - | OperationType::Conv2d - | OperationType::Conv3d - | OperationType::DepthwiseConv - | OperationType::BatchNorm - | OperationType::LayerNorm - | OperationType::Add - | OperationType::Mul - | OperationType::ReLU - | OperationType::GeLU - | OperationType::SiLU - | OperationType::Softmax - | OperationType::Sum - | OperationType::Mean - | OperationType::Max - | OperationType::ArgMax - | OperationType::Embedding - | OperationType::TopK - | OperationType::Sampling - | OperationType::Tokenization - | OperationType::Detokenization - | OperationType::DataLoad - | OperationType::DataPreprocess - | OperationType::Transpose - | OperationType::Reshape - | OperationType::Concat - | OperationType::Split + | OperationType::Conv2d + | OperationType::Conv3d + | OperationType::DepthwiseConv + | OperationType::BatchNorm + | OperationType::LayerNorm + | OperationType::Add + | OperationType::Mul + | OperationType::ReLU + | OperationType::GeLU + | OperationType::SiLU + | OperationType::Softmax + | OperationType::Sum + | OperationType::Mean + | OperationType::Max + | OperationType::ArgMax + | OperationType::Embedding + | OperationType::TopK + | OperationType::Sampling + | OperationType::Tokenization + | OperationType::Detokenization + | OperationType::DataLoad + | OperationType::DataPreprocess + | OperationType::Transpose + | OperationType::Reshape + | OperationType::Concat + | OperationType::Split ), // GPUs excel at parallel operations ProcessorType::Gpu(_) => matches!( op_type, OperationType::MatMul - | OperationType::Conv2d - | OperationType::Conv3d - | OperationType::DepthwiseConv - | OperationType::BatchNorm - | OperationType::LayerNorm - | OperationType::SelfAttention - | OperationType::CrossAttention - | OperationType::FlashAttention - | OperationType::Add - | OperationType::Mul - | OperationType::ReLU - | OperationType::GeLU - | OperationType::SiLU - | OperationType::Softmax - | OperationType::Sum - | OperationType::Mean - | OperationType::Max - | OperationType::ArgMax - | OperationType::Embedding - | OperationType::RoPE - | OperationType::KVCache - | OperationType::TopK - | OperationType::Sampling - | OperationType::Transpose - | OperationType::Reshape - | OperationType::Concat - | OperationType::Split - | OperationType::Gather - | OperationType::Scatter - | OperationType::AllReduce - | OperationType::AllGather - | OperationType::ReduceScatter - | OperationType::Backward - | OperationType::OptimizerStep - | OperationType::GradientClip + | OperationType::Conv2d + | OperationType::Conv3d + | OperationType::DepthwiseConv + | OperationType::BatchNorm + | OperationType::LayerNorm + | OperationType::SelfAttention + | OperationType::CrossAttention + | OperationType::FlashAttention + | OperationType::Add + | OperationType::Mul + | OperationType::ReLU + | OperationType::GeLU + | OperationType::SiLU + | OperationType::Softmax + | OperationType::Sum + | OperationType::Mean + | OperationType::Max + | OperationType::ArgMax + | OperationType::Embedding + | OperationType::RoPE + | OperationType::KVCache + | OperationType::TopK + | OperationType::Sampling + | OperationType::Transpose + | OperationType::Reshape + | OperationType::Concat + | OperationType::Split + | OperationType::Gather + | OperationType::Scatter + | OperationType::AllReduce + | OperationType::AllGather + | OperationType::ReduceScatter + | OperationType::Backward + | OperationType::OptimizerStep + | OperationType::GradientClip ), // TPUs optimized for ML ProcessorType::Tpu(_) => matches!( op_type, OperationType::MatMul - | OperationType::Conv2d - | OperationType::BatchNorm - | OperationType::LayerNorm - | OperationType::SelfAttention - | OperationType::CrossAttention - | OperationType::FlashAttention - | OperationType::Add - | OperationType::Mul - | OperationType::ReLU - | OperationType::GeLU - | OperationType::SiLU - | OperationType::Softmax - | OperationType::Sum - | OperationType::Mean - | OperationType::Embedding - | OperationType::RoPE - | OperationType::KVCache - | OperationType::AllReduce - | OperationType::AllGather - | OperationType::ReduceScatter - | OperationType::Backward - | OperationType::OptimizerStep + | OperationType::Conv2d + | OperationType::BatchNorm + | OperationType::LayerNorm + | OperationType::SelfAttention + | OperationType::CrossAttention + | OperationType::FlashAttention + | OperationType::Add + | OperationType::Mul + | OperationType::ReLU + | OperationType::GeLU + | OperationType::SiLU + | OperationType::Softmax + | OperationType::Sum + | OperationType::Mean + | OperationType::Embedding + | OperationType::RoPE + | OperationType::KVCache + | OperationType::AllReduce + | OperationType::AllGather + | OperationType::ReduceScatter + | OperationType::Backward + | OperationType::OptimizerStep ), // NPUs for neural network inference ProcessorType::Npu(_) => matches!( op_type, OperationType::MatMul - | OperationType::Conv2d - | OperationType::DepthwiseConv - | OperationType::BatchNorm - | OperationType::LayerNorm - | OperationType::SelfAttention - | OperationType::Add - | OperationType::Mul - | OperationType::ReLU - | OperationType::GeLU - | OperationType::SiLU - | OperationType::Softmax - | OperationType::Sum - | OperationType::Mean + | OperationType::Conv2d + | OperationType::DepthwiseConv + | OperationType::BatchNorm + | OperationType::LayerNorm + | OperationType::SelfAttention + | OperationType::Add + | OperationType::Mul + | OperationType::ReLU + | OperationType::GeLU + | OperationType::SiLU + | OperationType::Softmax + | OperationType::Sum + | OperationType::Mean ), // LPUs for sequential inference (optimized for LLMs) ProcessorType::Lpu => matches!( op_type, OperationType::MatMul - | OperationType::LayerNorm - | OperationType::SelfAttention - | OperationType::FlashAttention - | OperationType::Add - | OperationType::Mul - | OperationType::ReLU - | OperationType::GeLU - | OperationType::SiLU - | OperationType::Softmax - | OperationType::Embedding - | OperationType::RoPE - | OperationType::KVCache - | OperationType::TopK - | OperationType::Sampling + | OperationType::LayerNorm + | OperationType::SelfAttention + | OperationType::FlashAttention + | OperationType::Add + | OperationType::Mul + | OperationType::ReLU + | OperationType::GeLU + | OperationType::SiLU + | OperationType::Softmax + | OperationType::Embedding + | OperationType::RoPE + | OperationType::KVCache + | OperationType::TopK + | OperationType::Sampling ), // FPGAs can be programmed for anything @@ -322,40 +327,40 @@ impl LoadBalancer { ProcessorType::Dsp(_) => matches!( op_type, OperationType::Conv2d - | OperationType::DepthwiseConv - | OperationType::Add - | OperationType::Mul - | OperationType::Sum - | OperationType::Mean - | OperationType::Max + | OperationType::DepthwiseConv + | OperationType::Add + | OperationType::Mul + | OperationType::Sum + | OperationType::Mean + | OperationType::Max ), // WebGPU has limited operations ProcessorType::WebGpu => matches!( op_type, OperationType::MatMul - | OperationType::Conv2d - | OperationType::Add - | OperationType::Mul - | OperationType::ReLU - | OperationType::Softmax - | OperationType::Sum - | OperationType::Transpose - | OperationType::Reshape + | OperationType::Conv2d + | OperationType::Add + | OperationType::Mul + | OperationType::ReLU + | OperationType::Softmax + | OperationType::Sum + | OperationType::Transpose + | OperationType::Reshape ), // WASM for portable compute ProcessorType::Wasm => matches!( op_type, OperationType::MatMul - | OperationType::Add - | OperationType::Mul - | OperationType::ReLU - | OperationType::Softmax - | OperationType::Sum - | OperationType::Mean - | OperationType::Tokenization - | OperationType::Detokenization + | OperationType::Add + | OperationType::Mul + | OperationType::ReLU + | OperationType::Softmax + | OperationType::Sum + | OperationType::Mean + | OperationType::Tokenization + | OperationType::Detokenization ), // Custom processors - assume they can handle anything @@ -381,7 +386,9 @@ impl LoadBalancer { } // Get utilization and metrics - let utilization = proc_metrics.map(|m| m.utilization).unwrap_or(load as f64 / 100.0); + let utilization = proc_metrics + .map(|m| m.utilization) + .unwrap_or(load as f64 / 100.0); let power = proc_metrics.map(|m| m.power_watts).unwrap_or(100.0); let avg_completion = proc_metrics.map(|m| m.avg_completion_ms).unwrap_or(100.0); @@ -431,13 +438,13 @@ impl LoadBalancer { BalancingStrategy::Cost => { // Prioritize cheaper resources (consumer devices) let cost_factor = match processor_type { - ProcessorType::Wasm => 0.1, // Cheapest (browser) + ProcessorType::Wasm => 0.1, // Cheapest (browser) ProcessorType::WebGpu => 0.15, ProcessorType::Cpu(_) => 0.2, - ProcessorType::Npu(_) => 0.3, // Mobile NPUs + ProcessorType::Npu(_) => 0.3, // Mobile NPUs ProcessorType::Gpu(_) => 0.5, ProcessorType::Lpu => 0.8, - ProcessorType::Tpu(_) => 1.0, // Most expensive + ProcessorType::Tpu(_) => 1.0, // Most expensive _ => 0.5, }; @@ -450,7 +457,7 @@ impl LoadBalancer { // Bonus for low-latency processors let latency_bonus = match processor_type { - ProcessorType::Lpu => 5.0, // Designed for low latency + ProcessorType::Lpu => 5.0, // Designed for low latency ProcessorType::Npu(_) => 3.0, ProcessorType::Gpu(_) => 2.0, ProcessorType::Tpu(_) => 1.5, @@ -550,7 +557,8 @@ impl LoadBalancer { let mut suggestions = Vec::new(); let loads = self.loads.read(); - let load_values: Vec<_> = loads.iter() + let load_values: Vec<_> = loads + .iter() .map(|(id, load)| (*id, load.load(Ordering::Relaxed))) .collect(); @@ -558,16 +566,18 @@ impl LoadBalancer { return suggestions; } - let avg_load: f64 = load_values.iter().map(|(_, l)| *l as f64).sum::() - / load_values.len() as f64; + let avg_load: f64 = + load_values.iter().map(|(_, l)| *l as f64).sum::() / load_values.len() as f64; let processor_types = self.processor_types.read(); - let overloaded: Vec<_> = load_values.iter() + let overloaded: Vec<_> = load_values + .iter() .filter(|(_, l)| *l as f64 > avg_load * (1.0 + self.rebalance_threshold)) .collect(); - let underloaded: Vec<_> = load_values.iter() + let underloaded: Vec<_> = load_values + .iter() .filter(|(_, l)| (*l as f64) < avg_load * (1.0 - self.rebalance_threshold)) .collect(); @@ -627,7 +637,9 @@ impl LoadBalancer { /// Clean up old migration history. pub fn cleanup_history(&self, max_age: Duration) { let cutoff = Instant::now() - max_age; - self.migration_history.write().retain(|r| r.timestamp > cutoff); + self.migration_history + .write() + .retain(|r| r.timestamp > cutoff); } } @@ -725,7 +737,9 @@ mod tests { balancer.register_processor(ProcessorId(0), ProcessorType::Cpu(CpuVariant::default())); balancer.register_processor( ProcessorId(1), - ProcessorType::Gpu(GpuVariant::NvidiaCuda { compute_capability: (8, 9) }), + ProcessorType::Gpu(GpuVariant::NvidiaCuda { + compute_capability: (8, 9), + }), ); // Give CPU high load @@ -757,7 +771,9 @@ mod tests { }; let cpu = ProcessorType::Cpu(CpuVariant::default()); - let gpu = ProcessorType::Gpu(GpuVariant::NvidiaCuda { compute_capability: (8, 9) }); + let gpu = ProcessorType::Gpu(GpuVariant::NvidiaCuda { + compute_capability: (8, 9), + }); let lpu = ProcessorType::Lpu; // MatMul can run on all @@ -778,7 +794,10 @@ mod tests { let npu_id = ProcessorId(1); balancer.register_processor(cpu_id, ProcessorType::Cpu(CpuVariant::default())); - balancer.register_processor(npu_id, ProcessorType::Npu(crate::processor::NpuVariant::AppleNeuralEngine { cores: 16 })); + balancer.register_processor( + npu_id, + ProcessorType::Npu(crate::processor::NpuVariant::AppleNeuralEngine { cores: 16 }), + ); let task = create_test_task(TaskPriority::Normal); diff --git a/crates/synor-compute/src/scheduler/mod.rs b/crates/synor-compute/src/scheduler/mod.rs index 9ade413..bf246cf 100644 --- a/crates/synor-compute/src/scheduler/mod.rs +++ b/crates/synor-compute/src/scheduler/mod.rs @@ -69,7 +69,9 @@ impl HeterogeneousScheduler { let utilization = self.estimate_utilization(&schedule); // 5. Store active schedule - self.active_schedules.write().insert(schedule.id, schedule.clone()); + self.active_schedules + .write() + .insert(schedule.id, schedule.clone()); Ok(ScheduleResult { schedule, @@ -89,10 +91,12 @@ impl HeterogeneousScheduler { let mut handles = Vec::new(); for task_id in &stage.tasks { - let task = schedule.tasks.get(task_id) - .ok_or_else(|| ComputeError::Internal(format!("Task not found: {:?}", task_id)))?; - let processor_id = schedule.assignment.get(task_id) - .ok_or_else(|| ComputeError::Internal(format!("No assignment for task: {:?}", task_id)))?; + let task = schedule.tasks.get(task_id).ok_or_else(|| { + ComputeError::Internal(format!("Task not found: {:?}", task_id)) + })?; + let processor_id = schedule.assignment.get(task_id).ok_or_else(|| { + ComputeError::Internal(format!("No assignment for task: {:?}", task_id)) + })?; let processor = self.device_registry.get_processor(processor_id)?; let task_clone = task.clone(); @@ -144,8 +148,9 @@ impl HeterogeneousScheduler { let best_processor = self.find_best_processor(&task).await?; // Check if we should rebalance - let final_processor = self.load_balancer - .maybe_rebalance(&task, best_processor, &assignment); + let final_processor = + self.load_balancer + .maybe_rebalance(&task, best_processor, &assignment); assignment.assign(task.id, final_processor); } @@ -207,9 +212,7 @@ impl HeterogeneousScheduler { fn topological_sort(&self, tasks: &[Task], deps: &DependencyGraph) -> Vec { let mut sorted = Vec::new(); let mut visited = std::collections::HashSet::new(); - let task_map: HashMap = tasks.iter() - .map(|t| (t.id, t.clone())) - .collect(); + let task_map: HashMap = tasks.iter().map(|t| (t.id, t.clone())).collect(); fn visit( task_id: TaskId, @@ -254,9 +257,7 @@ impl HeterogeneousScheduler { ) -> Result { let mut stages = Vec::new(); let mut scheduled = std::collections::HashSet::new(); - let task_map: HashMap = tasks.iter() - .map(|t| (t.id, t.clone())) - .collect(); + let task_map: HashMap = tasks.iter().map(|t| (t.id, t.clone())).collect(); while scheduled.len() < tasks.len() { let mut stage_tasks = Vec::new(); @@ -267,8 +268,7 @@ impl HeterogeneousScheduler { } // Check if all dependencies are satisfied - let deps_satisfied = task.dependencies.iter() - .all(|dep| scheduled.contains(dep)); + let deps_satisfied = task.dependencies.iter().all(|dep| scheduled.contains(dep)); if deps_satisfied { stage_tasks.push(task.id); @@ -277,7 +277,7 @@ impl HeterogeneousScheduler { if stage_tasks.is_empty() { return Err(ComputeError::SchedulingFailed( - "Circular dependency detected".to_string() + "Circular dependency detected".to_string(), )); } diff --git a/crates/synor-compute/src/scheduler/work_queue.rs b/crates/synor-compute/src/scheduler/work_queue.rs index c5ed975..f700e72 100644 --- a/crates/synor-compute/src/scheduler/work_queue.rs +++ b/crates/synor-compute/src/scheduler/work_queue.rs @@ -153,7 +153,10 @@ impl PriorityWorkQueue { TaskPriority::Normal, TaskPriority::Background, ] { - queues.insert(priority, WorkQueue::new(processor_type.clone(), capacity_per_priority)); + queues.insert( + priority, + WorkQueue::new(processor_type.clone(), capacity_per_priority), + ); } Self { @@ -223,10 +226,7 @@ mod tests { #[test] fn test_work_queue_basic() { - let queue = WorkQueue::new( - ProcessorType::Cpu(CpuVariant::default()), - 100, - ); + let queue = WorkQueue::new(ProcessorType::Cpu(CpuVariant::default()), 100); assert!(queue.is_empty()); @@ -246,10 +246,7 @@ mod tests { #[test] fn test_priority_queue() { - let queue = PriorityWorkQueue::new( - ProcessorType::Cpu(CpuVariant::default()), - 100, - ); + let queue = PriorityWorkQueue::new(ProcessorType::Cpu(CpuVariant::default()), 100); queue.push(create_test_task(1, TaskPriority::Background)); queue.push(create_test_task(2, TaskPriority::Critical)); diff --git a/crates/synor-compute/src/task/mod.rs b/crates/synor-compute/src/task/mod.rs index 3c7abc7..ebecfbc 100644 --- a/crates/synor-compute/src/task/mod.rs +++ b/crates/synor-compute/src/task/mod.rs @@ -495,9 +495,9 @@ mod tests { compute_capability: (8, 0) } ))); - assert!(matmul_task.is_compatible_with(ProcessorType::Tpu( - crate::processor::TpuVersion::V5p - ))); + assert!( + matmul_task.is_compatible_with(ProcessorType::Tpu(crate::processor::TpuVersion::V5p)) + ); let data_load_task = Task::new(Operation::DataLoad { bytes: 1000, @@ -505,9 +505,8 @@ mod tests { }); // DataLoad should be compatible with CPU - assert!(data_load_task.is_compatible_with(ProcessorType::Cpu( - crate::processor::CpuVariant::default() - ))); + assert!(data_load_task + .is_compatible_with(ProcessorType::Cpu(crate::processor::CpuVariant::default()))); } #[test] diff --git a/crates/synor-consensus/src/network.rs b/crates/synor-consensus/src/network.rs index d3694a3..c7a3b42 100644 --- a/crates/synor-consensus/src/network.rs +++ b/crates/synor-consensus/src/network.rs @@ -25,8 +25,7 @@ use std::time::Duration; /// Blocks per second mode. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[derive(Default)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] pub enum BpsMode { /// Standard mode: 10 blocks per second (100ms block time) /// - Suitable for most network conditions @@ -75,7 +74,6 @@ impl BpsMode { } } - impl std::fmt::Display for BpsMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -148,39 +146,39 @@ impl NetworkConfig { bps_mode: mode, blocks_per_second: 10, target_block_time_ms: 100, - daa_window_size: 2641, // ~264s window - ghostdag_k: 18, // For 10 BPS + daa_window_size: 2641, // ~264s window + ghostdag_k: 18, // For 10 BPS dagknight_k_min: 8, dagknight_k_max: 64, - finality_depth: 864, // ~86 seconds - pruning_depth: 864_000, // ~24 hours + finality_depth: 864, // ~86 seconds + pruning_depth: 864_000, // ~24 hours merge_set_size_limit: 180, expected_delay_ms: 100, }, BpsMode::Fast32 => Self { bps_mode: mode, blocks_per_second: 32, - target_block_time_ms: 31, // ~31.25ms - daa_window_size: 8461, // ~264s window at 32 BPS - ghostdag_k: 58, // Scaled for 32 BPS - dagknight_k_min: 16, // Higher min for faster blocks - dagknight_k_max: 128, // Higher max for adaptation - finality_depth: 2765, // ~86 seconds at 32 BPS - pruning_depth: 2_764_800, // ~24 hours at 32 BPS - merge_set_size_limit: 576, // 32/10 * 180 + target_block_time_ms: 31, // ~31.25ms + daa_window_size: 8461, // ~264s window at 32 BPS + ghostdag_k: 58, // Scaled for 32 BPS + dagknight_k_min: 16, // Higher min for faster blocks + dagknight_k_max: 128, // Higher max for adaptation + finality_depth: 2765, // ~86 seconds at 32 BPS + pruning_depth: 2_764_800, // ~24 hours at 32 BPS + merge_set_size_limit: 576, // 32/10 * 180 expected_delay_ms: 50, }, BpsMode::Ultra100 => Self { bps_mode: mode, blocks_per_second: 100, target_block_time_ms: 10, - daa_window_size: 26410, // ~264s window at 100 BPS - ghostdag_k: 180, // Scaled for 100 BPS - dagknight_k_min: 50, // Higher min for very fast blocks - dagknight_k_max: 255, // u8 max - very high for adaptation - finality_depth: 8640, // ~86 seconds at 100 BPS - pruning_depth: 8_640_000, // ~24 hours at 100 BPS - merge_set_size_limit: 1800, // 100/10 * 180 + daa_window_size: 26410, // ~264s window at 100 BPS + ghostdag_k: 180, // Scaled for 100 BPS + dagknight_k_min: 50, // Higher min for very fast blocks + dagknight_k_max: 255, // u8 max - very high for adaptation + finality_depth: 8640, // ~86 seconds at 100 BPS + pruning_depth: 8_640_000, // ~24 hours at 100 BPS + merge_set_size_limit: 1800, // 100/10 * 180 expected_delay_ms: 20, }, BpsMode::Custom(bps) => { @@ -269,7 +267,7 @@ pub fn bps_comparison_table() -> String { let mut table = String::from( "| Property | Standard (10 BPS) | Fast (32 BPS) | Ultra (100 BPS) |\n\ - |----------|-------------------|---------------|------------------|\n" + |----------|-------------------|---------------|------------------|\n", ); // Block Time @@ -314,7 +312,9 @@ pub fn bps_comparison_table() -> String { // Estimated TPS table.push_str(&format!( "| Est. TPS @1000tx/block | {:.0} | {:.0} | {:.0} |\n", - standard.estimate_tps(1000), fast.estimate_tps(1000), ultra.estimate_tps(1000) + standard.estimate_tps(1000), + fast.estimate_tps(1000), + ultra.estimate_tps(1000) )); table @@ -401,9 +401,9 @@ mod tests { fn test_latency_acceptable() { let config = NetworkConfig::standard(); // expects 100ms - assert!(config.is_latency_acceptable(50)); // Good - assert!(config.is_latency_acceptable(100)); // OK - assert!(config.is_latency_acceptable(200)); // Still OK (2x limit) + assert!(config.is_latency_acceptable(50)); // Good + assert!(config.is_latency_acceptable(100)); // OK + assert!(config.is_latency_acceptable(200)); // Still OK (2x limit) assert!(!config.is_latency_acceptable(300)); // Too high } diff --git a/crates/synor-crypto/src/falcon.rs b/crates/synor-crypto/src/falcon.rs index a6e34b3..d58b233 100644 --- a/crates/synor-crypto/src/falcon.rs +++ b/crates/synor-crypto/src/falcon.rs @@ -55,8 +55,8 @@ //! | Layer 2 transactions | FALCON-512 (batch efficiency) | //! | High-value transactions | Dilithium3 (conservative choice) | -use pqcrypto_falcon::falcon512; use pqcrypto_falcon::falcon1024; +use pqcrypto_falcon::falcon512; use pqcrypto_traits::sign::{ DetachedSignature, PublicKey as PqPublicKey, SecretKey as PqSecretKey, }; @@ -64,8 +64,7 @@ use thiserror::Error; use zeroize::{Zeroize, ZeroizeOnDrop}; /// FALCON variant selection. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[derive(Default)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum FalconVariant { /// 128-bit security, ~690 byte signatures #[default] @@ -124,7 +123,6 @@ impl FalconVariant { } } - /// FALCON public key. #[derive(Clone)] pub struct FalconPublicKey { @@ -188,7 +186,10 @@ impl std::fmt::Debug for FalconPublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("FalconPublicKey") .field("variant", &self.variant) - .field("bytes", &hex::encode(&self.bytes[..8.min(self.bytes.len())])) + .field( + "bytes", + &hex::encode(&self.bytes[..8.min(self.bytes.len())]), + ) .finish() } } @@ -492,7 +493,10 @@ mod tests { // Verify with wrong message should fail let wrong_message = b"Wrong message"; - assert!(keypair.public_key().verify(wrong_message, &signature).is_err()); + assert!(keypair + .public_key() + .verify(wrong_message, &signature) + .is_err()); } #[test] diff --git a/crates/synor-crypto/src/negotiation.rs b/crates/synor-crypto/src/negotiation.rs index 5e14ec8..f383fcf 100644 --- a/crates/synor-crypto/src/negotiation.rs +++ b/crates/synor-crypto/src/negotiation.rs @@ -150,9 +150,9 @@ impl PqAlgorithm { /// Default priority order (higher = more preferred) fn default_priority(&self) -> u8 { match self { - Self::Dilithium3 => 100, // Default, well-balanced - Self::Falcon1024 => 90, // High security, compact - Self::Falcon512 => 85, // Compact, mobile-friendly + Self::Dilithium3 => 100, // Default, well-balanced + Self::Falcon1024 => 90, // High security, compact + Self::Falcon512 => 85, // Compact, mobile-friendly Self::SphincsShake192s => 70, // Conservative backup Self::SphincsShake256s => 60, // Maximum security Self::SphincsShake128s => 50, // Basic SPHINCS+ @@ -270,7 +270,8 @@ impl AlgorithmCapabilities { /// Decode capabilities from bytes pub fn decode(data: &[u8]) -> Result { - serde_json::from_slice(data).map_err(|e| NegotiationError::InvalidCapabilities(e.to_string())) + serde_json::from_slice(data) + .map_err(|e| NegotiationError::InvalidCapabilities(e.to_string())) } } @@ -384,8 +385,7 @@ impl AlgorithmNegotiator { // Check security level let meets_local_security = algo.security_level() >= self.local_caps.min_security_level; - let meets_remote_security = - algo.security_level() >= remote_caps.min_security_level; + let meets_remote_security = algo.security_level() >= remote_caps.min_security_level; // Check signature size let local_size_ok = self.local_caps.max_signature_size == 0 @@ -513,10 +513,7 @@ impl AlgorithmNegotiator { } /// Quick negotiation using just algorithm names - pub fn quick_negotiate( - local: &[PqAlgorithm], - remote: &[PqAlgorithm], - ) -> Option { + pub fn quick_negotiate(local: &[PqAlgorithm], remote: &[PqAlgorithm]) -> Option { // Find common algorithms and return the one with highest default priority let local_set: HashSet<_> = local.iter().collect(); let remote_set: HashSet<_> = remote.iter().collect(); @@ -604,7 +601,10 @@ pub enum NegotiationMessage { }, /// Acknowledge selection - Acknowledgment { session_id: [u8; 32], accepted: bool }, + Acknowledgment { + session_id: [u8; 32], + accepted: bool, + }, /// Request renegotiation Renegotiate { reason: String }, @@ -691,8 +691,10 @@ mod tests { let result = negotiator.negotiate(&remote_caps).unwrap(); // Should prefer FALCON for bandwidth-constrained scenarios - assert!(result.algorithm == PqAlgorithm::Falcon512 || - result.algorithm == PqAlgorithm::Falcon1024); + assert!( + result.algorithm == PqAlgorithm::Falcon512 + || result.algorithm == PqAlgorithm::Falcon1024 + ); } #[test] diff --git a/crates/synor-crypto/src/sphincs.rs b/crates/synor-crypto/src/sphincs.rs index aec6221..6dd5b05 100644 --- a/crates/synor-crypto/src/sphincs.rs +++ b/crates/synor-crypto/src/sphincs.rs @@ -60,8 +60,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; /// All variants use SHAKE (SHA3-based) for hashing. /// 's' variants have smaller signatures but are slower. /// 'f' variants are faster but have larger signatures. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[derive(Default)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum SphincsVariant { /// 128-bit security, small signatures (~7.8KB) #[default] @@ -119,7 +118,6 @@ impl SphincsVariant { } } - /// SPHINCS+ public key. #[derive(Clone)] pub struct SphincsPublicKey { @@ -191,7 +189,10 @@ impl std::fmt::Debug for SphincsPublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SphincsPublicKey") .field("variant", &self.variant) - .field("bytes", &hex::encode(&self.bytes[..8.min(self.bytes.len())])) + .field( + "bytes", + &hex::encode(&self.bytes[..8.min(self.bytes.len())]), + ) .finish() } } @@ -500,7 +501,10 @@ mod tests { // Verify with wrong message should fail let wrong_message = b"Wrong message"; - assert!(keypair.public_key().verify(wrong_message, &signature).is_err()); + assert!(keypair + .public_key() + .verify(wrong_message, &signature) + .is_err()); } #[test] diff --git a/crates/synor-dag/src/dagknight.rs b/crates/synor-dag/src/dagknight.rs index d2e6d98..ffbff78 100644 --- a/crates/synor-dag/src/dagknight.rs +++ b/crates/synor-dag/src/dagknight.rs @@ -155,10 +155,7 @@ pub struct DagKnightManager { impl DagKnightManager { /// Creates a new DAGKnight manager with standard 10 BPS configuration. - pub fn new( - dag: Arc, - reachability: Arc, - ) -> Self { + pub fn new(dag: Arc, reachability: Arc) -> Self { Self::with_config(dag, reachability, BlockRateConfig::Standard) } @@ -269,7 +266,8 @@ impl DagKnightManager { let anticone_size = self.calculate_anticone_size(&block_id, parents); // Record observation in latency tracker - self.latency_tracker.record_block(block_id, block_time_ms, anticone_size); + self.latency_tracker + .record_block(block_id, block_time_ms, anticone_size); // Process with underlying GHOSTDAG let data = self.ghostdag.add_block(block_id, parents)?; @@ -292,11 +290,9 @@ impl DagKnightManager { for tip in tips { if tip != *block_id && !parents.contains(&tip) { // Check if tip is in the past of any parent - let in_past = parents.iter().any(|p| { - self.reachability - .is_ancestor(p, &tip) - .unwrap_or(false) - }); + let in_past = parents + .iter() + .any(|p| self.reachability.is_ancestor(p, &tip).unwrap_or(false)); if !in_past { anticone_count += 1; @@ -375,7 +371,8 @@ impl DagKnightManager { let sigma_multiplier = confidence.sigma_multiplier(); // Required depth scales with variance and confidence level - let required_depth = (self.block_rate_bps * (mean_delay + sigma * sigma_multiplier)).ceil() as u64; + let required_depth = + (self.block_rate_bps * (mean_delay + sigma * sigma_multiplier)).ceil() as u64; // Current confidence based on actual depth let current_confidence = if depth >= required_depth { @@ -388,7 +385,8 @@ impl DagKnightManager { // Time to reach required depth let blocks_needed = required_depth.saturating_sub(depth); let time_per_block_ms = 1000.0 / self.block_rate_bps; - let estimated_time = Duration::from_millis((blocks_needed as f64 * time_per_block_ms) as u64); + let estimated_time = + Duration::from_millis((blocks_needed as f64 * time_per_block_ms) as u64); // Block is final if depth exceeds finality threshold for this block rate let is_final = depth >= self.finality_depth(); @@ -506,7 +504,10 @@ impl std::fmt::Debug for DagKnightManager { .field("block_rate_config", &self.block_rate_config) .field("block_rate_bps", &self.block_rate_bps) .field("adaptive_k", &*self.adaptive_k.read()) - .field("k_bounds", &format!("{}-{}", self.k_bounds.min_k, self.k_bounds.max_k)) + .field( + "k_bounds", + &format!("{}-{}", self.k_bounds.min_k, self.k_bounds.max_k), + ) .field("mean_delay_ms", &stats.mean_delay_ms) .field("sample_count", &stats.sample_count) .finish() @@ -534,10 +535,7 @@ pub fn calculate_optimal_k(network_delay_ms: f64, block_rate_bps: f64) -> u8 { } /// Calculates the optimal k for a specific block rate configuration. -pub fn calculate_optimal_k_for_config( - network_delay_ms: f64, - config: BlockRateConfig, -) -> u8 { +pub fn calculate_optimal_k_for_config(network_delay_ms: f64, config: BlockRateConfig) -> u8 { let bounds = AdaptiveKBounds::for_block_rate(config); let delay_secs = network_delay_ms / 1000.0; let k = (config.bps() * delay_secs * SAFETY_MARGIN).ceil() as u16; @@ -578,7 +576,9 @@ mod tests { (dag, reachability, dagknight) } - fn setup_test_dag_with_config(config: BlockRateConfig) -> (Arc, Arc, DagKnightManager) { + fn setup_test_dag_with_config( + config: BlockRateConfig, + ) -> (Arc, Arc, DagKnightManager) { let genesis = make_block_id(0); let dag = Arc::new(BlockDag::new(genesis, 0)); let reachability = Arc::new(ReachabilityStore::new(genesis)); @@ -671,14 +671,19 @@ mod tests { let tps_poor = estimate_throughput(10.0, 100, 40.0); // Good network should have higher throughput - assert!(tps_good > tps_poor, "tps_good={} should be > tps_poor={}", tps_good, tps_poor); + assert!( + tps_good > tps_poor, + "tps_good={} should be > tps_poor={}", + tps_good, + tps_poor + ); } #[test] fn test_throughput_by_config() { // At same network conditions, higher BPS = higher theoretical TPS - let tps_10 = estimate_throughput(10.0, 100, 20.0); // 10 BPS - let tps_32 = estimate_throughput(32.0, 100, 20.0); // 32 BPS + let tps_10 = estimate_throughput(10.0, 100, 20.0); // 10 BPS + let tps_32 = estimate_throughput(32.0, 100, 20.0); // 32 BPS let tps_100 = estimate_throughput(100.0, 100, 20.0); // 100 BPS // Higher block rates give higher TPS (with network overhead) @@ -698,19 +703,37 @@ mod tests { let maximum_time_hrs = maximum.finality_depth() as f64 / 100.0 / 3600.0; // Should all be approximately 2.4 hours (allow some variance) - assert!((standard_time_hrs - 2.4).abs() < 0.1, "standard: {}", standard_time_hrs); - assert!((enhanced_time_hrs - 2.4).abs() < 0.1, "enhanced: {}", enhanced_time_hrs); - assert!((maximum_time_hrs - 2.4).abs() < 0.1, "maximum: {}", maximum_time_hrs); + assert!( + (standard_time_hrs - 2.4).abs() < 0.1, + "standard: {}", + standard_time_hrs + ); + assert!( + (enhanced_time_hrs - 2.4).abs() < 0.1, + "enhanced: {}", + enhanced_time_hrs + ); + assert!( + (maximum_time_hrs - 2.4).abs() < 0.1, + "maximum: {}", + maximum_time_hrs + ); } #[test] fn test_confidence_levels() { - assert!(ConfirmationConfidence::VeryHigh.sigma_multiplier() - > ConfirmationConfidence::High.sigma_multiplier()); - assert!(ConfirmationConfidence::High.sigma_multiplier() - > ConfirmationConfidence::Medium.sigma_multiplier()); - assert!(ConfirmationConfidence::Medium.sigma_multiplier() - > ConfirmationConfidence::Low.sigma_multiplier()); + assert!( + ConfirmationConfidence::VeryHigh.sigma_multiplier() + > ConfirmationConfidence::High.sigma_multiplier() + ); + assert!( + ConfirmationConfidence::High.sigma_multiplier() + > ConfirmationConfidence::Medium.sigma_multiplier() + ); + assert!( + ConfirmationConfidence::Medium.sigma_multiplier() + > ConfirmationConfidence::Low.sigma_multiplier() + ); } #[test] diff --git a/crates/synor-dag/src/latency.rs b/crates/synor-dag/src/latency.rs index 87b3486..1cebedb 100644 --- a/crates/synor-dag/src/latency.rs +++ b/crates/synor-dag/src/latency.rs @@ -98,12 +98,7 @@ impl LatencyTracker { /// * `block_id` - Hash of the observed block /// * `block_time_ms` - Timestamp from block header (Unix ms) /// * `anticone_size` - Number of blocks in the anticone at observation time - pub fn record_block( - &self, - block_id: BlockId, - block_time_ms: u64, - anticone_size: usize, - ) { + pub fn record_block(&self, block_id: BlockId, block_time_ms: u64, anticone_size: usize) { let local_time = Instant::now(); let now_ms = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -208,7 +203,10 @@ impl LatencyTracker { let anticone_growth_rate = if n > 1 { let first = samples.front().unwrap(); let last = samples.back().unwrap(); - let time_span_secs = last.local_time.duration_since(first.local_time).as_secs_f64(); + let time_span_secs = last + .local_time + .duration_since(first.local_time) + .as_secs_f64(); if time_span_secs > 0.0 { let total_anticone_growth: usize = samples.iter().map(|s| s.anticone_size).sum(); diff --git a/crates/synor-dag/src/lib.rs b/crates/synor-dag/src/lib.rs index 816cda5..fa505a2 100644 --- a/crates/synor-dag/src/lib.rs +++ b/crates/synor-dag/src/lib.rs @@ -32,8 +32,8 @@ pub mod reachability; pub use dag::{BlockDag, BlockRelations, DagError}; pub use dagknight::{ - calculate_optimal_k, calculate_optimal_k_for_config, estimate_throughput, - AdaptiveKBounds, ConfirmationConfidence, ConfirmationStatus, DagKnightManager, + calculate_optimal_k, calculate_optimal_k_for_config, estimate_throughput, AdaptiveKBounds, + ConfirmationConfidence, ConfirmationStatus, DagKnightManager, }; pub use ghostdag::{GhostdagData, GhostdagError, GhostdagManager}; pub use latency::{LatencySample, LatencyStats, LatencyTracker}; @@ -116,27 +116,27 @@ impl BlockRateConfig { /// Returns the merge depth adjusted for block rate. pub const fn merge_depth(&self) -> u64 { match self { - BlockRateConfig::Standard => 3600, // ~6 min at 10 bps - BlockRateConfig::Enhanced => 11520, // ~6 min at 32 bps - BlockRateConfig::Maximum => 36000, // ~6 min at 100 bps + BlockRateConfig::Standard => 3600, // ~6 min at 10 bps + BlockRateConfig::Enhanced => 11520, // ~6 min at 32 bps + BlockRateConfig::Maximum => 36000, // ~6 min at 100 bps } } /// Returns the finality depth adjusted for block rate. pub const fn finality_depth(&self) -> u64 { match self { - BlockRateConfig::Standard => 86400, // ~2.4 hours at 10 bps - BlockRateConfig::Enhanced => 276480, // ~2.4 hours at 32 bps - BlockRateConfig::Maximum => 864000, // ~2.4 hours at 100 bps + BlockRateConfig::Standard => 86400, // ~2.4 hours at 10 bps + BlockRateConfig::Enhanced => 276480, // ~2.4 hours at 32 bps + BlockRateConfig::Maximum => 864000, // ~2.4 hours at 100 bps } } /// Returns the pruning depth adjusted for block rate. pub const fn pruning_depth(&self) -> u64 { match self { - BlockRateConfig::Standard => 288_000, // ~8 hours at 10 bps - BlockRateConfig::Enhanced => 921_600, // ~8 hours at 32 bps - BlockRateConfig::Maximum => 2_880_000, // ~8 hours at 100 bps + BlockRateConfig::Standard => 288_000, // ~8 hours at 10 bps + BlockRateConfig::Enhanced => 921_600, // ~8 hours at 32 bps + BlockRateConfig::Maximum => 2_880_000, // ~8 hours at 100 bps } } } diff --git a/crates/synor-database/src/document.rs b/crates/synor-database/src/document.rs index 8b6db67..0859124 100644 --- a/crates/synor-database/src/document.rs +++ b/crates/synor-database/src/document.rs @@ -42,7 +42,9 @@ impl DocumentId { let bytes = hex::decode(s) .map_err(|_| DatabaseError::InvalidOperation("Invalid hex string".into()))?; if bytes.len() != 32 { - return Err(DatabaseError::InvalidOperation("Invalid document ID length".into())); + return Err(DatabaseError::InvalidOperation( + "Invalid document ID length".into(), + )); } let mut arr = [0u8; 32]; arr.copy_from_slice(&bytes); @@ -249,7 +251,11 @@ impl Collection { } /// Updates documents matching a filter. - pub fn update_many(&self, filter: &DocumentFilter, update: JsonValue) -> Result { + pub fn update_many( + &self, + filter: &DocumentFilter, + update: JsonValue, + ) -> Result { let mut docs = self.documents.write(); let mut count = 0; for doc in docs.values_mut() { @@ -321,60 +327,71 @@ enum FilterCondition { impl DocumentFilter { /// Creates a new empty filter (matches all). pub fn new() -> Self { - Self { conditions: Vec::new() } + Self { + conditions: Vec::new(), + } } /// Equality condition. pub fn eq(mut self, field: impl Into, value: JsonValue) -> Self { - self.conditions.push(FilterCondition::Eq(field.into(), value)); + self.conditions + .push(FilterCondition::Eq(field.into(), value)); self } /// Not equal condition. pub fn ne(mut self, field: impl Into, value: JsonValue) -> Self { - self.conditions.push(FilterCondition::Ne(field.into(), value)); + self.conditions + .push(FilterCondition::Ne(field.into(), value)); self } /// Greater than. pub fn gt(mut self, field: impl Into, value: JsonValue) -> Self { - self.conditions.push(FilterCondition::Gt(field.into(), value)); + self.conditions + .push(FilterCondition::Gt(field.into(), value)); self } /// Greater than or equal. pub fn gte(mut self, field: impl Into, value: JsonValue) -> Self { - self.conditions.push(FilterCondition::Gte(field.into(), value)); + self.conditions + .push(FilterCondition::Gte(field.into(), value)); self } /// Less than. pub fn lt(mut self, field: impl Into, value: JsonValue) -> Self { - self.conditions.push(FilterCondition::Lt(field.into(), value)); + self.conditions + .push(FilterCondition::Lt(field.into(), value)); self } /// Less than or equal. pub fn lte(mut self, field: impl Into, value: JsonValue) -> Self { - self.conditions.push(FilterCondition::Lte(field.into(), value)); + self.conditions + .push(FilterCondition::Lte(field.into(), value)); self } /// In array. pub fn in_array(mut self, field: impl Into, values: Vec) -> Self { - self.conditions.push(FilterCondition::In(field.into(), values)); + self.conditions + .push(FilterCondition::In(field.into(), values)); self } /// String contains. pub fn contains(mut self, field: impl Into, substring: impl Into) -> Self { - self.conditions.push(FilterCondition::Contains(field.into(), substring.into())); + self.conditions + .push(FilterCondition::Contains(field.into(), substring.into())); self } /// Field exists. pub fn exists(mut self, field: impl Into, exists: bool) -> Self { - self.conditions.push(FilterCondition::Exists(field.into(), exists)); + self.conditions + .push(FilterCondition::Exists(field.into(), exists)); self } @@ -396,7 +413,9 @@ impl DocumentFilter { return true; } - self.conditions.iter().all(|cond| self.eval_condition(cond, doc)) + self.conditions + .iter() + .all(|cond| self.eval_condition(cond, doc)) } fn eval_condition(&self, cond: &FilterCondition, doc: &Document) -> bool { @@ -419,27 +438,21 @@ impl DocumentFilter { FilterCondition::Lte(field, value) => { self.compare_values(doc.get_nested(field), value, |a, b| a <= b) } - FilterCondition::In(field, values) => { - doc.get_nested(field) - .map(|v| values.contains(v)) - .unwrap_or(false) - } - FilterCondition::Contains(field, substring) => { - doc.get_nested(field) - .and_then(|v| v.as_str()) - .map(|s| s.contains(substring)) - .unwrap_or(false) - } + FilterCondition::In(field, values) => doc + .get_nested(field) + .map(|v| values.contains(v)) + .unwrap_or(false), + FilterCondition::Contains(field, substring) => doc + .get_nested(field) + .and_then(|v| v.as_str()) + .map(|s| s.contains(substring)) + .unwrap_or(false), FilterCondition::Exists(field, should_exist) => { let exists = doc.get_nested(field).is_some(); exists == *should_exist } - FilterCondition::And(filters) => { - filters.iter().all(|f| f.matches(doc)) - } - FilterCondition::Or(filters) => { - filters.iter().any(|f| f.matches(doc)) - } + FilterCondition::And(filters) => filters.iter().all(|f| f.matches(doc)), + FilterCondition::Or(filters) => filters.iter().any(|f| f.matches(doc)), } } @@ -448,12 +461,10 @@ impl DocumentFilter { F: Fn(f64, f64) -> bool, { match (a, b) { - (Some(JsonValue::Number(a)), JsonValue::Number(b)) => { - match (a.as_f64(), b.as_f64()) { - (Some(a), Some(b)) => cmp(a, b), - _ => false, - } - } + (Some(JsonValue::Number(a)), JsonValue::Number(b)) => match (a.as_f64(), b.as_f64()) { + (Some(a), Some(b)) => cmp(a, b), + _ => false, + }, _ => false, } } @@ -512,7 +523,11 @@ impl DocumentStore { } /// Finds documents in a collection. - pub fn find(&self, collection: &str, filter: &DocumentFilter) -> Result, DatabaseError> { + pub fn find( + &self, + collection: &str, + filter: &DocumentFilter, + ) -> Result, DatabaseError> { let collections = self.collections.read(); let coll = collections .get(collection) @@ -521,7 +536,11 @@ impl DocumentStore { } /// Finds one document. - pub fn find_one(&self, collection: &str, filter: &DocumentFilter) -> Result, DatabaseError> { + pub fn find_one( + &self, + collection: &str, + filter: &DocumentFilter, + ) -> Result, DatabaseError> { let collections = self.collections.read(); let coll = collections .get(collection) @@ -530,7 +549,11 @@ impl DocumentStore { } /// Finds a document by ID. - pub fn find_by_id(&self, collection: &str, id: &DocumentId) -> Result, DatabaseError> { + pub fn find_by_id( + &self, + collection: &str, + id: &DocumentId, + ) -> Result, DatabaseError> { let collections = self.collections.read(); let coll = collections .get(collection) @@ -539,7 +562,12 @@ impl DocumentStore { } /// Updates a document by ID. - pub fn update_by_id(&self, collection: &str, id: &DocumentId, update: JsonValue) -> Result { + pub fn update_by_id( + &self, + collection: &str, + id: &DocumentId, + update: JsonValue, + ) -> Result { let collections = self.collections.read(); let coll = collections .get(collection) @@ -584,7 +612,8 @@ mod tests { fn test_collection_insert_find() { let coll = Collection::new("users"); - coll.insert_one(json!({"name": "Alice", "age": 30})).unwrap(); + coll.insert_one(json!({"name": "Alice", "age": 30})) + .unwrap(); coll.insert_one(json!({"name": "Bob", "age": 25})).unwrap(); let filter = DocumentFilter::new().eq("name", json!("Alice")); @@ -597,9 +626,11 @@ mod tests { fn test_filter_comparison() { let coll = Collection::new("users"); - coll.insert_one(json!({"name": "Alice", "age": 30})).unwrap(); + coll.insert_one(json!({"name": "Alice", "age": 30})) + .unwrap(); coll.insert_one(json!({"name": "Bob", "age": 25})).unwrap(); - coll.insert_one(json!({"name": "Charlie", "age": 35})).unwrap(); + coll.insert_one(json!({"name": "Charlie", "age": 35})) + .unwrap(); let filter = DocumentFilter::new().gte("age", json!(30)); let results = coll.find(&filter); @@ -622,7 +653,9 @@ mod tests { #[test] fn test_update_document() { let coll = Collection::new("users"); - let id = coll.insert_one(json!({"name": "Alice", "age": 30})).unwrap(); + let id = coll + .insert_one(json!({"name": "Alice", "age": 30})) + .unwrap(); coll.update_by_id(&id, json!({"age": 31})).unwrap(); diff --git a/crates/synor-database/src/gateway/auth.rs b/crates/synor-database/src/gateway/auth.rs index 1f7dbb4..9950e72 100644 --- a/crates/synor-database/src/gateway/auth.rs +++ b/crates/synor-database/src/gateway/auth.rs @@ -1,9 +1,9 @@ //! Authentication and authorization for Database Gateway. +use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; -use parking_lot::RwLock; /// API key for authentication. #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/crates/synor-database/src/gateway/handlers.rs b/crates/synor-database/src/gateway/handlers.rs index d52b28f..ef154a1 100644 --- a/crates/synor-database/src/gateway/handlers.rs +++ b/crates/synor-database/src/gateway/handlers.rs @@ -272,19 +272,13 @@ pub fn json_to_filter(json: &JsonValue) -> Option { // Handle $and if let Some(and_arr) = obj.get("$and").and_then(|v| v.as_array()) { - let filters: Vec = and_arr - .iter() - .filter_map(json_to_filter) - .collect(); + let filters: Vec = and_arr.iter().filter_map(json_to_filter).collect(); return Some(Filter::And(filters)); } // Handle $or if let Some(or_arr) = obj.get("$or").and_then(|v| v.as_array()) { - let filters: Vec = or_arr - .iter() - .filter_map(json_to_filter) - .collect(); + let filters: Vec = or_arr.iter().filter_map(json_to_filter).collect(); return Some(Filter::Or(filters)); } diff --git a/crates/synor-database/src/gateway/router.rs b/crates/synor-database/src/gateway/router.rs index 8aef32d..6cb1bf6 100644 --- a/crates/synor-database/src/gateway/router.rs +++ b/crates/synor-database/src/gateway/router.rs @@ -137,7 +137,12 @@ async fn kv_get( // For demo, use a default database let db = match get_default_database(&state) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::::error("No database"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::::error("No database")), + ) + } }; state.record_read(); @@ -153,7 +158,12 @@ async fn kv_set( ) -> impl IntoResponse { let db = match get_default_database(&state) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::::error("No database"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::::error("No database")), + ) + } }; state.record_write(req.value.len() as u64); @@ -168,7 +178,12 @@ async fn kv_delete( ) -> impl IntoResponse { let db = match get_default_database(&state) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::::error("No database"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::::error("No database")), + ) + } }; let response = handle_kv_delete(db.kv(), &key); @@ -182,7 +197,12 @@ async fn kv_batch( ) -> impl IntoResponse { let db = match get_default_database(&state) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::::error("No database"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::::error("No database")), + ) + } }; let response = handle_kv_batch(db.kv(), req); @@ -217,7 +237,9 @@ async fn list_databases( }) .collect(); - Json(ApiResponse::ok(ListDatabasesResponse { databases: response })) + Json(ApiResponse::ok(ListDatabasesResponse { + databases: response, + })) } async fn create_database( @@ -250,7 +272,10 @@ async fn create_database( })), ) } - Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))), + Err(e) => ( + StatusCode::BAD_REQUEST, + Json(ApiResponse::error(e.to_string())), + ), } } @@ -302,12 +327,20 @@ async fn create_collection( ) -> impl IntoResponse { let db = match get_database(&state, &db_name) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::::error("Database not found"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::::error("Database not found")), + ) + } }; match db.documents().create_collection(&req.name) { Ok(_) => (StatusCode::CREATED, Json(ApiResponse::ok(true))), - Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::::error(e.to_string()))), + Err(e) => ( + StatusCode::BAD_REQUEST, + Json(ApiResponse::::error(e.to_string())), + ), } } @@ -349,15 +382,20 @@ async fn query_documents( ) -> impl IntoResponse { let db = match get_database(&state, &db_name) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::::error("Database not found"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::::error( + "Database not found", + )), + ) + } }; state.record_read(); // Build query - let mut query = Query::new(&coll_name) - .skip(req.skip) - .limit(req.limit); + let mut query = Query::new(&coll_name).skip(req.skip).limit(req.limit); // Add filter if let Some(filter_json) = &req.filter { @@ -384,8 +422,14 @@ async fn query_documents( } match db.query().execute(&query) { - Ok(result) => (StatusCode::OK, Json(ApiResponse::ok(QueryDocumentsResponse::from(result)))), - Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))), + Ok(result) => ( + StatusCode::OK, + Json(ApiResponse::ok(QueryDocumentsResponse::from(result))), + ), + Err(e) => ( + StatusCode::BAD_REQUEST, + Json(ApiResponse::error(e.to_string())), + ), } } @@ -396,15 +440,25 @@ async fn insert_document( ) -> impl IntoResponse { let db = match get_database(&state, &db_name) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::::error("Database not found"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::::error("Database not found")), + ) + } }; - let size = serde_json::to_vec(&req.document).map(|v| v.len()).unwrap_or(0); + let size = serde_json::to_vec(&req.document) + .map(|v| v.len()) + .unwrap_or(0); state.record_write(size as u64); match db.documents().insert(&coll_name, req.document) { Ok(id) => (StatusCode::CREATED, Json(ApiResponse::ok(id.to_hex()))), - Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))), + Err(e) => ( + StatusCode::BAD_REQUEST, + Json(ApiResponse::error(e.to_string())), + ), } } @@ -415,7 +469,12 @@ async fn insert_many_documents( ) -> impl IntoResponse { let db = match get_database(&state, &db_name) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::>::error("Database not found"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::>::error("Database not found")), + ) + } }; let mut ids = Vec::with_capacity(req.documents.len()); @@ -425,7 +484,12 @@ async fn insert_many_documents( total_size += serde_json::to_vec(&doc).map(|v| v.len()).unwrap_or(0) as u64; match db.documents().insert(&coll_name, doc) { Ok(id) => ids.push(id.to_hex()), - Err(e) => return (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))), + Err(e) => { + return ( + StatusCode::BAD_REQUEST, + Json(ApiResponse::error(e.to_string())), + ) + } } } @@ -477,7 +541,9 @@ async fn update_document( Err(e) => return Json(ApiResponse::error(e.to_string())), }; - let update_size = serde_json::to_vec(&req.update).map(|v| v.len()).unwrap_or(0); + let update_size = serde_json::to_vec(&req.update) + .map(|v| v.len()) + .unwrap_or(0); state.record_write(update_size as u64); match db.documents().update_by_id(&coll_name, &id, req.update) { @@ -519,7 +585,12 @@ async fn insert_embeddings( ) -> impl IntoResponse { let db = match get_database(&state, &db_name) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::::error("Database not found"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::::error("Database not found")), + ) + } }; let mut count = 0; @@ -533,7 +604,10 @@ async fn insert_embeddings( } if let Err(e) = db.vectors().insert(embedding) { - return (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))); + return ( + StatusCode::BAD_REQUEST, + Json(ApiResponse::error(e.to_string())), + ); } count += 1; } @@ -549,7 +623,14 @@ async fn vector_search( ) -> impl IntoResponse { let db = match get_database(&state, &db_name) { Some(db) => db, - None => return (StatusCode::NOT_FOUND, Json(ApiResponse::::error("Database not found"))), + None => { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse::::error( + "Database not found", + )), + ) + } }; state.record_vector_search(); @@ -563,9 +644,18 @@ async fn vector_search( Ok(results) => { let count = results.len(); let matches: Vec = results.into_iter().map(Into::into).collect(); - (StatusCode::OK, Json(ApiResponse::ok(VectorSearchResponse { results: matches, count }))) + ( + StatusCode::OK, + Json(ApiResponse::ok(VectorSearchResponse { + results: matches, + count, + })), + ) } - Err(e) => (StatusCode::BAD_REQUEST, Json(ApiResponse::error(e.to_string()))), + Err(e) => ( + StatusCode::BAD_REQUEST, + Json(ApiResponse::error(e.to_string())), + ), } } diff --git a/crates/synor-database/src/gateway/server.rs b/crates/synor-database/src/gateway/server.rs index 86d5871..f49206f 100644 --- a/crates/synor-database/src/gateway/server.rs +++ b/crates/synor-database/src/gateway/server.rs @@ -114,10 +114,7 @@ impl GatewayServer { tracing::error!("Failed to create default database: {}", e); } - let state = Arc::new(AppState::new( - self.db_manager.clone(), - self.auth.clone(), - )); + let state = Arc::new(AppState::new(self.db_manager.clone(), self.auth.clone())); let app = create_router(state); diff --git a/crates/synor-database/src/graph/edge.rs b/crates/synor-database/src/graph/edge.rs index 11eb475..143db9e 100644 --- a/crates/synor-database/src/graph/edge.rs +++ b/crates/synor-database/src/graph/edge.rs @@ -86,7 +86,12 @@ pub struct Edge { impl Edge { /// Creates a new directed edge. - pub fn new(source: NodeId, target: NodeId, edge_type: impl Into, properties: JsonValue) -> Self { + pub fn new( + source: NodeId, + target: NodeId, + edge_type: impl Into, + properties: JsonValue, + ) -> Self { let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() @@ -105,7 +110,12 @@ impl Edge { } /// Creates an undirected edge. - pub fn undirected(source: NodeId, target: NodeId, edge_type: impl Into, properties: JsonValue) -> Self { + pub fn undirected( + source: NodeId, + target: NodeId, + edge_type: impl Into, + properties: JsonValue, + ) -> Self { let mut edge = Self::new(source, target, edge_type, properties); edge.directed = false; edge @@ -138,8 +148,8 @@ impl Edge { /// Checks if this edge connects two specific nodes. pub fn connects_pair(&self, a: &NodeId, b: &NodeId) -> bool { - (&self.source == a && &self.target == b) || - (!self.directed && &self.source == b && &self.target == a) + (&self.source == a && &self.target == b) + || (!self.directed && &self.source == b && &self.target == a) } /// Gets a property value. @@ -156,7 +166,9 @@ impl Edge { /// Checks if the edge matches a property filter. pub fn matches_properties(&self, filter: &JsonValue) -> bool { - if let (Some(filter_obj), Some(props_obj)) = (filter.as_object(), self.properties.as_object()) { + if let (Some(filter_obj), Some(props_obj)) = + (filter.as_object(), self.properties.as_object()) + { for (key, expected) in filter_obj { if let Some(actual) = props_obj.get(key) { if actual != expected { @@ -216,7 +228,12 @@ impl EdgeBuilder { /// Builds the edge. pub fn build(self) -> Edge { - let mut edge = Edge::new(self.source, self.target, self.edge_type, JsonValue::Object(self.properties)); + let mut edge = Edge::new( + self.source, + self.target, + self.edge_type, + JsonValue::Object(self.properties), + ); edge.directed = self.directed; edge.weight = self.weight; edge @@ -264,7 +281,10 @@ mod tests { assert!(!edge.directed); assert_eq!(edge.weight, 2.5); - assert_eq!(edge.get_property("percentage"), Some(&serde_json::json!(50))); + assert_eq!( + edge.get_property("percentage"), + Some(&serde_json::json!(50)) + ); } #[test] diff --git a/crates/synor-database/src/graph/node.rs b/crates/synor-database/src/graph/node.rs index 0937c38..bfb9c86 100644 --- a/crates/synor-database/src/graph/node.rs +++ b/crates/synor-database/src/graph/node.rs @@ -172,7 +172,9 @@ impl Node { /// Checks if the node matches a property filter. pub fn matches_properties(&self, filter: &JsonValue) -> bool { - if let (Some(filter_obj), Some(props_obj)) = (filter.as_object(), self.properties.as_object()) { + if let (Some(filter_obj), Some(props_obj)) = + (filter.as_object(), self.properties.as_object()) + { for (key, expected) in filter_obj { if let Some(actual) = props_obj.get(key) { if actual != expected { @@ -258,10 +260,7 @@ mod tests { assert!(node.has_label("User")); assert!(!node.has_label("Admin")); - assert_eq!( - node.get_property("name"), - Some(&serde_json::json!("Alice")) - ); + assert_eq!(node.get_property("name"), Some(&serde_json::json!("Alice"))); } #[test] diff --git a/crates/synor-database/src/graph/path.rs b/crates/synor-database/src/graph/path.rs index 16e3508..f4f56a9 100644 --- a/crates/synor-database/src/graph/path.rs +++ b/crates/synor-database/src/graph/path.rs @@ -60,7 +60,10 @@ impl Eq for DijkstraState {} impl Ord for DijkstraState { fn cmp(&self, other: &Self) -> Ordering { // Reverse ordering for min-heap - other.distance.partial_cmp(&self.distance).unwrap_or(Ordering::Equal) + other + .distance + .partial_cmp(&self.distance) + .unwrap_or(Ordering::Equal) } } @@ -140,9 +143,16 @@ impl<'a> PathFinder<'a> { let mut visited = HashSet::new(); distances.insert(*from, 0.0); - heap.push(DijkstraState { node: *from, distance: 0.0 }); + heap.push(DijkstraState { + node: *from, + distance: 0.0, + }); - while let Some(DijkstraState { node: current, distance: dist }) = heap.pop() { + while let Some(DijkstraState { + node: current, + distance: dist, + }) = heap.pop() + { if ¤t == to { // Reconstruct path let mut path = vec![current]; @@ -181,12 +191,18 @@ impl<'a> PathFinder<'a> { } let new_dist = dist + edge.weight; - let is_shorter = distances.get(&neighbor).map(|&d| new_dist < d).unwrap_or(true); + let is_shorter = distances + .get(&neighbor) + .map(|&d| new_dist < d) + .unwrap_or(true); if is_shorter { distances.insert(neighbor, new_dist); previous.insert(neighbor, (current, edge.clone())); - heap.push(DijkstraState { node: neighbor, distance: new_dist }); + heap.push(DijkstraState { + node: neighbor, + distance: new_dist, + }); } } } @@ -251,7 +267,9 @@ impl<'a> PathFinder<'a> { path.push(neighbor); edges.push(edge.clone()); - self.find_all_paths_dfs(&neighbor, target, max_length, path, edges, visited, results); + self.find_all_paths_dfs( + &neighbor, target, max_length, path, edges, visited, results, + ); path.pop(); edges.pop(); @@ -261,7 +279,12 @@ impl<'a> PathFinder<'a> { } /// Finds the shortest path considering only specific edge types. - pub fn shortest_path_by_type(&self, from: &NodeId, to: &NodeId, edge_types: &[String]) -> PathResult { + pub fn shortest_path_by_type( + &self, + from: &NodeId, + to: &NodeId, + edge_types: &[String], + ) -> PathResult { if from == to { return PathResult::found(vec![*from], Vec::new(), 0.0); } @@ -377,11 +400,21 @@ mod tests { let d = store.create_node(vec![], serde_json::json!({"name": "D"})); let e = store.create_node(vec![], serde_json::json!({"name": "E"})); - store.create_edge(a, b, "LINK", serde_json::json!({})).unwrap(); - store.create_edge(b, c, "LINK", serde_json::json!({})).unwrap(); - store.create_edge(c, d, "LINK", serde_json::json!({})).unwrap(); - store.create_edge(a, e, "LINK", serde_json::json!({})).unwrap(); - store.create_edge(e, d, "LINK", serde_json::json!({})).unwrap(); + store + .create_edge(a, b, "LINK", serde_json::json!({})) + .unwrap(); + store + .create_edge(b, c, "LINK", serde_json::json!({})) + .unwrap(); + store + .create_edge(c, d, "LINK", serde_json::json!({})) + .unwrap(); + store + .create_edge(a, e, "LINK", serde_json::json!({})) + .unwrap(); + store + .create_edge(e, d, "LINK", serde_json::json!({})) + .unwrap(); store } @@ -392,8 +425,14 @@ mod tests { let finder = PathFinder::new(&store); let nodes: Vec<_> = store.find_nodes(None, &serde_json::json!({})); - let a = nodes.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("A"))).unwrap(); - let d = nodes.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("D"))).unwrap(); + let a = nodes + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("A"))) + .unwrap(); + let d = nodes + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("D"))) + .unwrap(); let result = finder.shortest_path_bfs(&a.id, &d.id); @@ -413,13 +452,19 @@ mod tests { // A --(3.0)--> C let mut edge1 = super::super::edge::Edge::new(a, b, "LINK", serde_json::json!({})); edge1.weight = 1.0; - store.create_edge(a, b, "LINK", serde_json::json!({})).unwrap(); + store + .create_edge(a, b, "LINK", serde_json::json!({})) + .unwrap(); let mut edge2 = super::super::edge::Edge::new(b, c, "LINK", serde_json::json!({})); edge2.weight = 1.0; - store.create_edge(b, c, "LINK", serde_json::json!({})).unwrap(); + store + .create_edge(b, c, "LINK", serde_json::json!({})) + .unwrap(); - store.create_edge(a, c, "DIRECT", serde_json::json!({})).unwrap(); + store + .create_edge(a, c, "DIRECT", serde_json::json!({})) + .unwrap(); let finder = PathFinder::new(&store); let result = finder.shortest_path_dijkstra(&a, &c); @@ -449,8 +494,14 @@ mod tests { let finder = PathFinder::new(&store); let nodes: Vec<_> = store.find_nodes(None, &serde_json::json!({})); - let a = nodes.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("A"))).unwrap(); - let d = nodes.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("D"))).unwrap(); + let a = nodes + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("A"))) + .unwrap(); + let d = nodes + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("D"))) + .unwrap(); let paths = finder.all_paths(&a.id, &d.id, 5); @@ -463,8 +514,14 @@ mod tests { let finder = PathFinder::new(&store); let nodes: Vec<_> = store.find_nodes(None, &serde_json::json!({})); - let a = nodes.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("A"))).unwrap(); - let d = nodes.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("D"))).unwrap(); + let a = nodes + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("A"))) + .unwrap(); + let d = nodes + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("D"))) + .unwrap(); assert!(finder.path_exists(&a.id, &d.id)); } @@ -475,9 +532,18 @@ mod tests { let finder = PathFinder::new(&store); let nodes: Vec<_> = store.find_nodes(None, &serde_json::json!({})); - let a = nodes.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("A"))).unwrap(); - let d = nodes.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("D"))).unwrap(); - let b = nodes.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("B"))).unwrap(); + let a = nodes + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("A"))) + .unwrap(); + let d = nodes + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("D"))) + .unwrap(); + let b = nodes + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("B"))) + .unwrap(); assert_eq!(finder.distance(&a.id, &b.id), Some(1)); assert_eq!(finder.distance(&a.id, &d.id), Some(2)); // A -> E -> D diff --git a/crates/synor-database/src/graph/query.rs b/crates/synor-database/src/graph/query.rs index 44f0be1..b679c96 100644 --- a/crates/synor-database/src/graph/query.rs +++ b/crates/synor-database/src/graph/query.rs @@ -23,7 +23,10 @@ pub enum GraphQuery { /// DELETE query for removing nodes/edges. Delete { variable: String, detach: bool }, /// SET query for updating properties. - Set { variable: String, properties: JsonValue }, + Set { + variable: String, + properties: JsonValue, + }, } /// Pattern to match in the graph. @@ -78,15 +81,35 @@ pub enum RelationshipDirection { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum WhereClause { /// Property comparison. - PropertyEquals { variable: String, property: String, value: JsonValue }, + PropertyEquals { + variable: String, + property: String, + value: JsonValue, + }, /// Property comparison (not equals). - PropertyNotEquals { variable: String, property: String, value: JsonValue }, + PropertyNotEquals { + variable: String, + property: String, + value: JsonValue, + }, /// Property greater than. - PropertyGt { variable: String, property: String, value: JsonValue }, + PropertyGt { + variable: String, + property: String, + value: JsonValue, + }, /// Property less than. - PropertyLt { variable: String, property: String, value: JsonValue }, + PropertyLt { + variable: String, + property: String, + value: JsonValue, + }, /// Property contains (for text). - PropertyContains { variable: String, property: String, value: String }, + PropertyContains { + variable: String, + property: String, + value: String, + }, /// AND condition. And(Box, Box), /// OR condition. @@ -105,7 +128,10 @@ pub enum ReturnItem { /// Return a property of a variable. Property { variable: String, property: String }, /// Return with an alias. - Alias { item: Box, alias: String }, + Alias { + item: Box, + alias: String, + }, /// Count aggregation. Count(Option), } @@ -114,7 +140,11 @@ pub enum ReturnItem { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum CreateElement { /// Create a node. - Node { variable: Option, labels: Vec, properties: JsonValue }, + Node { + variable: Option, + labels: Vec, + properties: JsonValue, + }, /// Create a relationship. Relationship { from_var: String, @@ -176,7 +206,10 @@ impl GraphQueryParser { } else if upper.starts_with("SET") { Self::parse_set(query) } else { - Err(GraphError::InvalidOperation(format!("Unknown query type: {}", query))) + Err(GraphError::InvalidOperation(format!( + "Unknown query type: {}", + query + ))) } } @@ -185,7 +218,10 @@ impl GraphQueryParser { let upper = query.to_uppercase(); // Find MATCH, WHERE, RETURN, LIMIT positions - let match_end = upper.find("WHERE").or_else(|| upper.find("RETURN")).unwrap_or(query.len()); + let match_end = upper + .find("WHERE") + .or_else(|| upper.find("RETURN")) + .unwrap_or(query.len()); let where_start = upper.find("WHERE"); let return_start = upper.find("RETURN"); let limit_start = upper.find("LIMIT"); @@ -253,7 +289,9 @@ impl GraphQueryParser { } if nodes.is_empty() { - return Err(GraphError::InvalidOperation("No node pattern found".to_string())); + return Err(GraphError::InvalidOperation( + "No node pattern found".to_string(), + )); } // Combine nodes with relationships @@ -264,10 +302,15 @@ impl GraphQueryParser { } } - Ok(MatchPattern { start, relationships }) + Ok(MatchPattern { + start, + relationships, + }) } - fn parse_node_pattern(chars: &mut std::iter::Peekable) -> Result { + fn parse_node_pattern( + chars: &mut std::iter::Peekable, + ) -> Result { // Consume '(' chars.next(); @@ -335,10 +378,16 @@ impl GraphQueryParser { } } - Ok(NodePattern { variable, labels, properties }) + Ok(NodePattern { + variable, + labels, + properties, + }) } - fn parse_relationship_pattern(chars: &mut std::iter::Peekable) -> Result { + fn parse_relationship_pattern( + chars: &mut std::iter::Peekable, + ) -> Result { let mut direction = RelationshipDirection::Undirected; let mut edge_type = None; let mut variable = None; @@ -408,7 +457,11 @@ impl GraphQueryParser { variable, edge_type, direction, - target: NodePattern { variable: None, labels: Vec::new(), properties: None }, + target: NodePattern { + variable: None, + labels: Vec::new(), + properties: None, + }, min_hops, max_hops, }) @@ -476,7 +529,9 @@ impl GraphQueryParser { elements.push(CreateElement::Node { variable: node.variable, labels: node.labels, - properties: node.properties.unwrap_or(JsonValue::Object(serde_json::Map::new())), + properties: node + .properties + .unwrap_or(JsonValue::Object(serde_json::Map::new())), }); } else { break; @@ -488,7 +543,11 @@ impl GraphQueryParser { fn parse_delete(query: &str) -> Result { let detach = query.to_uppercase().starts_with("DETACH"); - let start = if detach { "DETACH DELETE".len() } else { "DELETE".len() }; + let start = if detach { + "DETACH DELETE".len() + } else { + "DELETE".len() + }; let variable = query[start..].trim().to_string(); Ok(GraphQuery::Delete { variable, detach }) @@ -500,19 +559,24 @@ impl GraphQueryParser { let parts: Vec<_> = content.split('=').collect(); if parts.len() != 2 { - return Err(GraphError::InvalidOperation("Invalid SET syntax".to_string())); + return Err(GraphError::InvalidOperation( + "Invalid SET syntax".to_string(), + )); } let var_prop: Vec<_> = parts[0].trim().split('.').collect(); if var_prop.len() != 2 { - return Err(GraphError::InvalidOperation("Invalid SET variable".to_string())); + return Err(GraphError::InvalidOperation( + "Invalid SET variable".to_string(), + )); } let variable = var_prop[0].to_string(); let property = var_prop[1].to_string(); let value_str = parts[1].trim(); - let value: JsonValue = serde_json::from_str(value_str).unwrap_or(JsonValue::String(value_str.to_string())); + let value: JsonValue = + serde_json::from_str(value_str).unwrap_or(JsonValue::String(value_str.to_string())); Ok(GraphQuery::Set { variable, @@ -535,18 +599,21 @@ impl<'a> GraphQueryExecutor<'a> { /// Executes a graph query. pub fn execute(&self, query: &GraphQuery) -> Result { match query { - GraphQuery::Match { pattern, where_clause, return_items, limit } => { - self.execute_match(pattern, where_clause.as_ref(), return_items, *limit) - } - GraphQuery::Create { .. } => { - Err(GraphError::InvalidOperation("CREATE requires mutable access".to_string())) - } - GraphQuery::Delete { .. } => { - Err(GraphError::InvalidOperation("DELETE requires mutable access".to_string())) - } - GraphQuery::Set { .. } => { - Err(GraphError::InvalidOperation("SET requires mutable access".to_string())) - } + GraphQuery::Match { + pattern, + where_clause, + return_items, + limit, + } => self.execute_match(pattern, where_clause.as_ref(), return_items, *limit), + GraphQuery::Create { .. } => Err(GraphError::InvalidOperation( + "CREATE requires mutable access".to_string(), + )), + GraphQuery::Delete { .. } => Err(GraphError::InvalidOperation( + "DELETE requires mutable access".to_string(), + )), + GraphQuery::Set { .. } => Err(GraphError::InvalidOperation( + "SET requires mutable access".to_string(), + )), } } @@ -586,7 +653,11 @@ impl<'a> GraphQueryExecutor<'a> { .depth(rel_pattern.max_hops) .direction(direction) .edge_types( - rel_pattern.edge_type.clone().map(|t| vec![t]).unwrap_or_default(), + rel_pattern + .edge_type + .clone() + .map(|t| vec![t]) + .unwrap_or_default(), ) .labels(rel_pattern.target.labels.clone()); @@ -635,7 +706,10 @@ impl<'a> GraphQueryExecutor<'a> { fn find_matching_nodes(&self, pattern: &NodePattern) -> Vec { let label = pattern.labels.first().map(|s| s.as_str()); - let filter = pattern.properties.clone().unwrap_or(JsonValue::Object(serde_json::Map::new())); + let filter = pattern + .properties + .clone() + .unwrap_or(JsonValue::Object(serde_json::Map::new())); self.store.find_nodes(label, &filter) } @@ -657,7 +731,11 @@ impl<'a> GraphQueryExecutor<'a> { }) } - fn get_column_names(&self, return_items: &[ReturnItem], bindings: &[HashMap]) -> Vec { + fn get_column_names( + &self, + return_items: &[ReturnItem], + bindings: &[HashMap], + ) -> Vec { let mut columns = Vec::new(); for item in return_items { @@ -673,7 +751,10 @@ impl<'a> GraphQueryExecutor<'a> { } ReturnItem::Alias { alias, .. } => columns.push(alias.clone()), ReturnItem::Count(var) => { - columns.push(format!("count({})", var.as_ref().map(|s| s.as_str()).unwrap_or("*"))); + columns.push(format!( + "count({})", + var.as_ref().map(|s| s.as_str()).unwrap_or("*") + )); } } } @@ -681,11 +762,18 @@ impl<'a> GraphQueryExecutor<'a> { columns } - fn extract_rows(&self, return_items: &[ReturnItem], bindings: &[HashMap]) -> Vec> { + fn extract_rows( + &self, + return_items: &[ReturnItem], + bindings: &[HashMap], + ) -> Vec> { let mut rows = Vec::new(); // Handle COUNT specially - if return_items.iter().any(|i| matches!(i, ReturnItem::Count(_))) { + if return_items + .iter() + .any(|i| matches!(i, ReturnItem::Count(_))) + { rows.push(vec![JsonValue::Number(bindings.len().into())]); return rows; } @@ -760,8 +848,14 @@ mod tests { if let GraphQuery::Match { pattern, .. } = parsed { assert_eq!(pattern.start.labels, vec!["User".to_string()]); assert_eq!(pattern.relationships.len(), 1); - assert_eq!(pattern.relationships[0].edge_type, Some("FRIEND".to_string())); - assert_eq!(pattern.relationships[0].direction, RelationshipDirection::Outgoing); + assert_eq!( + pattern.relationships[0].edge_type, + Some("FRIEND".to_string()) + ); + assert_eq!( + pattern.relationships[0].direction, + RelationshipDirection::Outgoing + ); } else { panic!("Expected Match query"); } @@ -771,9 +865,14 @@ mod tests { fn test_execute_match() { let store = GraphStore::new(); - let alice = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Alice"})); + let alice = store.create_node( + vec!["User".to_string()], + serde_json::json!({"name": "Alice"}), + ); let bob = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Bob"})); - store.create_edge(alice, bob, "FRIEND", serde_json::json!({})).unwrap(); + store + .create_edge(alice, bob, "FRIEND", serde_json::json!({})) + .unwrap(); let query = GraphQueryParser::parse("MATCH (n:User) RETURN n").unwrap(); let executor = GraphQueryExecutor::new(&store); diff --git a/crates/synor-database/src/graph/store.rs b/crates/synor-database/src/graph/store.rs index f91a148..cc2a51f 100644 --- a/crates/synor-database/src/graph/store.rs +++ b/crates/synor-database/src/graph/store.rs @@ -177,18 +177,8 @@ impl GraphStore { /// Deletes a node and all its connected edges. pub fn delete_node(&self, id: &NodeId) -> Result<(), GraphError> { // Get connected edges - let outgoing: Vec = self - .adjacency - .read() - .get(id) - .cloned() - .unwrap_or_default(); - let incoming: Vec = self - .reverse_adj - .read() - .get(id) - .cloned() - .unwrap_or_default(); + let outgoing: Vec = self.adjacency.read().get(id).cloned().unwrap_or_default(); + let incoming: Vec = self.reverse_adj.read().get(id).cloned().unwrap_or_default(); // Delete all connected edges for edge_id in outgoing.iter().chain(incoming.iter()) { @@ -457,7 +447,12 @@ impl GraphStore { } /// Gets the neighbor node from an edge. - fn get_neighbor_from_edge(&self, edge: &Edge, from: &NodeId, direction: Direction) -> Option { + fn get_neighbor_from_edge( + &self, + edge: &Edge, + from: &NodeId, + direction: Direction, + ) -> Option { match direction { Direction::Outgoing => { if &edge.source == from { @@ -491,7 +486,12 @@ impl GraphStore { } /// Gets neighbors connected by a specific edge type. - pub fn neighbors_by_type(&self, id: &NodeId, edge_type: &str, direction: Direction) -> Vec { + pub fn neighbors_by_type( + &self, + id: &NodeId, + edge_type: &str, + direction: Direction, + ) -> Vec { let edges = self.edges_of(id, direction); let nodes = self.nodes.read(); @@ -565,7 +565,10 @@ mod tests { fn test_create_edge() { let store = GraphStore::new(); - let alice = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Alice"})); + let alice = store.create_node( + vec!["User".to_string()], + serde_json::json!({"name": "Alice"}), + ); let bob = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Bob"})); let edge_id = store @@ -582,12 +585,22 @@ mod tests { fn test_neighbors() { let store = GraphStore::new(); - let alice = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Alice"})); + let alice = store.create_node( + vec!["User".to_string()], + serde_json::json!({"name": "Alice"}), + ); let bob = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Bob"})); - let charlie = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Charlie"})); + let charlie = store.create_node( + vec!["User".to_string()], + serde_json::json!({"name": "Charlie"}), + ); - store.create_edge(alice, bob, "FRIEND", serde_json::json!({})).unwrap(); - store.create_edge(alice, charlie, "FRIEND", serde_json::json!({})).unwrap(); + store + .create_edge(alice, bob, "FRIEND", serde_json::json!({})) + .unwrap(); + store + .create_edge(alice, charlie, "FRIEND", serde_json::json!({})) + .unwrap(); let neighbors = store.neighbors(&alice, Direction::Outgoing); assert_eq!(neighbors.len(), 2); @@ -597,9 +610,15 @@ mod tests { fn test_find_by_label() { let store = GraphStore::new(); - store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Alice"})); + store.create_node( + vec!["User".to_string()], + serde_json::json!({"name": "Alice"}), + ); store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Bob"})); - store.create_node(vec!["Product".to_string()], serde_json::json!({"name": "Widget"})); + store.create_node( + vec!["Product".to_string()], + serde_json::json!({"name": "Widget"}), + ); let users = store.find_nodes_by_label("User"); assert_eq!(users.len(), 2); @@ -615,7 +634,9 @@ mod tests { let alice = store.create_node(vec!["User".to_string()], serde_json::json!({})); let bob = store.create_node(vec!["User".to_string()], serde_json::json!({})); - store.create_edge(alice, bob, "FRIEND", serde_json::json!({})).unwrap(); + store + .create_edge(alice, bob, "FRIEND", serde_json::json!({})) + .unwrap(); // Delete Alice - should also delete the edge store.delete_node(&alice).unwrap(); @@ -631,7 +652,9 @@ mod tests { let a = store.create_node(vec![], serde_json::json!({})); let b = store.create_node(vec![], serde_json::json!({})); - store.create_undirected_edge(a, b, "LINK", serde_json::json!({})).unwrap(); + store + .create_undirected_edge(a, b, "LINK", serde_json::json!({})) + .unwrap(); // Both directions should work let a_neighbors = store.neighbors(&a, Direction::Outgoing); @@ -648,8 +671,12 @@ mod tests { let a = store.create_node(vec![], serde_json::json!({})); let b = store.create_node(vec![], serde_json::json!({})); - store.create_edge(a, b, "TYPE_A", serde_json::json!({})).unwrap(); - store.create_edge(a, b, "TYPE_B", serde_json::json!({})).unwrap(); + store + .create_edge(a, b, "TYPE_A", serde_json::json!({})) + .unwrap(); + store + .create_edge(a, b, "TYPE_B", serde_json::json!({})) + .unwrap(); let edges = store.edges_between(&a, &b); assert_eq!(edges.len(), 2); diff --git a/crates/synor-database/src/graph/traversal.rs b/crates/synor-database/src/graph/traversal.rs index 56554ad..d2bc5c2 100644 --- a/crates/synor-database/src/graph/traversal.rs +++ b/crates/synor-database/src/graph/traversal.rs @@ -171,7 +171,9 @@ impl<'a> Traverser<'a> { for edge in edges { // Check edge type filter - if !query.edge_types.is_empty() && !query.edge_types.contains(&edge.edge_type) { + if !query.edge_types.is_empty() + && !query.edge_types.contains(&edge.edge_type) + { continue; } @@ -395,18 +397,35 @@ mod tests { fn setup_social_graph() -> GraphStore { let store = GraphStore::new(); - let alice = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Alice"})); + let alice = store.create_node( + vec!["User".to_string()], + serde_json::json!({"name": "Alice"}), + ); let bob = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Bob"})); - let charlie = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Charlie"})); - let dave = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Dave"})); + let charlie = store.create_node( + vec!["User".to_string()], + serde_json::json!({"name": "Charlie"}), + ); + let dave = store.create_node( + vec!["User".to_string()], + serde_json::json!({"name": "Dave"}), + ); // Alice -> Bob -> Charlie -> Dave - store.create_edge(alice, bob, "FRIEND", serde_json::json!({})).unwrap(); - store.create_edge(bob, charlie, "FRIEND", serde_json::json!({})).unwrap(); - store.create_edge(charlie, dave, "FRIEND", serde_json::json!({})).unwrap(); + store + .create_edge(alice, bob, "FRIEND", serde_json::json!({})) + .unwrap(); + store + .create_edge(bob, charlie, "FRIEND", serde_json::json!({})) + .unwrap(); + store + .create_edge(charlie, dave, "FRIEND", serde_json::json!({})) + .unwrap(); // Alice -> Charlie (shortcut) - store.create_edge(alice, charlie, "KNOWS", serde_json::json!({})).unwrap(); + store + .create_edge(alice, charlie, "KNOWS", serde_json::json!({})) + .unwrap(); store } @@ -417,7 +436,10 @@ mod tests { let traverser = Traverser::new(&store); let users = store.find_nodes_by_label("User"); - let alice = users.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))).unwrap(); + let alice = users + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))) + .unwrap(); let query = TraversalQuery::new().depth(2); let results = traverser.traverse(&alice.id, &query); @@ -432,7 +454,10 @@ mod tests { let traverser = Traverser::new(&store); let users = store.find_nodes_by_label("User"); - let alice = users.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))).unwrap(); + let alice = users + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))) + .unwrap(); let query = TraversalQuery::new() .depth(2) @@ -440,7 +465,10 @@ mod tests { let results = traverser.traverse(&alice.id, &query); // Following only FRIEND edges: Alice -> Bob -> Charlie - let names: Vec<_> = results.iter().filter_map(|r| r.node.get_property("name")).collect(); + let names: Vec<_> = results + .iter() + .filter_map(|r| r.node.get_property("name")) + .collect(); assert!(names.contains(&&serde_json::json!("Bob"))); assert!(names.contains(&&serde_json::json!("Charlie"))); } @@ -451,7 +479,10 @@ mod tests { let traverser = Traverser::new(&store); let users = store.find_nodes_by_label("User"); - let alice = users.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))).unwrap(); + let alice = users + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))) + .unwrap(); let query = TraversalQuery::new().depth(1); let results = traverser.traverse(&alice.id, &query); @@ -468,7 +499,10 @@ mod tests { let traverser = Traverser::new(&store); let users = store.find_nodes_by_label("User"); - let alice = users.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))).unwrap(); + let alice = users + .iter() + .find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))) + .unwrap(); let query = TraversalQuery::new().depth(10).limit(2); let results = traverser.traverse(&alice.id, &query); @@ -486,11 +520,21 @@ mod tests { let mutual2 = store.create_node(vec![], serde_json::json!({"name": "Mutual2"})); let only_alice = store.create_node(vec![], serde_json::json!({"name": "OnlyAlice"})); - store.create_edge(alice, mutual1, "FRIEND", serde_json::json!({})).unwrap(); - store.create_edge(alice, mutual2, "FRIEND", serde_json::json!({})).unwrap(); - store.create_edge(alice, only_alice, "FRIEND", serde_json::json!({})).unwrap(); - store.create_edge(bob, mutual1, "FRIEND", serde_json::json!({})).unwrap(); - store.create_edge(bob, mutual2, "FRIEND", serde_json::json!({})).unwrap(); + store + .create_edge(alice, mutual1, "FRIEND", serde_json::json!({})) + .unwrap(); + store + .create_edge(alice, mutual2, "FRIEND", serde_json::json!({})) + .unwrap(); + store + .create_edge(alice, only_alice, "FRIEND", serde_json::json!({})) + .unwrap(); + store + .create_edge(bob, mutual1, "FRIEND", serde_json::json!({})) + .unwrap(); + store + .create_edge(bob, mutual2, "FRIEND", serde_json::json!({})) + .unwrap(); let traverser = Traverser::new(&store); let mutual = traverser.mutual_connections(&alice, &bob, Some("FRIEND")); diff --git a/crates/synor-database/src/index.rs b/crates/synor-database/src/index.rs index b42a135..e0febf6 100644 --- a/crates/synor-database/src/index.rs +++ b/crates/synor-database/src/index.rs @@ -174,17 +174,24 @@ impl Index { // Check uniqueness if required if self.config.unique { let exists = match self.config.index_type { - IndexType::Hash | IndexType::Unique => { - self.hash.read().get(&key).map(|s| !s.is_empty()).unwrap_or(false) - } - _ => { - self.btree.read().get(&key).map(|s| !s.is_empty()).unwrap_or(false) - } + IndexType::Hash | IndexType::Unique => self + .hash + .read() + .get(&key) + .map(|s| !s.is_empty()) + .unwrap_or(false), + _ => self + .btree + .read() + .get(&key) + .map(|s| !s.is_empty()) + .unwrap_or(false), }; if exists { - return Err(DatabaseError::AlreadyExists( - format!("Unique constraint violation on index '{}'", self.config.name) - )); + return Err(DatabaseError::AlreadyExists(format!( + "Unique constraint violation on index '{}'", + self.config.name + ))); } } @@ -239,20 +246,18 @@ impl Index { self.stats.write().lookups += 1; let result: Vec = match self.config.index_type { - IndexType::Hash | IndexType::Unique => { - self.hash - .read() - .get(&key) - .map(|s| s.iter().cloned().collect()) - .unwrap_or_default() - } - _ => { - self.btree - .read() - .get(&key) - .map(|s| s.iter().cloned().collect()) - .unwrap_or_default() - } + IndexType::Hash | IndexType::Unique => self + .hash + .read() + .get(&key) + .map(|s| s.iter().cloned().collect()) + .unwrap_or_default(), + _ => self + .btree + .read() + .get(&key) + .map(|s| s.iter().cloned().collect()) + .unwrap_or_default(), }; if !result.is_empty() { @@ -407,12 +412,7 @@ impl IndexManager { } /// Removes a document from indexes. - pub fn unindex_document( - &self, - collection: &str, - doc_id: &DocumentId, - document: &JsonValue, - ) { + pub fn unindex_document(&self, collection: &str, doc_id: &DocumentId, document: &JsonValue) { let index_names = self.get_collection_indexes(collection); let indexes = self.indexes.read(); @@ -483,7 +483,9 @@ mod tests { let index = Index::new(config); let doc1 = DocumentId::new(); - index.insert(doc1.clone(), &json!("alice@example.com")).unwrap(); + index + .insert(doc1.clone(), &json!("alice@example.com")) + .unwrap(); let results = index.lookup(&json!("alice@example.com")); assert_eq!(results.len(), 1); @@ -521,7 +523,9 @@ mod tests { let doc_id = DocumentId::new(); let doc = json!({"name": "Alice", "age": 30}); - manager.index_document("users", doc_id.clone(), &doc).unwrap(); + manager + .index_document("users", doc_id.clone(), &doc) + .unwrap(); let indexes = manager.list_indexes(); assert_eq!(indexes.len(), 1); diff --git a/crates/synor-database/src/keyvalue.rs b/crates/synor-database/src/keyvalue.rs index 8fd973c..3a3035d 100644 --- a/crates/synor-database/src/keyvalue.rs +++ b/crates/synor-database/src/keyvalue.rs @@ -126,8 +126,7 @@ impl KeyValueStore { /// Gets a value as string. pub fn get_string(&self, key: &str) -> Option { - self.get(key) - .and_then(|v| String::from_utf8(v).ok()) + self.get(key).and_then(|v| String::from_utf8(v).ok()) } /// Sets a value with optional TTL. @@ -224,8 +223,9 @@ impl KeyValueStore { } else { let s = String::from_utf8(entry.value.clone()) .map_err(|_| DatabaseError::InvalidOperation("Value is not a string".into()))?; - s.parse::() - .map_err(|_| DatabaseError::InvalidOperation("Value is not an integer".into()))? + s.parse::().map_err(|_| { + DatabaseError::InvalidOperation("Value is not an integer".into()) + })? } } else { 0 @@ -243,9 +243,9 @@ impl KeyValueStore { pub fn append(&self, key: &str, value: &[u8]) -> Result { let mut data = self.data.write(); - let entry = data.entry(key.to_string()).or_insert_with(|| { - KvEntry::new(Vec::new(), 0) - }); + let entry = data + .entry(key.to_string()) + .or_insert_with(|| KvEntry::new(Vec::new(), 0)); if entry.is_expired() { entry.value.clear(); @@ -393,11 +393,16 @@ mod tests { fn test_mget_mset() { let store = KeyValueStore::new(); - store.mset(&[ - ("k1", b"v1".to_vec()), - ("k2", b"v2".to_vec()), - ("k3", b"v3".to_vec()), - ], 0).unwrap(); + store + .mset( + &[ + ("k1", b"v1".to_vec()), + ("k2", b"v2".to_vec()), + ("k3", b"v3".to_vec()), + ], + 0, + ) + .unwrap(); let results = store.mget(&["k1", "k2", "k4"]); assert_eq!(results.len(), 3); diff --git a/crates/synor-database/src/lib.rs b/crates/synor-database/src/lib.rs index 410c778..5ceedec 100644 --- a/crates/synor-database/src/lib.rs +++ b/crates/synor-database/src/lib.rs @@ -65,12 +65,14 @@ pub use graph::{ pub use index::{Index, IndexConfig, IndexManager, IndexType}; pub use keyvalue::{KeyValue, KeyValueStore, KvEntry}; pub use query::{Filter, Query, QueryEngine, QueryResult, SortOrder}; -pub use schema::{Field, FieldType, Schema, SchemaValidator}; pub use replication::{ ClusterConfig, Command as RaftCommand, NodeRole, RaftConfig, RaftEvent, RaftNode, RaftState, ReplicatedLog, }; -pub use sql::{QueryResult as SqlQueryResult, SqlEngine, SqlParser, SqlType, SqlValue, Table, TableDef}; +pub use schema::{Field, FieldType, Schema, SchemaValidator}; +pub use sql::{ + QueryResult as SqlQueryResult, SqlEngine, SqlParser, SqlType, SqlValue, Table, TableDef, +}; pub use timeseries::{DataPoint, Metric, TimeSeries, TimeSeriesStore}; pub use vector::{Embedding, SimilarityMetric, VectorIndex, VectorStore}; diff --git a/crates/synor-database/src/query.rs b/crates/synor-database/src/query.rs index 5ed26c0..a18c34a 100644 --- a/crates/synor-database/src/query.rs +++ b/crates/synor-database/src/query.rs @@ -419,10 +419,7 @@ impl QueryEngine { let values: Vec = docs .iter() - .filter_map(|doc| { - doc.get(field) - .and_then(|v| v.as_f64()) - }) + .filter_map(|doc| doc.get(field).and_then(|v| v.as_f64())) .collect(); let result = match op { @@ -439,22 +436,18 @@ impl QueryEngine { serde_json::to_value(avg).unwrap_or(JsonValue::Null) } } - AggregateOp::Min => { - values - .iter() - .copied() - .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) - .map(|v| serde_json::to_value(v).unwrap_or(JsonValue::Null)) - .unwrap_or(JsonValue::Null) - } - AggregateOp::Max => { - values - .iter() - .copied() - .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) - .map(|v| serde_json::to_value(v).unwrap_or(JsonValue::Null)) - .unwrap_or(JsonValue::Null) - } + AggregateOp::Min => values + .iter() + .copied() + .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) + .map(|v| serde_json::to_value(v).unwrap_or(JsonValue::Null)) + .unwrap_or(JsonValue::Null), + AggregateOp::Max => values + .iter() + .copied() + .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) + .map(|v| serde_json::to_value(v).unwrap_or(JsonValue::Null)) + .unwrap_or(JsonValue::Null), }; Ok(result) @@ -507,8 +500,10 @@ mod tests { fn test_simple_query() { let docs = Arc::new(DocumentStore::new()); docs.create_collection("users").unwrap(); - docs.insert("users", json!({"name": "Alice", "age": 30})).unwrap(); - docs.insert("users", json!({"name": "Bob", "age": 25})).unwrap(); + docs.insert("users", json!({"name": "Alice", "age": 30})) + .unwrap(); + docs.insert("users", json!({"name": "Bob", "age": 25})) + .unwrap(); let vectors = Arc::new(VectorStore::new(3)); let indexes = Arc::new(IndexManager::new()); @@ -525,8 +520,10 @@ mod tests { fn test_filter_query() { let docs = Arc::new(DocumentStore::new()); docs.create_collection("users").unwrap(); - docs.insert("users", json!({"name": "Alice", "age": 30})).unwrap(); - docs.insert("users", json!({"name": "Bob", "age": 25})).unwrap(); + docs.insert("users", json!({"name": "Alice", "age": 30})) + .unwrap(); + docs.insert("users", json!({"name": "Bob", "age": 25})) + .unwrap(); let vectors = Arc::new(VectorStore::new(3)); let indexes = Arc::new(IndexManager::new()); @@ -543,9 +540,12 @@ mod tests { fn test_sorted_query() { let docs = Arc::new(DocumentStore::new()); docs.create_collection("users").unwrap(); - docs.insert("users", json!({"name": "Alice", "age": 30})).unwrap(); - docs.insert("users", json!({"name": "Bob", "age": 25})).unwrap(); - docs.insert("users", json!({"name": "Charlie", "age": 35})).unwrap(); + docs.insert("users", json!({"name": "Alice", "age": 30})) + .unwrap(); + docs.insert("users", json!({"name": "Bob", "age": 25})) + .unwrap(); + docs.insert("users", json!({"name": "Charlie", "age": 35})) + .unwrap(); let vectors = Arc::new(VectorStore::new(3)); let indexes = Arc::new(IndexManager::new()); diff --git a/crates/synor-database/src/replication/election.rs b/crates/synor-database/src/replication/election.rs index 1b2c93f..355c295 100644 --- a/crates/synor-database/src/replication/election.rs +++ b/crates/synor-database/src/replication/election.rs @@ -92,12 +92,7 @@ impl Election { /// Creates a RequestVote message for this election. pub fn create_request(&self, log: &ReplicatedLog) -> RequestVote { - RequestVote::new( - self.term, - self.node_id, - log.last_index(), - log.last_term(), - ) + RequestVote::new(self.term, self.node_id, log.last_index(), log.last_term()) } /// Checks the current result of the election. @@ -217,8 +212,8 @@ impl Default for ElectionTimeout { #[cfg(test)] mod tests { use super::*; - use crate::replication::state::Command; use crate::replication::log::LogEntry; + use crate::replication::state::Command; #[test] fn test_election_basic() { diff --git a/crates/synor-database/src/replication/log.rs b/crates/synor-database/src/replication/log.rs index 417fbae..4129537 100644 --- a/crates/synor-database/src/replication/log.rs +++ b/crates/synor-database/src/replication/log.rs @@ -139,7 +139,10 @@ impl ReplicatedLog { let entries = self.entries.read(); let offset = (from_index - start) as usize; - entries.get(offset..).map(|s| s.to_vec()).unwrap_or_default() + entries + .get(offset..) + .map(|s| s.to_vec()) + .unwrap_or_default() } /// Appends an entry to the log. @@ -151,7 +154,12 @@ impl ReplicatedLog { } /// Appends multiple entries, potentially overwriting conflicting entries. - pub fn append_entries(&self, prev_index: u64, prev_term: u64, new_entries: Vec) -> bool { + pub fn append_entries( + &self, + prev_index: u64, + prev_term: u64, + new_entries: Vec, + ) -> bool { // Check that prev entry matches if prev_index > 0 { if let Some(prev_entry_term) = self.term_at(prev_index) { @@ -245,7 +253,11 @@ impl ReplicatedLog { } /// Creates entries for replication starting from a given index. - pub fn entries_for_replication(&self, from_index: u64, max_entries: usize) -> (u64, u64, Vec) { + pub fn entries_for_replication( + &self, + from_index: u64, + max_entries: usize, + ) -> (u64, u64, Vec) { let prev_index = from_index.saturating_sub(1); let prev_term = self.term_at(prev_index).unwrap_or(0); diff --git a/crates/synor-database/src/replication/raft.rs b/crates/synor-database/src/replication/raft.rs index adce4ad..2aebcbc 100644 --- a/crates/synor-database/src/replication/raft.rs +++ b/crates/synor-database/src/replication/raft.rs @@ -222,7 +222,11 @@ impl RaftNode { // Create new election let cluster_size = self.cluster.voting_members(); - self.election = Some(Election::new(self.id, self.state.current_term, cluster_size)); + self.election = Some(Election::new( + self.id, + self.state.current_term, + cluster_size, + )); // Create RequestVote message let request = RequestVote::new( @@ -295,9 +299,9 @@ impl RaftNode { return; } - let (prev_log_index, prev_log_term, entries) = - self.log - .entries_for_replication(next_index, self.config.max_entries_per_rpc); + let (prev_log_index, prev_log_term, entries) = self + .log + .entries_for_replication(next_index, self.config.max_entries_per_rpc); let request = AppendEntries::with_entries( self.state.current_term, @@ -308,8 +312,10 @@ impl RaftNode { self.state.commit_index, ); - self.events - .push(RaftEvent::SendRpc(peer_id, RpcMessage::AppendEntries(request))); + self.events.push(RaftEvent::SendRpc( + peer_id, + RpcMessage::AppendEntries(request), + )); } fn send_install_snapshot(&mut self, peer_id: NodeId) { @@ -332,8 +338,10 @@ impl RaftNode { done, ); - self.events - .push(RaftEvent::SendRpc(peer_id, RpcMessage::InstallSnapshot(request))); + self.events.push(RaftEvent::SendRpc( + peer_id, + RpcMessage::InstallSnapshot(request), + )); } } @@ -395,7 +403,11 @@ impl RaftNode { } } - fn handle_append_entries(&mut self, _from: NodeId, req: AppendEntries) -> AppendEntriesResponse { + fn handle_append_entries( + &mut self, + _from: NodeId, + req: AppendEntries, + ) -> AppendEntriesResponse { // Rule: If term > currentTerm, become follower if req.term > self.state.current_term { self.become_follower(req.term, Some(req.leader_id)); @@ -416,9 +428,9 @@ impl RaftNode { } // Try to append entries - let success = - self.log - .append_entries(req.prev_log_index, req.prev_log_term, req.entries); + let success = self + .log + .append_entries(req.prev_log_index, req.prev_log_term, req.entries); if success { // Update commit index @@ -443,7 +455,11 @@ impl RaftNode { } conflict_index -= 1; } - AppendEntriesResponse::conflict(self.state.current_term, conflict_term, conflict_index) + AppendEntriesResponse::conflict( + self.state.current_term, + conflict_term, + conflict_index, + ) } else { AppendEntriesResponse::failure(self.state.current_term) } @@ -502,7 +518,11 @@ impl RaftNode { self.cluster.update_peer_state(from, PeerState::Reachable); } - fn handle_install_snapshot(&mut self, _from: NodeId, req: InstallSnapshot) -> InstallSnapshotResponse { + fn handle_install_snapshot( + &mut self, + _from: NodeId, + req: InstallSnapshot, + ) -> InstallSnapshotResponse { // Rule: If term > currentTerm, become follower if req.term > self.state.current_term { self.become_follower(req.term, Some(req.leader_id)); @@ -692,12 +712,14 @@ impl RaftNode { #[cfg(test)] mod tests { - use super::*; use super::super::cluster::PeerAddress; + use super::*; fn create_test_cluster(node_id: NodeId, peers: &[NodeId]) -> ClusterConfig { - let mut cluster = - ClusterConfig::new(node_id, PeerAddress::new("127.0.0.1", 9000 + node_id as u16)); + let mut cluster = ClusterConfig::new( + node_id, + PeerAddress::new("127.0.0.1", 9000 + node_id as u16), + ); for &peer in peers { cluster.add_peer(super::super::cluster::PeerInfo::new( peer, diff --git a/crates/synor-database/src/replication/snapshot.rs b/crates/synor-database/src/replication/snapshot.rs index c5411e6..cf64477 100644 --- a/crates/synor-database/src/replication/snapshot.rs +++ b/crates/synor-database/src/replication/snapshot.rs @@ -176,7 +176,10 @@ impl SnapshotManager { /// Adds a chunk to the pending snapshot. pub fn add_chunk(&mut self, offset: u64, data: Vec) -> bool { if let Some(ref mut pending) = self.pending_snapshot { - if offset == pending.expected_offset + pending.chunks.iter().map(|c| c.len() as u64).sum::() { + if offset + == pending.expected_offset + + pending.chunks.iter().map(|c| c.len() as u64).sum::() + { pending.chunks.push(data); return true; } diff --git a/crates/synor-database/src/replication/state.rs b/crates/synor-database/src/replication/state.rs index dea2ca8..7e7d4ce 100644 --- a/crates/synor-database/src/replication/state.rs +++ b/crates/synor-database/src/replication/state.rs @@ -118,31 +118,55 @@ pub enum Command { // Key-Value operations /// Set a key-value pair. - KvSet { key: String, value: Vec, ttl: Option }, + KvSet { + key: String, + value: Vec, + ttl: Option, + }, /// Delete a key. KvDelete { key: String }, // Document operations /// Insert a document. - DocInsert { collection: String, document: JsonValue }, + DocInsert { + collection: String, + document: JsonValue, + }, /// Update a document. - DocUpdate { collection: String, id: String, update: JsonValue }, + DocUpdate { + collection: String, + id: String, + update: JsonValue, + }, /// Delete a document. DocDelete { collection: String, id: String }, // Vector operations /// Insert a vector. - VectorInsert { namespace: String, id: String, vector: Vec, metadata: JsonValue }, + VectorInsert { + namespace: String, + id: String, + vector: Vec, + metadata: JsonValue, + }, /// Delete a vector. VectorDelete { namespace: String, id: String }, // Time-series operations /// Record a metric data point. - TimeSeriesRecord { metric: String, value: f64, timestamp: u64, tags: JsonValue }, + TimeSeriesRecord { + metric: String, + value: f64, + timestamp: u64, + tags: JsonValue, + }, // Graph operations /// Create a graph node. - GraphNodeCreate { labels: Vec, properties: JsonValue }, + GraphNodeCreate { + labels: Vec, + properties: JsonValue, + }, /// Delete a graph node. GraphNodeDelete { id: String }, /// Create a graph edge. @@ -161,13 +185,20 @@ pub enum Command { // Schema operations /// Create a collection/table. - CreateCollection { name: String, schema: Option }, + CreateCollection { + name: String, + schema: Option, + }, /// Drop a collection/table. DropCollection { name: String }, // Index operations /// Create an index. - CreateIndex { collection: String, field: String, index_type: String }, + CreateIndex { + collection: String, + field: String, + index_type: String, + }, /// Drop an index. DropIndex { name: String }, @@ -265,7 +296,12 @@ impl LeaderState { } /// Calculates the new commit index based on majority replication. - pub fn calculate_commit_index(&self, current_commit: u64, current_term: u64, log_term_at: impl Fn(u64) -> Option) -> u64 { + pub fn calculate_commit_index( + &self, + current_commit: u64, + current_term: u64, + log_term_at: impl Fn(u64) -> Option, + ) -> u64 { // Find the highest index that a majority have replicated let mut indices: Vec = self.match_index.values().cloned().collect(); indices.sort_unstable(); diff --git a/crates/synor-database/src/schema.rs b/crates/synor-database/src/schema.rs index de76019..cadcd09 100644 --- a/crates/synor-database/src/schema.rs +++ b/crates/synor-database/src/schema.rs @@ -315,8 +315,8 @@ mod tests { #[test] fn test_vector_field() { - let schema = Schema::new("embedding") - .field(Field::required("vector", FieldType::Vector(3))); + let schema = + Schema::new("embedding").field(Field::required("vector", FieldType::Vector(3))); let mut validator = SchemaValidator::new(); validator.register(schema); diff --git a/crates/synor-database/src/sql/executor.rs b/crates/synor-database/src/sql/executor.rs index ac5847e..8a016bd 100644 --- a/crates/synor-database/src/sql/executor.rs +++ b/crates/synor-database/src/sql/executor.rs @@ -1,8 +1,7 @@ //! SQL query executor. use super::parser::{ - BinaryOp, ParsedExpr, ParsedSelect, ParsedSelectItem, - ParsedStatement, SqlParser, + BinaryOp, ParsedExpr, ParsedSelect, ParsedSelectItem, ParsedStatement, SqlParser, }; use super::row::{Row, RowId}; use super::table::{ColumnDef, Table, TableDef}; @@ -192,11 +191,7 @@ impl SqlEngine { match a_val.partial_cmp(&b_val) { Some(std::cmp::Ordering::Equal) => continue, Some(ord) => { - return if ob.ascending { - ord - } else { - ord.reverse() - }; + return if ob.ascending { ord } else { ord.reverse() }; } None => continue, } @@ -216,7 +211,11 @@ impl SqlEngine { } // Handle aggregates - if select.columns.iter().any(|c| matches!(c, ParsedSelectItem::Aggregate { .. })) { + if select + .columns + .iter() + .any(|c| matches!(c, ParsedSelectItem::Aggregate { .. })) + { return self.execute_aggregate(select, &rows, table); } @@ -244,7 +243,9 @@ impl SqlEngine { ParsedSelectItem::Wildcard => table.def.column_names(), ParsedSelectItem::Column(name) => vec![name.clone()], ParsedSelectItem::ColumnAlias { alias, .. } => vec![alias.clone()], - ParsedSelectItem::Aggregate { function, alias, .. } => { + ParsedSelectItem::Aggregate { + function, alias, .. + } => { vec![alias.clone().unwrap_or_else(|| function.clone())] } }) @@ -328,7 +329,9 @@ impl SqlEngine { rows.iter() .map(|r| r.get_or_null(col)) .filter(|v| !v.is_null()) - .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) + .min_by(|a, b| { + a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) + }) .unwrap_or(SqlValue::Null) } "MAX" => { @@ -338,12 +341,12 @@ impl SqlEngine { rows.iter() .map(|r| r.get_or_null(col)) .filter(|v| !v.is_null()) - .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) + .max_by(|a, b| { + a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) + }) .unwrap_or(SqlValue::Null) } - _ => { - return Err(SqlError::Unsupported(format!("Function: {}", function))) - } + _ => return Err(SqlError::Unsupported(format!("Function: {}", function))), }; result_values.push(value); } @@ -404,7 +407,11 @@ impl SqlEngine { ParsedExpr::IsNotNull(inner) => { SqlValue::Boolean(!self.evaluate_expr(row, inner).is_null()) } - ParsedExpr::InList { expr, list, negated } => { + ParsedExpr::InList { + expr, + list, + negated, + } => { let val = self.evaluate_expr(row, expr); let in_list = list.iter().any(|item| { let item_val = self.evaluate_expr(row, item); @@ -424,9 +431,7 @@ impl SqlEngine { let between = val >= low_val && val <= high_val; SqlValue::Boolean(if *negated { !between } else { between }) } - ParsedExpr::Function { name, args } => { - self.evaluate_function(row, name, args) - } + ParsedExpr::Function { name, args } => self.evaluate_function(row, name, args), } } @@ -474,9 +479,7 @@ impl SqlEngine { _ => SqlValue::Null, }, BinaryOp::Divide => match (left, right) { - (SqlValue::Integer(a), SqlValue::Integer(b)) if *b != 0 => { - SqlValue::Integer(a / b) - } + (SqlValue::Integer(a), SqlValue::Integer(b)) if *b != 0 => SqlValue::Integer(a / b), (SqlValue::Real(a), SqlValue::Real(b)) if *b != 0.0 => SqlValue::Real(a / b), _ => SqlValue::Null, }, @@ -536,9 +539,7 @@ impl SqlEngine { /// Matches a LIKE pattern. fn match_like(&self, text: &str, pattern: &str) -> bool { // Simple LIKE implementation: % = any chars, _ = single char - let _regex_pattern = pattern - .replace('%', ".*") - .replace('_', "."); + let _regex_pattern = pattern.replace('%', ".*").replace('_', "."); // For simplicity, just do case-insensitive contains for now if pattern.starts_with('%') && pattern.ends_with('%') { let inner = &pattern[1..pattern.len() - 1]; @@ -615,8 +616,7 @@ impl SqlEngine { .unwrap_or(true); if matches { - let updates: HashMap = - assignments.iter().cloned().collect(); + let updates: HashMap = assignments.iter().cloned().collect(); table.update(row.id, updates)?; count += 1; } @@ -672,9 +672,9 @@ impl SqlEngine { .ok_or_else(|| SqlError::TableNotFound(table_name.to_string()))?; // For simplicity, only support single-column indexes - let column = columns - .first() - .ok_or_else(|| SqlError::InvalidOperation("Index requires at least one column".to_string()))?; + let column = columns.first().ok_or_else(|| { + SqlError::InvalidOperation("Index requires at least one column".to_string()) + })?; table.create_index(name, column, unique)?; Ok(QueryResult::empty()) @@ -775,7 +775,9 @@ mod tests { .execute("INSERT INTO users (id, name, age) VALUES (2, 'Bob', 25)") .unwrap(); - let result = engine.execute("SELECT name FROM users WHERE age > 26").unwrap(); + let result = engine + .execute("SELECT name FROM users WHERE age > 26") + .unwrap(); assert_eq!(result.rows.len(), 1); assert_eq!(result.rows[0][0], SqlValue::Text("Alice".to_string())); } @@ -806,7 +808,9 @@ mod tests { engine .execute(&format!( "INSERT INTO users (id, name, age) VALUES ({}, 'User{}', {})", - i, i, 20 + i + i, + i, + 20 + i )) .unwrap(); } diff --git a/crates/synor-database/src/sql/parser.rs b/crates/synor-database/src/sql/parser.rs index 71d49e0..220e897 100644 --- a/crates/synor-database/src/sql/parser.rs +++ b/crates/synor-database/src/sql/parser.rs @@ -18,10 +18,7 @@ pub enum ParsedStatement { if_not_exists: bool, }, /// DROP TABLE statement. - DropTable { - name: String, - if_exists: bool, - }, + DropTable { name: String, if_exists: bool }, /// SELECT statement. Select(ParsedSelect), /// INSERT statement. @@ -179,15 +176,17 @@ impl SqlParser { /// Parses a SQL statement. pub fn parse(sql: &str) -> Result { let dialect = SQLiteDialect {}; - let statements = Parser::parse_sql(&dialect, sql) - .map_err(|e| SqlError::Parse(e.to_string()))?; + let statements = + Parser::parse_sql(&dialect, sql).map_err(|e| SqlError::Parse(e.to_string()))?; if statements.is_empty() { return Err(SqlError::Parse("Empty SQL statement".to_string())); } if statements.len() > 1 { - return Err(SqlError::Parse("Multiple statements not supported".to_string())); + return Err(SqlError::Parse( + "Multiple statements not supported".to_string(), + )); } Self::convert_statement(&statements[0]) @@ -195,25 +194,42 @@ impl SqlParser { fn convert_statement(stmt: &Statement) -> Result { match stmt { - Statement::CreateTable { name, columns, if_not_exists, constraints, .. } => { - Self::convert_create_table(name, columns, constraints, *if_not_exists) - } - Statement::Drop { object_type, names, if_exists, .. } => { - Self::convert_drop(object_type, names, *if_exists) - } + Statement::CreateTable { + name, + columns, + if_not_exists, + constraints, + .. + } => Self::convert_create_table(name, columns, constraints, *if_not_exists), + Statement::Drop { + object_type, + names, + if_exists, + .. + } => Self::convert_drop(object_type, names, *if_exists), Statement::Query(query) => Self::convert_query(query), - Statement::Insert { table_name, columns, source, .. } => { - Self::convert_insert(table_name, columns, source) - } - Statement::Update { table, assignments, selection, .. } => { - Self::convert_update(table, assignments, selection) - } - Statement::Delete { from, selection, .. } => { - Self::convert_delete(from, selection) - } - Statement::CreateIndex { name, table_name, columns, unique, .. } => { - Self::convert_create_index(name, table_name, columns, *unique) - } + Statement::Insert { + table_name, + columns, + source, + .. + } => Self::convert_insert(table_name, columns, source), + Statement::Update { + table, + assignments, + selection, + .. + } => Self::convert_update(table, assignments, selection), + Statement::Delete { + from, selection, .. + } => Self::convert_delete(from, selection), + Statement::CreateIndex { + name, + table_name, + columns, + unique, + .. + } => Self::convert_create_index(name, table_name, columns, *unique), _ => Err(SqlError::Unsupported(format!("Statement not supported"))), } } @@ -230,7 +246,12 @@ impl SqlParser { // Extract primary keys from table constraints for constraint in constraints { - if let sqlparser::ast::TableConstraint::Unique { columns: pk_cols, is_primary: true, .. } = constraint { + if let sqlparser::ast::TableConstraint::Unique { + columns: pk_cols, + is_primary: true, + .. + } = constraint + { for col in pk_cols { primary_keys.push(col.value.clone()); } @@ -296,10 +317,9 @@ impl SqlParser { DataType::Real | DataType::Float(_) | DataType::Double | DataType::DoublePrecision => { Ok(SqlType::Real) } - DataType::Varchar(_) - | DataType::Char(_) - | DataType::Text - | DataType::String(_) => Ok(SqlType::Text), + DataType::Varchar(_) | DataType::Char(_) | DataType::Text | DataType::String(_) => { + Ok(SqlType::Text) + } DataType::Binary(_) | DataType::Varbinary(_) | DataType::Blob(_) => Ok(SqlType::Blob), DataType::Boolean => Ok(SqlType::Boolean), DataType::Timestamp(_, _) | DataType::Date | DataType::Datetime(_) => { @@ -367,10 +387,7 @@ impl SqlParser { .collect(); // Parse LIMIT/OFFSET - let limit = query - .limit - .as_ref() - .and_then(|l| Self::expr_to_usize(l)); + let limit = query.limit.as_ref().and_then(|l| Self::expr_to_usize(l)); let offset = query .offset .as_ref() @@ -403,16 +420,18 @@ impl SqlParser { Self::convert_select_expr(expr) } } - _ => Err(SqlError::Unsupported("Select item not supported".to_string())), + _ => Err(SqlError::Unsupported( + "Select item not supported".to_string(), + )), } } fn convert_select_expr(expr: &Expr) -> Result { match expr { Expr::Identifier(id) => Ok(ParsedSelectItem::Column(id.value.clone())), - Expr::CompoundIdentifier(ids) => { - Ok(ParsedSelectItem::Column(ids.last().map(|i| i.value.clone()).unwrap_or_default())) - } + Expr::CompoundIdentifier(ids) => Ok(ParsedSelectItem::Column( + ids.last().map(|i| i.value.clone()).unwrap_or_default(), + )), Expr::Function(func) => { let name = func.name.to_string().to_uppercase(); // Try to extract column from first arg - simplified for compatibility @@ -423,14 +442,18 @@ impl SqlParser { alias: None, }) } - _ => Err(SqlError::Unsupported("Select expression not supported".to_string())), + _ => Err(SqlError::Unsupported( + "Select expression not supported".to_string(), + )), } } fn convert_table_factor(factor: &TableFactor) -> Result { match factor { TableFactor::Table { name, .. } => Ok(name.to_string()), - _ => Err(SqlError::Unsupported("Table factor not supported".to_string())), + _ => Err(SqlError::Unsupported( + "Table factor not supported".to_string(), + )), } } @@ -461,9 +484,9 @@ impl SqlParser { fn convert_expr(expr: &Expr) -> Result { match expr { Expr::Identifier(id) => Ok(ParsedExpr::Column(id.value.clone())), - Expr::CompoundIdentifier(ids) => { - Ok(ParsedExpr::Column(ids.last().map(|i| i.value.clone()).unwrap_or_default())) - } + Expr::CompoundIdentifier(ids) => Ok(ParsedExpr::Column( + ids.last().map(|i| i.value.clone()).unwrap_or_default(), + )), Expr::Value(v) => Ok(ParsedExpr::Literal(Self::convert_value(v)?)), Expr::BinaryOp { left, op, right } => { let left = Box::new(Self::convert_expr(left)?); @@ -471,17 +494,30 @@ impl SqlParser { let op = Self::convert_binary_op(op)?; Ok(ParsedExpr::BinaryOp { left, op, right }) } - Expr::UnaryOp { op: sqlparser::ast::UnaryOperator::Not, expr } => { - Ok(ParsedExpr::Not(Box::new(Self::convert_expr(expr)?))) - } + Expr::UnaryOp { + op: sqlparser::ast::UnaryOperator::Not, + expr, + } => Ok(ParsedExpr::Not(Box::new(Self::convert_expr(expr)?))), Expr::IsNull(expr) => Ok(ParsedExpr::IsNull(Box::new(Self::convert_expr(expr)?))), Expr::IsNotNull(expr) => Ok(ParsedExpr::IsNotNull(Box::new(Self::convert_expr(expr)?))), - Expr::InList { expr, list, negated } => Ok(ParsedExpr::InList { + Expr::InList { + expr, + list, + negated, + } => Ok(ParsedExpr::InList { expr: Box::new(Self::convert_expr(expr)?), - list: list.iter().map(Self::convert_expr).collect::>()?, + list: list + .iter() + .map(Self::convert_expr) + .collect::>()?, negated: *negated, }), - Expr::Between { expr, low, high, negated } => Ok(ParsedExpr::Between { + Expr::Between { + expr, + low, + high, + negated, + } => Ok(ParsedExpr::Between { expr: Box::new(Self::convert_expr(expr)?), low: Box::new(Self::convert_expr(low)?), high: Box::new(Self::convert_expr(high)?), @@ -490,10 +526,16 @@ impl SqlParser { Expr::Like { expr, pattern, .. } => { let left = Box::new(Self::convert_expr(expr)?); let right = Box::new(Self::convert_expr(pattern)?); - Ok(ParsedExpr::BinaryOp { left, op: BinaryOp::Like, right }) + Ok(ParsedExpr::BinaryOp { + left, + op: BinaryOp::Like, + right, + }) } Expr::Nested(inner) => Self::convert_expr(inner), - _ => Err(SqlError::Unsupported("Expression not supported".to_string())), + _ => Err(SqlError::Unsupported( + "Expression not supported".to_string(), + )), } } @@ -587,7 +629,11 @@ impl SqlParser { let parsed_assignments: Vec<(String, SqlValue)> = assignments .iter() .map(|a| { - let col = a.id.iter().map(|i| i.value.clone()).collect::>().join("."); + let col = + a.id.iter() + .map(|i| i.value.clone()) + .collect::>() + .join("."); let val = Self::convert_value_expr(&a.value)?; Ok((col, val)) }) @@ -633,10 +679,7 @@ impl SqlParser { let table = table_name.to_string(); - let cols: Vec = columns - .iter() - .map(|c| c.expr.to_string()) - .collect(); + let cols: Vec = columns.iter().map(|c| c.expr.to_string()).collect(); Ok(ParsedStatement::CreateIndex { name: index_name, @@ -694,7 +737,12 @@ mod tests { let sql = "INSERT INTO users (name, age) VALUES ('Alice', 30), ('Bob', 25)"; let stmt = SqlParser::parse(sql).unwrap(); - if let ParsedStatement::Insert { table, columns, values } = stmt { + if let ParsedStatement::Insert { + table, + columns, + values, + } = stmt + { assert_eq!(table, "users"); assert_eq!(columns, vec!["name", "age"]); assert_eq!(values.len(), 2); @@ -708,7 +756,12 @@ mod tests { let sql = "UPDATE users SET age = 31 WHERE name = 'Alice'"; let stmt = SqlParser::parse(sql).unwrap(); - if let ParsedStatement::Update { table, assignments, where_clause } = stmt { + if let ParsedStatement::Update { + table, + assignments, + where_clause, + } = stmt + { assert_eq!(table, "users"); assert_eq!(assignments.len(), 1); assert!(where_clause.is_some()); @@ -722,7 +775,11 @@ mod tests { let sql = "DELETE FROM users WHERE age < 18"; let stmt = SqlParser::parse(sql).unwrap(); - if let ParsedStatement::Delete { table, where_clause } = stmt { + if let ParsedStatement::Delete { + table, + where_clause, + } = stmt + { assert_eq!(table, "users"); assert!(where_clause.is_some()); } else { diff --git a/crates/synor-database/src/sql/row.rs b/crates/synor-database/src/sql/row.rs index f193728..9463720 100644 --- a/crates/synor-database/src/sql/row.rs +++ b/crates/synor-database/src/sql/row.rs @@ -85,7 +85,10 @@ impl Row { /// Returns all values in column order. pub fn values(&self) -> Vec<&SqlValue> { - self.columns.iter().map(|c| self.values.get(c).unwrap()).collect() + self.columns + .iter() + .map(|c| self.values.get(c).unwrap()) + .collect() } /// Returns the number of columns. diff --git a/crates/synor-database/src/sql/table.rs b/crates/synor-database/src/sql/table.rs index 7cce4e3..8920460 100644 --- a/crates/synor-database/src/sql/table.rs +++ b/crates/synor-database/src/sql/table.rs @@ -299,7 +299,10 @@ impl Table { let mut indexes = self.indexes.write(); if indexes.contains_key(&name) { - return Err(SqlError::InvalidOperation(format!("Index '{}' already exists", name))); + return Err(SqlError::InvalidOperation(format!( + "Index '{}' already exists", + name + ))); } let mut index = TableIndex::new(&name, &column, unique); @@ -319,7 +322,10 @@ impl Table { pub fn drop_index(&self, name: &str) -> Result<(), SqlError> { let mut indexes = self.indexes.write(); if indexes.remove(name).is_none() { - return Err(SqlError::InvalidOperation(format!("Index '{}' not found", name))); + return Err(SqlError::InvalidOperation(format!( + "Index '{}' not found", + name + ))); } Ok(()) } @@ -371,9 +377,9 @@ impl Table { /// Updates a row. pub fn update(&self, id: RowId, updates: HashMap) -> Result<(), SqlError> { let mut rows = self.rows.write(); - let row = rows.get_mut(&id).ok_or_else(|| { - SqlError::InvalidOperation(format!("Row {} not found", id)) - })?; + let row = rows + .get_mut(&id) + .ok_or_else(|| SqlError::InvalidOperation(format!("Row {} not found", id)))?; let old_values: HashMap = updates .keys() @@ -392,7 +398,10 @@ impl Table { let mut indexes = self.indexes.write(); for (_, index) in indexes.iter_mut() { if let Some(new_value) = updates.get(&index.column) { - let old_value = old_values.get(&index.column).cloned().unwrap_or(SqlValue::Null); + let old_value = old_values + .get(&index.column) + .cloned() + .unwrap_or(SqlValue::Null); index.remove(&old_value, &id); index.insert(new_value.clone(), id)?; } @@ -480,7 +489,10 @@ mod tests { values.insert("id".to_string(), SqlValue::Integer(1)); values.insert("name".to_string(), SqlValue::Text("Alice".to_string())); values.insert("age".to_string(), SqlValue::Integer(30)); - values.insert("email".to_string(), SqlValue::Text("alice@example.com".to_string())); + values.insert( + "email".to_string(), + SqlValue::Text("alice@example.com".to_string()), + ); let row_id = table.insert(values).unwrap(); assert_eq!(table.count(), 1); @@ -508,13 +520,19 @@ mod tests { let mut values1 = HashMap::new(); values1.insert("id".to_string(), SqlValue::Integer(1)); values1.insert("name".to_string(), SqlValue::Text("Alice".to_string())); - values1.insert("email".to_string(), SqlValue::Text("test@example.com".to_string())); + values1.insert( + "email".to_string(), + SqlValue::Text("test@example.com".to_string()), + ); table.insert(values1).unwrap(); let mut values2 = HashMap::new(); values2.insert("id".to_string(), SqlValue::Integer(2)); values2.insert("name".to_string(), SqlValue::Text("Bob".to_string())); - values2.insert("email".to_string(), SqlValue::Text("test@example.com".to_string())); + values2.insert( + "email".to_string(), + SqlValue::Text("test@example.com".to_string()), + ); let result = table.insert(values2); assert!(result.is_err()); // Duplicate email diff --git a/crates/synor-database/src/sql/transaction.rs b/crates/synor-database/src/sql/transaction.rs index 13f1fb5..76e803f 100644 --- a/crates/synor-database/src/sql/transaction.rs +++ b/crates/synor-database/src/sql/transaction.rs @@ -124,7 +124,12 @@ impl Transaction { } /// Records an insert operation. - pub fn record_insert(&mut self, table: String, row_id: RowId, values: HashMap) { + pub fn record_insert( + &mut self, + table: String, + row_id: RowId, + values: HashMap, + ) { self.operations.push(TransactionOp::Insert { table, row_id, @@ -149,7 +154,12 @@ impl Transaction { } /// Records a delete operation. - pub fn record_delete(&mut self, table: String, row_id: RowId, old_values: HashMap) { + pub fn record_delete( + &mut self, + table: String, + row_id: RowId, + old_values: HashMap, + ) { self.operations.push(TransactionOp::Delete { table, row_id, @@ -213,7 +223,10 @@ impl TransactionManager { .ok_or_else(|| SqlError::Transaction(format!("Transaction {} not found", id)))?; if !txn.is_active() { - return Err(SqlError::Transaction(format!("Transaction {} is not active", id))); + return Err(SqlError::Transaction(format!( + "Transaction {} is not active", + id + ))); } txn.operations.push(op); @@ -228,7 +241,10 @@ impl TransactionManager { .ok_or_else(|| SqlError::Transaction(format!("Transaction {} not found", id)))?; if !txn.is_active() { - return Err(SqlError::Transaction(format!("Transaction {} is not active", id))); + return Err(SqlError::Transaction(format!( + "Transaction {} is not active", + id + ))); } txn.mark_committed(); @@ -245,7 +261,10 @@ impl TransactionManager { .ok_or_else(|| SqlError::Transaction(format!("Transaction {} not found", id)))?; if !txn.is_active() { - return Err(SqlError::Transaction(format!("Transaction {} is not active", id))); + return Err(SqlError::Transaction(format!( + "Transaction {} is not active", + id + ))); } txn.mark_rolled_back(); diff --git a/crates/synor-database/src/sql/types.rs b/crates/synor-database/src/sql/types.rs index b3bf1f7..8f079c7 100644 --- a/crates/synor-database/src/sql/types.rs +++ b/crates/synor-database/src/sql/types.rs @@ -233,12 +233,8 @@ impl Ord for SqlValue { (SqlValue::Blob(a), SqlValue::Blob(b)) => a.cmp(b), (SqlValue::Boolean(a), SqlValue::Boolean(b)) => a.cmp(b), (SqlValue::Timestamp(a), SqlValue::Timestamp(b)) => a.cmp(b), - (SqlValue::Integer(a), SqlValue::Real(b)) => { - (*a as f64).to_bits().cmp(&b.to_bits()) - } - (SqlValue::Real(a), SqlValue::Integer(b)) => { - a.to_bits().cmp(&(*b as f64).to_bits()) - } + (SqlValue::Integer(a), SqlValue::Real(b)) => (*a as f64).to_bits().cmp(&b.to_bits()), + (SqlValue::Real(a), SqlValue::Integer(b)) => a.to_bits().cmp(&(*b as f64).to_bits()), // Different types: order by type discriminant _ => self.type_order().cmp(&other.type_order()), } diff --git a/crates/synor-database/src/timeseries.rs b/crates/synor-database/src/timeseries.rs index fc830a2..d82f997 100644 --- a/crates/synor-database/src/timeseries.rs +++ b/crates/synor-database/src/timeseries.rs @@ -158,11 +158,7 @@ impl Metric { /// Calculates sum in a time range. pub fn sum(&self, start: u64, end: u64) -> f64 { - self.data - .read() - .range(start..=end) - .map(|(_, &v)| v) - .sum() + self.data.read().range(start..=end).map(|(_, &v)| v).sum() } /// Counts data points in a time range. diff --git a/crates/synor-database/src/vector.rs b/crates/synor-database/src/vector.rs index d948198..f3c23fa 100644 --- a/crates/synor-database/src/vector.rs +++ b/crates/synor-database/src/vector.rs @@ -207,9 +207,7 @@ impl VectorIndex { let embeddings = self.embeddings.read(); let mut results: Vec = embeddings .values() - .filter(|e| { - namespace.map(|ns| e.namespace == ns).unwrap_or(true) - }) + .filter(|e| namespace.map(|ns| e.namespace == ns).unwrap_or(true)) .map(|e| { let score = self.calculate_similarity(&e.vector, query); VectorSearchResult { @@ -217,14 +215,14 @@ impl VectorIndex { score, } }) - .filter(|r| { - threshold.map(|t| r.score >= t).unwrap_or(true) - }) + .filter(|r| threshold.map(|t| r.score >= t).unwrap_or(true)) .collect(); // Sort by score descending results.sort_by(|a, b| { - b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal) + b.score + .partial_cmp(&a.score) + .unwrap_or(std::cmp::Ordering::Equal) }); // Apply limit @@ -234,8 +232,9 @@ impl VectorIndex { let elapsed = start.elapsed().as_millis() as f64; let mut stats = self.stats.write(); stats.searches += 1; - stats.avg_search_time_ms = - (stats.avg_search_time_ms * (stats.searches - 1) as f64 + elapsed) / stats.searches as f64; + stats.avg_search_time_ms = (stats.avg_search_time_ms * (stats.searches - 1) as f64 + + elapsed) + / stats.searches as f64; Ok(results) } @@ -329,7 +328,8 @@ impl VectorStore { namespace: Option<&str>, threshold: Option, ) -> Result, DatabaseError> { - self.default_index.search(query, limit, namespace, threshold) + self.default_index + .search(query, limit, namespace, threshold) } /// Gets an embedding by ID. @@ -388,10 +388,7 @@ pub fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 { /// Manhattan distance (L1) between two vectors. pub fn manhattan_distance(a: &[f32], b: &[f32]) -> f32 { - a.iter() - .zip(b.iter()) - .map(|(x, y)| (x - y).abs()) - .sum() + a.iter().zip(b.iter()).map(|(x, y)| (x - y).abs()).sum() } #[cfg(test)] @@ -414,9 +411,15 @@ mod tests { fn test_vector_insert_search() { let store = VectorStore::new(3); - store.insert(Embedding::new("a", vec![1.0, 0.0, 0.0])).unwrap(); - store.insert(Embedding::new("b", vec![0.9, 0.1, 0.0])).unwrap(); - store.insert(Embedding::new("c", vec![0.0, 1.0, 0.0])).unwrap(); + store + .insert(Embedding::new("a", vec![1.0, 0.0, 0.0])) + .unwrap(); + store + .insert(Embedding::new("b", vec![0.9, 0.1, 0.0])) + .unwrap(); + store + .insert(Embedding::new("c", vec![0.0, 1.0, 0.0])) + .unwrap(); let results = store.search(&[1.0, 0.0, 0.0], 2, None, None).unwrap(); @@ -429,8 +432,12 @@ mod tests { fn test_similarity_threshold() { let store = VectorStore::new(3); - store.insert(Embedding::new("a", vec![1.0, 0.0, 0.0])).unwrap(); - store.insert(Embedding::new("b", vec![0.0, 1.0, 0.0])).unwrap(); + store + .insert(Embedding::new("a", vec![1.0, 0.0, 0.0])) + .unwrap(); + store + .insert(Embedding::new("b", vec![0.0, 1.0, 0.0])) + .unwrap(); let results = store.search(&[1.0, 0.0, 0.0], 10, None, Some(0.5)).unwrap(); @@ -443,14 +450,16 @@ mod tests { fn test_namespace_filter() { let store = VectorStore::new(3); - store.insert( - Embedding::new("a", vec![1.0, 0.0, 0.0]).with_namespace("ns1") - ).unwrap(); - store.insert( - Embedding::new("b", vec![1.0, 0.0, 0.0]).with_namespace("ns2") - ).unwrap(); + store + .insert(Embedding::new("a", vec![1.0, 0.0, 0.0]).with_namespace("ns1")) + .unwrap(); + store + .insert(Embedding::new("b", vec![1.0, 0.0, 0.0]).with_namespace("ns2")) + .unwrap(); - let results = store.search(&[1.0, 0.0, 0.0], 10, Some("ns1"), None).unwrap(); + let results = store + .search(&[1.0, 0.0, 0.0], 10, Some("ns1"), None) + .unwrap(); assert_eq!(results.len(), 1); assert_eq!(results[0].embedding.id, "a"); diff --git a/crates/synor-economics/src/billing/credit.rs b/crates/synor-economics/src/billing/credit.rs index 278e6d8..be39c31 100644 --- a/crates/synor-economics/src/billing/credit.rs +++ b/crates/synor-economics/src/billing/credit.rs @@ -184,9 +184,7 @@ impl Credit { /// Check if credit is expired pub fn is_expired(&self) -> bool { - self.expires_at - .map(|exp| Utc::now() > exp) - .unwrap_or(false) + self.expires_at.map(|exp| Utc::now() > exp).unwrap_or(false) } /// Get remaining amount @@ -241,14 +239,21 @@ impl std::fmt::Display for CreditError { match self { CreditError::CreditInactive => write!(f, "Credit is no longer active"), CreditError::CreditExpired => write!(f, "Credit has expired"), - CreditError::InsufficientCredit { requested, available } => { + CreditError::InsufficientCredit { + requested, + available, + } => { write!( f, "Insufficient credit: requested {}, available {}", requested, available ) } - CreditError::ExceedsMaxCredit { current, requested, maximum } => { + CreditError::ExceedsMaxCredit { + current, + requested, + maximum, + } => { write!( f, "Credit exceeds maximum: current {}, requested {}, maximum {}", @@ -279,9 +284,9 @@ pub struct CreditPolicy { impl Default for CreditPolicy { fn default() -> Self { Self { - welcome_amount: Decimal::new(10, 0), // 10 SYNOR + welcome_amount: Decimal::new(10, 0), // 10 SYNOR referral_referrer_amount: Decimal::new(25, 0), // 25 SYNOR - referral_referee_amount: Decimal::new(10, 0), // 10 SYNOR + referral_referee_amount: Decimal::new(10, 0), // 10 SYNOR max_credit_per_account: Decimal::new(1000, 0), // 1000 SYNOR default_expiry_days: 365, } @@ -334,12 +339,20 @@ impl CreditManager { let referee_id = referee_id.into(); // Credit for the referrer - let referrer_credit = Credit::referral(&referrer_id, self.policy.referral_referrer_amount, &referee_id) - .with_expiry_days(self.policy.default_expiry_days); + let referrer_credit = Credit::referral( + &referrer_id, + self.policy.referral_referrer_amount, + &referee_id, + ) + .with_expiry_days(self.policy.default_expiry_days); // Credit for the referee - let referee_credit = Credit::referral(&referee_id, self.policy.referral_referee_amount, &referrer_id) - .with_expiry_days(self.policy.default_expiry_days); + let referee_credit = Credit::referral( + &referee_id, + self.policy.referral_referee_amount, + &referrer_id, + ) + .with_expiry_days(self.policy.default_expiry_days); self.credits .entry(referrer_id) @@ -448,13 +461,11 @@ impl CreditManager { let mut remaining = amount; // Sort by expiry date (soonest first) for FIFO - credits.sort_by(|a, b| { - match (&a.expires_at, &b.expires_at) { - (Some(a_exp), Some(b_exp)) => a_exp.cmp(b_exp), - (Some(_), None) => std::cmp::Ordering::Less, - (None, Some(_)) => std::cmp::Ordering::Greater, - (None, None) => a.created_at.cmp(&b.created_at), - } + credits.sort_by(|a, b| match (&a.expires_at, &b.expires_at) { + (Some(a_exp), Some(b_exp)) => a_exp.cmp(b_exp), + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (None, None) => a.created_at.cmp(&b.created_at), }); for credit in credits.iter_mut() { diff --git a/crates/synor-economics/src/billing/invoice.rs b/crates/synor-economics/src/billing/invoice.rs index a6d73f1..68ad019 100644 --- a/crates/synor-economics/src/billing/invoice.rs +++ b/crates/synor-economics/src/billing/invoice.rs @@ -319,12 +319,7 @@ mod tests { #[test] fn test_line_item() { - let item = InvoiceLineItem::new( - "Storage L2", - ServiceType::Storage, - dec!(10), - dec!(0.02), - ); + let item = InvoiceLineItem::new("Storage L2", ServiceType::Storage, dec!(10), dec!(0.02)); assert_eq!(item.amount, dec!(0.20)); } @@ -332,8 +327,18 @@ mod tests { #[test] fn test_invoice_calculate() { let mut invoice = Invoice::new("test") - .add_line_item(InvoiceLineItem::new("Storage", ServiceType::Storage, dec!(100), dec!(0.02))) - .add_line_item(InvoiceLineItem::new("Compute", ServiceType::Compute, dec!(10), dec!(0.50))); + .add_line_item(InvoiceLineItem::new( + "Storage", + ServiceType::Storage, + dec!(100), + dec!(0.02), + )) + .add_line_item(InvoiceLineItem::new( + "Compute", + ServiceType::Compute, + dec!(10), + dec!(0.50), + )); invoice.discount = dec!(1); invoice.calculate(); diff --git a/crates/synor-economics/src/billing/mod.rs b/crates/synor-economics/src/billing/mod.rs index 64d027a..20d94a4 100644 --- a/crates/synor-economics/src/billing/mod.rs +++ b/crates/synor-economics/src/billing/mod.rs @@ -164,17 +164,12 @@ impl BillingEngine { let outstanding: Vec<_> = account .invoice_ids .iter() - .filter(|id| { - invoices - .get(*id) - .map(|inv| !inv.is_paid()) - .unwrap_or(false) - }) + .filter(|id| invoices.get(*id).map(|inv| !inv.is_paid()).unwrap_or(false)) .cloned() .collect(); - let next_invoice = account.billing_cycle_start - + Duration::days(self.config.billing_cycle_days as i64); + let next_invoice = + account.billing_cycle_start + Duration::days(self.config.billing_cycle_days as i64); Ok(AccountBillingInfo { account_id: account_id.to_string(), @@ -198,11 +193,7 @@ impl BillingEngine { account.prepaid_balance += amount; - tracing::info!( - "Added {} SYNOR prepaid to account {}", - amount, - account_id - ); + tracing::info!("Added {} SYNOR prepaid to account {}", amount, account_id); Ok(()) } @@ -378,7 +369,9 @@ impl BillingEngine { PaymentMethod::CreditBalance => { // Deduct from credit balance if account.credit_balance < payment.amount { - return Err(EconomicsError::PaymentFailed("Insufficient credit balance".to_string())); + return Err(EconomicsError::PaymentFailed( + "Insufficient credit balance".to_string(), + )); } account.credit_balance -= payment.amount; payment.mark_completed(); @@ -484,7 +477,10 @@ impl BillingEngine { /// Get unpaid invoices for an account pub async fn get_unpaid_invoices(&self, account_id: &str) -> Result> { let all_invoices = self.get_account_invoices(account_id).await?; - Ok(all_invoices.into_iter().filter(|inv| !inv.is_paid()).collect()) + Ok(all_invoices + .into_iter() + .filter(|inv| !inv.is_paid()) + .collect()) } /// Get detailed account information including creation date @@ -617,7 +613,10 @@ mod tests { #[tokio::test] async fn test_register_account() { let engine = setup_engine().await; - engine.register_account("test_account", "standard").await.unwrap(); + engine + .register_account("test_account", "standard") + .await + .unwrap(); let info = engine.get_account_details("test_account").await.unwrap(); assert_eq!(info.account_id, "test_account"); @@ -627,7 +626,10 @@ mod tests { #[tokio::test] async fn test_add_prepaid() { let engine = setup_engine().await; - engine.register_account("prepaid_test", "standard").await.unwrap(); + engine + .register_account("prepaid_test", "standard") + .await + .unwrap(); engine.add_prepaid("prepaid_test", dec!(100)).await.unwrap(); let info = engine.get_account_info("prepaid_test").await.unwrap(); @@ -637,7 +639,10 @@ mod tests { #[tokio::test] async fn test_add_credit() { let engine = setup_engine().await; - engine.register_account("credit_test", "standard").await.unwrap(); + engine + .register_account("credit_test", "standard") + .await + .unwrap(); let credit = Credit::new("credit_test", dec!(50), "Welcome bonus"); engine.add_credit("credit_test", credit).await.unwrap(); diff --git a/crates/synor-economics/src/billing/payment.rs b/crates/synor-economics/src/billing/payment.rs index f5e4874..27fce34 100644 --- a/crates/synor-economics/src/billing/payment.rs +++ b/crates/synor-economics/src/billing/payment.rs @@ -210,17 +210,23 @@ impl PaymentProcessor { payment.mark_processing(); // Simulate transaction - let tx_hash = format!("0x{:x}000000000000000000000000000000000000000000000000000000000000", + let tx_hash = format!( + "0x{:x}000000000000000000000000000000000000000000000000000000000000", std::time::SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() - .as_secs()); + .as_secs() + ); payment.mark_confirmed(tx_hash); // Add addresses to metadata - payment.metadata.insert("from".to_string(), from_address.to_string()); - payment.metadata.insert("to".to_string(), to_address.to_string()); + payment + .metadata + .insert("from".to_string(), from_address.to_string()); + payment + .metadata + .insert("to".to_string(), to_address.to_string()); payment.mark_completed(); @@ -325,7 +331,10 @@ mod tests { payment.mark_failed("Insufficient funds"); assert!(!payment.is_complete()); - assert_eq!(payment.failure_reason, Some("Insufficient funds".to_string())); + assert_eq!( + payment.failure_reason, + Some("Insufficient funds".to_string()) + ); } #[tokio::test] diff --git a/crates/synor-economics/src/calculator/estimator.rs b/crates/synor-economics/src/calculator/estimator.rs index 705551b..7988b9e 100644 --- a/crates/synor-economics/src/calculator/estimator.rs +++ b/crates/synor-economics/src/calculator/estimator.rs @@ -177,7 +177,10 @@ impl CostEstimator { /// Estimate cost for a usage projection pub async fn estimate(&self, projection: UsageProjection) -> Result { - let tier_name = projection.tier.clone().unwrap_or_else(|| "free".to_string()); + let tier_name = projection + .tier + .clone() + .unwrap_or_else(|| "free".to_string()); let months = projection.duration_months.max(1); let mut by_service = HashMap::new(); diff --git a/crates/synor-economics/src/error.rs b/crates/synor-economics/src/error.rs index d5ffecd..23b5a55 100644 --- a/crates/synor-economics/src/error.rs +++ b/crates/synor-economics/src/error.rs @@ -22,10 +22,7 @@ pub enum EconomicsError { /// Insufficient balance #[error("Insufficient balance: required {required}, available {available}")] - InsufficientBalance { - required: String, - available: String, - }, + InsufficientBalance { required: String, available: String }, /// Insufficient funds (with Decimal values) #[error("Insufficient funds: required {required}, available {available}")] @@ -36,10 +33,7 @@ pub enum EconomicsError { /// Stale price with specific asset #[error("Price stale for {asset}: {age_seconds} seconds old")] - StalePrice { - asset: String, - age_seconds: i64, - }, + StalePrice { asset: String, age_seconds: i64 }, /// Account not found #[error("Account not found: {0}")] diff --git a/crates/synor-economics/src/lib.rs b/crates/synor-economics/src/lib.rs index 0b3679a..360a7ed 100644 --- a/crates/synor-economics/src/lib.rs +++ b/crates/synor-economics/src/lib.rs @@ -251,9 +251,7 @@ impl EconomicsManager { use rust_decimal_macros::dec; // Default to development oracle with mock feeds at $1.50 base price - let oracle = Arc::new(RwLock::new( - oracle::OracleFactory::development(dec!(1.50)) - )); + let oracle = Arc::new(RwLock::new(oracle::OracleFactory::development(dec!(1.50)))); let pricing = Arc::new(PricingEngine::new()); let metering = Arc::new(MeteringService::new(pricing.clone())); let billing = Arc::new(BillingEngine::new(metering.clone(), pricing.clone())); @@ -270,9 +268,7 @@ impl EconomicsManager { /// Create an economics manager with production oracle configuration pub fn with_production_oracle(config: oracle::ProductionOracleConfig) -> Self { - let oracle = Arc::new(RwLock::new( - oracle::OracleFactory::production(config) - )); + let oracle = Arc::new(RwLock::new(oracle::OracleFactory::production(config))); let pricing = Arc::new(PricingEngine::new()); let metering = Arc::new(MeteringService::new(pricing.clone())); let billing = Arc::new(BillingEngine::new(metering.clone(), pricing.clone())); diff --git a/crates/synor-economics/src/metering/mod.rs b/crates/synor-economics/src/metering/mod.rs index b2238c5..7b15ace 100644 --- a/crates/synor-economics/src/metering/mod.rs +++ b/crates/synor-economics/src/metering/mod.rs @@ -209,23 +209,22 @@ impl MeteringService { } // Calculate cost for this event - let cost = self.pricing.calculate_cost( - event.service_type, - event.resource_unit, - event.amount, - )?; + let cost = + self.pricing + .calculate_cost(event.service_type, event.resource_unit, event.amount)?; // Update current usage { let mut usage = self.current_usage.write().await; - let account_usage = usage.entry(event.account_id.clone()).or_insert_with(|| { - AccountUsage { - account_id: event.account_id.clone(), - by_service: HashMap::new(), - current_period_start: Utc::now(), - last_event: None, - } - }); + let account_usage = + usage + .entry(event.account_id.clone()) + .or_insert_with(|| AccountUsage { + account_id: event.account_id.clone(), + by_service: HashMap::new(), + current_period_start: Utc::now(), + last_event: None, + }); *account_usage .by_service @@ -263,7 +262,8 @@ impl MeteringService { ServiceType::Storage, ResourceUnit::Bytes, Decimal::from(usage.bytes_stored), - )).await?; + )) + .await?; } // Storage: bytes retrieved @@ -273,7 +273,8 @@ impl MeteringService { ServiceType::Storage, ResourceUnit::BandwidthGb, Decimal::from(usage.bytes_retrieved) / Decimal::from(1_073_741_824u64), // to GB - )).await?; + )) + .await?; } Ok(()) @@ -288,7 +289,8 @@ impl MeteringService { ServiceType::Hosting, ResourceUnit::BandwidthGb, Decimal::from(usage.bandwidth_bytes) / Decimal::from(1_073_741_824u64), - )).await?; + )) + .await?; } // Custom domains @@ -298,7 +300,8 @@ impl MeteringService { ServiceType::Hosting, ResourceUnit::Domains, Decimal::from(usage.custom_domains), - )).await?; + )) + .await?; } Ok(()) @@ -313,7 +316,8 @@ impl MeteringService { ServiceType::Database, ResourceUnit::Queries, Decimal::from(usage.queries), - )).await?; + )) + .await?; } // Vector searches @@ -323,7 +327,8 @@ impl MeteringService { ServiceType::Database, ResourceUnit::VectorSearches, Decimal::from(usage.vector_searches), - )).await?; + )) + .await?; } // Storage @@ -333,7 +338,8 @@ impl MeteringService { ServiceType::Database, ResourceUnit::GbMonth, Decimal::from(usage.storage_bytes) / Decimal::from(1_073_741_824u64), - )).await?; + )) + .await?; } Ok(()) @@ -348,7 +354,8 @@ impl MeteringService { ServiceType::Compute, ResourceUnit::CpuCoreHours, Decimal::from(usage.cpu_core_seconds) / Decimal::from(3600), - )).await?; + )) + .await?; } // GPU hours @@ -358,7 +365,8 @@ impl MeteringService { ServiceType::Compute, ResourceUnit::GpuHours, Decimal::from(usage.gpu_seconds) / Decimal::from(3600), - )).await?; + )) + .await?; } // Memory GB hours @@ -368,7 +376,8 @@ impl MeteringService { ServiceType::Compute, ResourceUnit::MemoryGbHours, Decimal::from(usage.memory_gb_seconds) / Decimal::from(3600), - )).await?; + )) + .await?; } // Invocations (serverless) @@ -378,7 +387,8 @@ impl MeteringService { ServiceType::Compute, ResourceUnit::Invocations, Decimal::from(usage.invocations), - )).await?; + )) + .await?; } Ok(()) @@ -393,7 +403,8 @@ impl MeteringService { ServiceType::Network, ResourceUnit::BandwidthGb, Decimal::from(total_bytes) / Decimal::from(1_073_741_824u64), - )).await?; + )) + .await?; } Ok(()) @@ -421,10 +432,7 @@ impl MeteringService { // Check buffered events let buffer = self.event_buffer.read().await; for event in buffer.iter() { - if event.account_id == account_id - && event.timestamp >= start - && event.timestamp < end - { + if event.account_id == account_id && event.timestamp >= start && event.timestamp < end { let cost = self.pricing.calculate_cost( event.service_type, event.resource_unit, diff --git a/crates/synor-economics/src/oracle/anomaly.rs b/crates/synor-economics/src/oracle/anomaly.rs index 161547c..08f5243 100644 --- a/crates/synor-economics/src/oracle/anomaly.rs +++ b/crates/synor-economics/src/oracle/anomaly.rs @@ -224,9 +224,8 @@ impl IsolationTree { // Random split point let split = min_val + (max_val - min_val) * 0.5; - let (left_data, right_data): (Vec<_>, Vec<_>) = data.iter() - .cloned() - .partition(|row| row[feature] < split); + let (left_data, right_data): (Vec<_>, Vec<_>) = + data.iter().cloned().partition(|row| row[feature] < split); Some(Self { split_feature: feature, @@ -280,7 +279,8 @@ impl IsolationForest { let trees: Vec<_> = (0..n_trees) .filter_map(|i| { // Subsample with deterministic "randomness" based on tree index - let sample: Vec<_> = data.iter() + let sample: Vec<_> = data + .iter() .enumerate() .filter(|(j, _)| (i + j) % 3 != 0) .take(sample_size) @@ -299,9 +299,12 @@ impl IsolationForest { return 0.5; } - let avg_path: f64 = self.trees.iter() + let avg_path: f64 = self + .trees + .iter() .map(|tree| tree.path_length(point, 0.0)) - .sum::() / self.trees.len() as f64; + .sum::() + / self.trees.len() as f64; let c = c_factor(self.sample_size); if c < f64::EPSILON { @@ -365,17 +368,28 @@ impl PairDetector { // Track addresses if !point.addresses.is_empty() { - self.recent_addresses.push_back((point.timestamp, point.addresses.clone())); + self.recent_addresses + .push_back((point.timestamp, point.addresses.clone())); } self.price_history.push_back(point); // Cleanup old data let cutoff = Utc::now() - Duration::hours(24); - while self.price_history.front().map(|p| p.timestamp < cutoff).unwrap_or(false) { + while self + .price_history + .front() + .map(|p| p.timestamp < cutoff) + .unwrap_or(false) + { self.price_history.pop_front(); } - while self.recent_addresses.front().map(|(t, _)| *t < cutoff).unwrap_or(false) { + while self + .recent_addresses + .front() + .map(|(t, _)| *t < cutoff) + .unwrap_or(false) + { self.recent_addresses.pop_front(); } } @@ -386,7 +400,9 @@ impl PairDetector { } // Build feature vectors: [price, volume, return, bid/ask ratio] - let data: Vec> = self.price_history.iter() + let data: Vec> = self + .price_history + .iter() .skip(1) .zip(self.price_history.iter()) .map(|(curr, prev)| { @@ -403,7 +419,11 @@ impl PairDetector { (Some(bid), Some(ask)) => { let bid_f = bid.to_string().parse::().unwrap_or(0.0); let ask_f = ask.to_string().parse::().unwrap_or(1.0); - if ask_f > 0.0 { bid_f / ask_f } else { 1.0 } + if ask_f > 0.0 { + bid_f / ask_f + } else { + 1.0 + } } _ => 1.0, }; @@ -462,19 +482,34 @@ impl AnomalyDetector { // Run all detectors using the immutable reference first if let Some(detector) = self.detectors.get(pair) { - if let Some(a) = Self::detect_price_outlier_impl(pair, &data, detector, min_data_points, z_score_threshold) { + if let Some(a) = Self::detect_price_outlier_impl( + pair, + &data, + detector, + min_data_points, + z_score_threshold, + ) { anomalies.push(a); } - if let Some(a) = Self::detect_volume_spike_impl(pair, &data, detector, min_data_points, volume_spike_multiplier) { + if let Some(a) = Self::detect_volume_spike_impl( + pair, + &data, + detector, + min_data_points, + volume_spike_multiplier, + ) { anomalies.push(a); } - if let Some(a) = Self::detect_wash_trading_impl(pair, &data, detector, wash_trading_window) { + if let Some(a) = + Self::detect_wash_trading_impl(pair, &data, detector, wash_trading_window) + { anomalies.push(a); } if let Some(a) = Self::detect_pump_dump_impl(pair, detector, pump_dump_window) { anomalies.push(a); } - if let Some(a) = Self::detect_flash_loan_impl(pair, &data, detector, flash_loan_window) { + if let Some(a) = Self::detect_flash_loan_impl(pair, &data, detector, flash_loan_window) + { anomalies.push(a); } if ml_enabled { @@ -493,7 +528,13 @@ impl AnomalyDetector { anomalies } - fn detect_price_outlier_impl(pair: &str, data: &MarketDataPoint, detector: &PairDetector, min_data_points: usize, z_score_threshold: f64) -> Option { + fn detect_price_outlier_impl( + pair: &str, + data: &MarketDataPoint, + detector: &PairDetector, + min_data_points: usize, + z_score_threshold: f64, + ) -> Option { if detector.price_stats.count < min_data_points { return None; } @@ -531,7 +572,13 @@ impl AnomalyDetector { } } - fn detect_volume_spike_impl(pair: &str, data: &MarketDataPoint, detector: &PairDetector, min_data_points: usize, volume_spike_multiplier: f64) -> Option { + fn detect_volume_spike_impl( + pair: &str, + data: &MarketDataPoint, + detector: &PairDetector, + min_data_points: usize, + volume_spike_multiplier: f64, + ) -> Option { if detector.volume_stats.count < min_data_points { return None; } @@ -550,7 +597,9 @@ impl AnomalyDetector { confidence: 0.75, description: format!( "Volume {} is {:.1}x the average {:.2}", - data.volume, volume_f64 / mean, mean + data.volume, + volume_f64 / mean, + mean ), data: AnomalyData { current_value: data.volume, @@ -566,7 +615,12 @@ impl AnomalyDetector { } } - fn detect_wash_trading_impl(pair: &str, data: &MarketDataPoint, detector: &PairDetector, wash_trading_window: i64) -> Option { + fn detect_wash_trading_impl( + pair: &str, + data: &MarketDataPoint, + detector: &PairDetector, + wash_trading_window: i64, + ) -> Option { if data.addresses.is_empty() { return None; } @@ -617,14 +671,20 @@ impl AnomalyDetector { None } - fn detect_pump_dump_impl(pair: &str, detector: &PairDetector, pump_dump_window: i64) -> Option { + fn detect_pump_dump_impl( + pair: &str, + detector: &PairDetector, + pump_dump_window: i64, + ) -> Option { // Need enough history if detector.price_history.len() < 10 { return None; } let window_start = Utc::now() - Duration::minutes(pump_dump_window); - let prices: Vec<_> = detector.price_history.iter() + let prices: Vec<_> = detector + .price_history + .iter() .filter(|p| p.timestamp >= window_start) .map(|p| p.price.to_string().parse::().unwrap_or(0.0)) .collect(); @@ -635,7 +695,9 @@ impl AnomalyDetector { // Find max and check for reversal let max_price = prices.iter().copied().fold(f64::MIN, f64::max); - let max_idx = prices.iter().position(|&p| (p - max_price).abs() < f64::EPSILON)?; + let max_idx = prices + .iter() + .position(|&p| (p - max_price).abs() < f64::EPSILON)?; let first_price = prices.first()?; let last_price = prices.last()?; @@ -672,10 +734,17 @@ impl AnomalyDetector { None } - fn detect_flash_loan_impl(pair: &str, data: &MarketDataPoint, detector: &PairDetector, flash_loan_window: i64) -> Option { + fn detect_flash_loan_impl( + pair: &str, + data: &MarketDataPoint, + detector: &PairDetector, + flash_loan_window: i64, + ) -> Option { // Flash loan signature: huge volume spike + quick price movement + reversal let window_start = Utc::now() - Duration::seconds(flash_loan_window); - let recent: Vec<_> = detector.price_history.iter() + let recent: Vec<_> = detector + .price_history + .iter() .filter(|p| p.timestamp >= window_start) .collect(); @@ -683,11 +752,13 @@ impl AnomalyDetector { return None; } - let volumes: Vec = recent.iter() + let volumes: Vec = recent + .iter() .map(|p| p.volume.to_string().parse::().unwrap_or(0.0)) .collect(); - let prices: Vec = recent.iter() + let prices: Vec = recent + .iter() .map(|p| p.price.to_string().parse::().unwrap_or(0.0)) .collect(); @@ -706,7 +777,10 @@ impl AnomalyDetector { // Big spike and quick reversal if spike > 10.0 && reversal > 8.0 { let mut context = HashMap::new(); - context.insert("volume_spike".to_string(), format!("{:.0}x", max_volume / avg_volume)); + context.insert( + "volume_spike".to_string(), + format!("{:.0}x", max_volume / avg_volume), + ); context.insert("price_spike".to_string(), format!("{:.1}%", spike)); return Some(Anomaly { @@ -717,7 +791,8 @@ impl AnomalyDetector { confidence: 0.65, description: format!( "Suspected flash loan attack: {}x volume, {:.1}% price spike", - max_volume / avg_volume, spike + max_volume / avg_volume, + spike ), data: AnomalyData { current_value: data.volume, @@ -734,7 +809,11 @@ impl AnomalyDetector { None } - fn detect_ml_anomaly_impl(pair: &str, data: &MarketDataPoint, detector: &PairDetector) -> Option { + fn detect_ml_anomaly_impl( + pair: &str, + data: &MarketDataPoint, + detector: &PairDetector, + ) -> Option { let forest = detector.isolation_forest.as_ref()?; if detector.price_history.is_empty() { @@ -756,7 +835,11 @@ impl AnomalyDetector { (Some(bid), Some(ask)) => { let bid_f = bid.to_string().parse::().ok()?; let ask_f = ask.to_string().parse::().ok()?; - if ask_f > 0.0 { bid_f / ask_f } else { 1.0 } + if ask_f > 0.0 { + bid_f / ask_f + } else { + 1.0 + } } _ => 1.0, }; @@ -774,10 +857,7 @@ impl AnomalyDetector { detected_at: Utc::now(), severity: score, confidence: 0.6, - description: format!( - "ML model detected anomaly with score {:.3}", - score - ), + description: format!("ML model detected anomaly with score {:.3}", score), data: AnomalyData { current_value: data.price, expected_value: Decimal::from_f64_retain(detector.price_stats.mean)?, @@ -798,7 +878,8 @@ impl AnomalyDetector { /// Get recent anomalies for a pair pub fn get_anomalies(&self, pair: &str) -> Vec { - self.detectors.get(pair) + self.detectors + .get(pair) .map(|d| d.anomalies.clone()) .unwrap_or_default() } @@ -807,11 +888,14 @@ impl AnomalyDetector { pub fn get_stats(&self, pair: &str) -> Option { let detector = self.detectors.get(pair)?; - let by_type: HashMap = detector.anomalies.iter() - .fold(HashMap::new(), |mut acc, a| { - *acc.entry(a.anomaly_type.clone()).or_insert(0) += 1; - acc - }); + let by_type: HashMap = + detector + .anomalies + .iter() + .fold(HashMap::new(), |mut acc, a| { + *acc.entry(a.anomaly_type.clone()).or_insert(0) += 1; + acc + }); Some(AnomalyStats { total_anomalies: detector.anomalies.len(), @@ -913,7 +997,9 @@ mod tests { let anomalies = detector.process("SYNOR/USD", outlier); assert!(!anomalies.is_empty()); - assert!(anomalies.iter().any(|a| a.anomaly_type == AnomalyType::PriceOutlier)); + assert!(anomalies + .iter() + .any(|a| a.anomaly_type == AnomalyType::PriceOutlier)); } #[test] @@ -946,6 +1032,8 @@ mod tests { }; let anomalies = detector.process("SYNOR/USD", spike); - assert!(anomalies.iter().any(|a| a.anomaly_type == AnomalyType::VolumeSpike)); + assert!(anomalies + .iter() + .any(|a| a.anomaly_type == AnomalyType::VolumeSpike)); } } diff --git a/crates/synor-economics/src/oracle/circuit_breaker.rs b/crates/synor-economics/src/oracle/circuit_breaker.rs index f9e2915..4b1734b 100644 --- a/crates/synor-economics/src/oracle/circuit_breaker.rs +++ b/crates/synor-economics/src/oracle/circuit_breaker.rs @@ -40,17 +40,11 @@ pub enum TriggerReason { threshold: SynorDecimal, }, /// Multiple oracle sources disagree - OracleDisagreement { - spread_percent: Decimal, - }, + OracleDisagreement { spread_percent: Decimal }, /// Manual trigger by admin - ManualHalt { - reason: String, - }, + ManualHalt { reason: String }, /// Cascade from related market - CascadeTrigger { - source_pair: String, - }, + CascadeTrigger { source_pair: String }, } /// Circuit breaker event @@ -98,12 +92,12 @@ pub struct CircuitBreakerConfig { impl Default for CircuitBreakerConfig { fn default() -> Self { Self { - max_1m_change: Decimal::new(10, 2), // 10% - max_5m_change: Decimal::new(20, 2), // 20% - max_1h_change: Decimal::new(50, 2), // 50% + max_1m_change: Decimal::new(10, 2), // 10% + max_5m_change: Decimal::new(20, 2), // 20% + max_1h_change: Decimal::new(50, 2), // 50% max_twap_deviation: Decimal::new(30, 2), // 30% - min_liquidity: Decimal::new(10000, 0), // $10k - max_oracle_spread: Decimal::new(5, 2), // 5% + min_liquidity: Decimal::new(10000, 0), // $10k + max_oracle_spread: Decimal::new(5, 2), // 5% cooldown_duration: Duration::minutes(5), recovery_checks: 3, cascade_enabled: true, @@ -173,7 +167,12 @@ impl PairCircuitBreaker { // Keep only last 24 hours let cutoff = Utc::now() - Duration::hours(24); - while self.price_history.front().map(|s| s.timestamp < cutoff).unwrap_or(false) { + while self + .price_history + .front() + .map(|s| s.timestamp < cutoff) + .unwrap_or(false) + { self.price_history.pop_front(); } @@ -192,7 +191,8 @@ impl PairCircuitBreaker { fn get_price_at(&self, seconds_ago: i64) -> Option { let target = Utc::now() - Duration::seconds(seconds_ago); - self.price_history.iter() + self.price_history + .iter() .rev() .find(|s| s.timestamp <= target) .map(|s| s.price) @@ -232,7 +232,9 @@ impl CircuitBreakerManager { price: SynorDecimal, liquidity: Option, ) -> Result { - let breaker = self.breakers.entry(pair.to_string()) + let breaker = self + .breakers + .entry(pair.to_string()) .or_insert_with(PairCircuitBreaker::new); // Use the convenience method for real-time price recording @@ -256,7 +258,9 @@ impl CircuitBreakerManager { liquidity: Option, timestamp: DateTime, ) -> Result { - let breaker = self.breakers.entry(pair.to_string()) + let breaker = self + .breakers + .entry(pair.to_string()) .or_insert_with(PairCircuitBreaker::new); breaker.record_price_at(price, liquidity, timestamp); @@ -273,22 +277,26 @@ impl CircuitBreakerManager { /// Check all trigger conditions fn check_triggers(&mut self, pair: &str) -> Result<()> { - let breaker = self.breakers.get(pair).ok_or_else(|| - EconomicsError::PriceFeedUnavailable(pair.to_string()) - )?; + let breaker = self + .breakers + .get(pair) + .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair.to_string()))?; - let current = breaker.current_price().ok_or_else(|| - EconomicsError::PriceFeedUnavailable(pair.to_string()) - )?; + let current = breaker + .current_price() + .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair.to_string()))?; // Check 1-minute change if let Some(price_1m) = breaker.get_price_at(60) { let change = ((current - price_1m) / price_1m).abs(); if change > self.config.max_1m_change { - return self.trigger_breaker(pair, TriggerReason::RapidPriceChange { - change_percent: change * Decimal::ONE_HUNDRED, - window_seconds: 60, - }); + return self.trigger_breaker( + pair, + TriggerReason::RapidPriceChange { + change_percent: change * Decimal::ONE_HUNDRED, + window_seconds: 60, + }, + ); } } @@ -296,10 +304,13 @@ impl CircuitBreakerManager { if let Some(price_5m) = breaker.get_price_at(300) { let change = ((current - price_5m) / price_5m).abs(); if change > self.config.max_5m_change { - return self.trigger_breaker(pair, TriggerReason::RapidPriceChange { - change_percent: change * Decimal::ONE_HUNDRED, - window_seconds: 300, - }); + return self.trigger_breaker( + pair, + TriggerReason::RapidPriceChange { + change_percent: change * Decimal::ONE_HUNDRED, + window_seconds: 300, + }, + ); } } @@ -307,10 +318,13 @@ impl CircuitBreakerManager { if let Some(price_1h) = breaker.get_price_at(3600) { let change = ((current - price_1h) / price_1h).abs(); if change > self.config.max_1h_change { - return self.trigger_breaker(pair, TriggerReason::RapidPriceChange { - change_percent: change * Decimal::ONE_HUNDRED, - window_seconds: 3600, - }); + return self.trigger_breaker( + pair, + TriggerReason::RapidPriceChange { + change_percent: change * Decimal::ONE_HUNDRED, + window_seconds: 3600, + }, + ); } } @@ -318,20 +332,26 @@ impl CircuitBreakerManager { if let Some(twap) = breaker.twap_24h { let deviation = ((current - twap) / twap).abs(); if deviation > self.config.max_twap_deviation { - return self.trigger_breaker(pair, TriggerReason::ExcessiveDeviation { - deviation_percent: deviation * Decimal::ONE_HUNDRED, - reference_price: twap, - }); + return self.trigger_breaker( + pair, + TriggerReason::ExcessiveDeviation { + deviation_percent: deviation * Decimal::ONE_HUNDRED, + reference_price: twap, + }, + ); } } // Check liquidity if let Some(liquidity) = breaker.current_liquidity() { if liquidity < self.config.min_liquidity { - return self.trigger_breaker(pair, TriggerReason::LowLiquidity { - current: liquidity, - threshold: self.config.min_liquidity, - }); + return self.trigger_breaker( + pair, + TriggerReason::LowLiquidity { + current: liquidity, + threshold: self.config.min_liquidity, + }, + ); } } @@ -340,9 +360,10 @@ impl CircuitBreakerManager { /// Trigger the circuit breaker fn trigger_breaker(&mut self, pair: &str, reason: TriggerReason) -> Result<()> { - let breaker = self.breakers.get_mut(pair).ok_or_else(|| - EconomicsError::PriceFeedUnavailable(pair.to_string()) - )?; + let breaker = self + .breakers + .get_mut(pair) + .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair.to_string()))?; let event = CircuitEvent { pair: pair.to_string(), @@ -361,7 +382,10 @@ impl CircuitBreakerManager { // Check cascade triggers if self.config.cascade_enabled { - let cascades: Vec<_> = self.config.cascade_pairs.iter() + let cascades: Vec<_> = self + .config + .cascade_pairs + .iter() .filter(|(source, _)| source == pair) .map(|(_, target)| target.clone()) .collect(); @@ -407,10 +431,15 @@ impl CircuitBreakerManager { // Get current state first (immutable borrow) let (current_state, triggered_at, trigger_reason) = { - let breaker = self.breakers.get(pair).ok_or_else(|| - EconomicsError::PriceFeedUnavailable(pair.to_string()) - )?; - (breaker.state, breaker.triggered_at, breaker.trigger_reason.clone()) + let breaker = self + .breakers + .get(pair) + .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair.to_string()))?; + ( + breaker.state, + breaker.triggered_at, + breaker.trigger_reason.clone(), + ) }; // Check stability for half-open state (immutable borrow) @@ -421,9 +450,10 @@ impl CircuitBreakerManager { }; // Now get mutable reference for updates - let breaker = self.breakers.get_mut(pair).ok_or_else(|| - EconomicsError::PriceFeedUnavailable(pair.to_string()) - )?; + let breaker = self + .breakers + .get_mut(pair) + .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair.to_string()))?; match current_state { CircuitState::Open => { @@ -435,9 +465,9 @@ impl CircuitBreakerManager { pair: pair.to_string(), from_state: CircuitState::Open, to_state: CircuitState::HalfOpen, - reason: trigger_reason.clone().unwrap_or( - TriggerReason::ManualHalt { reason: "Unknown".into() } - ), + reason: trigger_reason.clone().unwrap_or(TriggerReason::ManualHalt { + reason: "Unknown".into(), + }), timestamp: Utc::now(), cooldown: None, }; @@ -457,9 +487,9 @@ impl CircuitBreakerManager { pair: pair.to_string(), from_state: CircuitState::HalfOpen, to_state: CircuitState::Closed, - reason: trigger_reason.unwrap_or( - TriggerReason::ManualHalt { reason: "Recovery".into() } - ), + reason: trigger_reason.unwrap_or(TriggerReason::ManualHalt { + reason: "Recovery".into(), + }), timestamp: Utc::now(), cooldown: None, }; @@ -482,9 +512,10 @@ impl CircuitBreakerManager { /// Check if market conditions are stable fn is_stable(&self, pair: &str) -> Result { - let breaker = self.breakers.get(pair).ok_or_else(|| - EconomicsError::PriceFeedUnavailable(pair.to_string()) - )?; + let breaker = self + .breakers + .get(pair) + .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair.to_string()))?; let current = match breaker.current_price() { Some(p) => p, @@ -511,7 +542,8 @@ impl CircuitBreakerManager { /// Get current state for a pair pub fn get_state(&self, pair: &str) -> CircuitState { - self.breakers.get(pair) + self.breakers + .get(pair) .map(|b| b.state) .unwrap_or(CircuitState::Closed) } @@ -523,25 +555,32 @@ impl CircuitBreakerManager { /// Manually trigger circuit breaker pub fn manual_halt(&mut self, pair: &str, reason: impl Into) -> Result<()> { - self.breakers.entry(pair.to_string()) + self.breakers + .entry(pair.to_string()) .or_insert_with(PairCircuitBreaker::new); - self.trigger_breaker(pair, TriggerReason::ManualHalt { - reason: reason.into(), - }) + self.trigger_breaker( + pair, + TriggerReason::ManualHalt { + reason: reason.into(), + }, + ) } /// Manually reset circuit breaker pub fn manual_reset(&mut self, pair: &str) -> Result<()> { - let breaker = self.breakers.get_mut(pair).ok_or_else(|| - EconomicsError::PriceFeedUnavailable(pair.to_string()) - )?; + let breaker = self + .breakers + .get_mut(pair) + .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair.to_string()))?; let event = CircuitEvent { pair: pair.to_string(), from_state: breaker.state, to_state: CircuitState::Closed, - reason: TriggerReason::ManualHalt { reason: "Manual reset".into() }, + reason: TriggerReason::ManualHalt { + reason: "Manual reset".into(), + }, timestamp: Utc::now(), cooldown: None, }; @@ -558,26 +597,32 @@ impl CircuitBreakerManager { /// Record oracle disagreement pub fn record_oracle_spread(&mut self, pair: &str, spread: Decimal) -> Result<()> { if spread > self.config.max_oracle_spread { - self.breakers.entry(pair.to_string()) + self.breakers + .entry(pair.to_string()) .or_insert_with(PairCircuitBreaker::new); - self.trigger_breaker(pair, TriggerReason::OracleDisagreement { - spread_percent: spread * Decimal::ONE_HUNDRED, - })?; + self.trigger_breaker( + pair, + TriggerReason::OracleDisagreement { + spread_percent: spread * Decimal::ONE_HUNDRED, + }, + )?; } Ok(()) } /// Get event history for a pair pub fn get_events(&self, pair: &str) -> Vec { - self.breakers.get(pair) + self.breakers + .get(pair) .map(|b| b.events.clone()) .unwrap_or_default() } /// Get all currently halted pairs pub fn get_halted_pairs(&self) -> Vec<(String, CircuitState, Option)> { - self.breakers.iter() + self.breakers + .iter() .filter(|(_, b)| b.state != CircuitState::Closed) .map(|(pair, b)| (pair.clone(), b.state, b.trigger_reason.clone())) .collect() @@ -586,8 +631,16 @@ impl CircuitBreakerManager { /// Get summary statistics pub fn get_stats(&self) -> CircuitBreakerStats { let total = self.breakers.len(); - let open = self.breakers.values().filter(|b| b.state == CircuitState::Open).count(); - let half_open = self.breakers.values().filter(|b| b.state == CircuitState::HalfOpen).count(); + let open = self + .breakers + .values() + .filter(|b| b.state == CircuitState::Open) + .count(); + let half_open = self + .breakers + .values() + .filter(|b| b.state == CircuitState::HalfOpen) + .count(); let total_events: usize = self.breakers.values().map(|b| b.events.len()).sum(); CircuitBreakerStats { @@ -628,7 +681,9 @@ mod tests { // Normal price movements should not trigger for i in 0..10 { let price = dec!(100) + Decimal::from(i); - let state = manager.record_price("SYNOR/USD", price, Some(dec!(100000))).unwrap(); + let state = manager + .record_price("SYNOR/USD", price, Some(dec!(100000))) + .unwrap(); assert_eq!(state, CircuitState::Closed); } } @@ -641,10 +696,19 @@ mod tests { let now = Utc::now(); // Record baseline 2 minutes ago - manager.record_price_at("SYNOR/USD", dec!(100), Some(dec!(100000)), now - Duration::minutes(2)).unwrap(); + manager + .record_price_at( + "SYNOR/USD", + dec!(100), + Some(dec!(100000)), + now - Duration::minutes(2), + ) + .unwrap(); // Simulate 15% drop (exceeds 10% 1-minute threshold) - let state = manager.record_price_at("SYNOR/USD", dec!(85), Some(dec!(100000)), now).unwrap(); + let state = manager + .record_price_at("SYNOR/USD", dec!(85), Some(dec!(100000)), now) + .unwrap(); assert_eq!(state, CircuitState::Open); } @@ -653,7 +717,9 @@ mod tests { let mut manager = CircuitBreakerManager::new(); // Record with very low liquidity - let state = manager.record_price("SYNOR/USD", dec!(100), Some(dec!(100))).unwrap(); + let state = manager + .record_price("SYNOR/USD", dec!(100), Some(dec!(100))) + .unwrap(); assert_eq!(state, CircuitState::Open); } @@ -661,11 +727,15 @@ mod tests { fn test_manual_halt_and_reset() { let mut manager = CircuitBreakerManager::new(); - manager.record_price("SYNOR/USD", dec!(100), Some(dec!(100000))).unwrap(); + manager + .record_price("SYNOR/USD", dec!(100), Some(dec!(100000))) + .unwrap(); assert!(manager.is_trading_allowed("SYNOR/USD")); // Manual halt - manager.manual_halt("SYNOR/USD", "Scheduled maintenance").unwrap(); + manager + .manual_halt("SYNOR/USD", "Scheduled maintenance") + .unwrap(); assert!(!manager.is_trading_allowed("SYNOR/USD")); // Manual reset @@ -678,10 +748,14 @@ mod tests { let mut manager = CircuitBreakerManager::new(); // Initialize - manager.record_price("SYNOR/USD", dec!(100), Some(dec!(100000))).unwrap(); + manager + .record_price("SYNOR/USD", dec!(100), Some(dec!(100000))) + .unwrap(); // Record 10% spread (exceeds 5% threshold) - manager.record_oracle_spread("SYNOR/USD", dec!(0.10)).unwrap(); + manager + .record_oracle_spread("SYNOR/USD", dec!(0.10)) + .unwrap(); assert_eq!(manager.get_state("SYNOR/USD"), CircuitState::Open); } diff --git a/crates/synor-economics/src/oracle/cross_chain.rs b/crates/synor-economics/src/oracle/cross_chain.rs index a2afd82..850cfd7 100644 --- a/crates/synor-economics/src/oracle/cross_chain.rs +++ b/crates/synor-economics/src/oracle/cross_chain.rs @@ -200,7 +200,10 @@ pub struct CrossChainConfig { impl Default for CrossChainConfig { fn default() -> Self { let mut tracked = HashMap::new(); - tracked.insert(ChainNetwork::Ethereum, vec!["ETH".to_string(), "USDC".to_string(), "USDT".to_string()]); + tracked.insert( + ChainNetwork::Ethereum, + vec!["ETH".to_string(), "USDC".to_string(), "USDT".to_string()], + ); tracked.insert(ChainNetwork::Bitcoin, vec!["BTC".to_string()]); tracked.insert(ChainNetwork::Cosmos, vec!["ATOM".to_string()]); tracked.insert(ChainNetwork::Osmosis, vec!["OSMO".to_string()]); @@ -305,16 +308,21 @@ impl CrossChainOracle { }; if !verified { - return Err(EconomicsError::InvalidPrice("Packet verification failed".into())); + return Err(EconomicsError::InvalidPrice( + "Packet verification failed".into(), + )); } // Cache the price let pair_key = format!("{}/{}", packet.token, packet.quote); - self.cache.insert(pair_key, CrossChainPrice { - packet, - received_at: Utc::now(), - verified, - }); + self.cache.insert( + pair_key, + CrossChainPrice { + packet, + received_at: Utc::now(), + verified, + }, + ); Ok(()) } @@ -326,7 +334,9 @@ impl CrossChainOracle { token: &str, quote: &str, ) -> Result { - let fetcher = self.fetchers.get(&chain) + let fetcher = self + .fetchers + .get(&chain) .ok_or_else(|| EconomicsError::PriceFeedUnavailable(format!("{:?}", chain)))?; let packet = fetcher.fetch_price(token, quote).await?; @@ -334,11 +344,14 @@ impl CrossChainOracle { // Verify and cache if fetcher.verify_packet(&packet) { let pair_key = format!("{}/{}", token, quote); - self.cache.insert(pair_key.clone(), CrossChainPrice { - packet: packet.clone(), - received_at: Utc::now(), - verified: true, - }); + self.cache.insert( + pair_key.clone(), + CrossChainPrice { + packet: packet.clone(), + received_at: Utc::now(), + verified: true, + }, + ); } Ok(packet) @@ -347,7 +360,8 @@ impl CrossChainOracle { /// Get cached price for a token pair pub fn get_price(&self, token: &str, quote: &str) -> Option { let pair_key = format!("{}/{}", token, quote); - self.cache.get(&pair_key) + self.cache + .get(&pair_key) .filter(|c| c.verified) .filter(|c| (Utc::now() - c.received_at).num_seconds() < self.config.max_packet_age) .map(|c| c.packet.price) @@ -356,7 +370,8 @@ impl CrossChainOracle { /// Get price with full packet info pub fn get_price_with_info(&self, token: &str, quote: &str) -> Option<&CrossChainPricePacket> { let pair_key = format!("{}/{}", token, quote); - self.cache.get(&pair_key) + self.cache + .get(&pair_key) .filter(|c| c.verified) .map(|c| &c.packet) } @@ -408,7 +423,8 @@ impl CrossChainOracle { /// Get all cached prices pub fn get_all_prices(&self) -> Vec { - self.cache.values() + self.cache + .values() .filter(|c| c.verified) .map(|c| c.packet.to_token_price()) .collect() @@ -417,9 +433,8 @@ impl CrossChainOracle { /// Clear stale cache entries pub fn cleanup_cache(&mut self) { let max_age = self.config.max_packet_age; - self.cache.retain(|_, v| { - (Utc::now() - v.received_at).num_seconds() < max_age - }); + self.cache + .retain(|_, v| (Utc::now() - v.received_at).num_seconds() < max_age); } /// Send an IBC price request and track pending packet @@ -450,7 +465,11 @@ impl CrossChainOracle { } /// Confirm a pending packet was received - pub fn confirm_pending_packet(&mut self, channel: &str, sequence: u64) -> Option { + pub fn confirm_pending_packet( + &mut self, + channel: &str, + sequence: u64, + ) -> Option { if let Some(idx) = self .pending_packets .iter() @@ -469,9 +488,8 @@ impl CrossChainOracle { /// Cleanup timed out pending packets pub fn cleanup_pending(&mut self, timeout_secs: i64) { - self.pending_packets.retain(|p| { - (Utc::now() - p.sent_at).num_seconds() < timeout_secs - }); + self.pending_packets + .retain(|p| (Utc::now() - p.sent_at).num_seconds() < timeout_secs); } } @@ -502,9 +520,18 @@ pub struct EthereumPriceFetcher { impl EthereumPriceFetcher { pub fn new(rpc_url: impl Into) -> Self { let mut feeds = HashMap::new(); - feeds.insert("ETH/USD".to_string(), "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419".to_string()); - feeds.insert("BTC/USD".to_string(), "0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c".to_string()); - feeds.insert("USDC/USD".to_string(), "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6".to_string()); + feeds.insert( + "ETH/USD".to_string(), + "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419".to_string(), + ); + feeds.insert( + "BTC/USD".to_string(), + "0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c".to_string(), + ); + feeds.insert( + "USDC/USD".to_string(), + "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6".to_string(), + ); Self { rpc_url: rpc_url.into(), @@ -526,7 +553,9 @@ impl ChainPriceFetcher for EthereumPriceFetcher { async fn fetch_price(&self, token: &str, quote: &str) -> Result { let pair = format!("{}/{}", token, quote); - let _feed_addr = self.chainlink_feeds.get(&pair) + let _feed_addr = self + .chainlink_feeds + .get(&pair) .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair.clone()))?; // In production: Call Chainlink aggregator via ethers-rs @@ -539,19 +568,16 @@ impl ChainPriceFetcher for EthereumPriceFetcher { source_block: 19000000, source_timestamp: Utc::now(), proof: None, - signatures: vec![ - OracleSignature { - signer: "chainlink".to_string(), - signature: vec![0; 65], - timestamp: Utc::now(), - }, - ], + signatures: vec![OracleSignature { + signer: "chainlink".to_string(), + signature: vec![0; 65], + timestamp: Utc::now(), + }], }) } fn verify_packet(&self, packet: &CrossChainPricePacket) -> bool { - packet.source_chain == ChainNetwork::Ethereum - && !packet.signatures.is_empty() + packet.source_chain == ChainNetwork::Ethereum && !packet.signatures.is_empty() } fn supported_tokens(&self) -> Vec { @@ -611,8 +637,7 @@ impl ChainPriceFetcher for CosmosPriceFetcher { } fn verify_packet(&self, packet: &CrossChainPricePacket) -> bool { - packet.source_chain == ChainNetwork::Cosmos - && packet.proof.is_some() + packet.source_chain == ChainNetwork::Cosmos && packet.proof.is_some() } fn supported_tokens(&self) -> Vec { @@ -650,7 +675,11 @@ impl CrossChainOracleBuilder { } /// Add Cosmos/IBC price fetcher - pub fn with_cosmos(mut self, light_client_id: impl Into, chain_id: impl Into) -> Self { + pub fn with_cosmos( + mut self, + light_client_id: impl Into, + chain_id: impl Into, + ) -> Self { self.cosmos_light_client = Some((light_client_id.into(), chain_id.into())); self } @@ -693,8 +722,7 @@ impl CrossChainOracleFactory { /// Create a production oracle with real endpoints pub fn production(config: CrossChainProductionConfig) -> CrossChainOracle { - let mut builder = CrossChainOracleBuilder::new() - .with_config(config.cross_chain_config); + let mut builder = CrossChainOracleBuilder::new().with_config(config.cross_chain_config); if let Some(eth_rpc) = config.ethereum_rpc_url { builder = builder.with_ethereum(eth_rpc); @@ -715,7 +743,10 @@ impl CrossChainOracleFactory { } /// Create an oracle with only Cosmos/IBC support - pub fn cosmos_only(light_client_id: impl Into, chain_id: impl Into) -> CrossChainOracle { + pub fn cosmos_only( + light_client_id: impl Into, + chain_id: impl Into, + ) -> CrossChainOracle { CrossChainOracleBuilder::new() .with_cosmos(light_client_id, chain_id) .build() diff --git a/crates/synor-economics/src/oracle/decentralized.rs b/crates/synor-economics/src/oracle/decentralized.rs index 3053b48..e0dea2a 100644 --- a/crates/synor-economics/src/oracle/decentralized.rs +++ b/crates/synor-economics/src/oracle/decentralized.rs @@ -112,7 +112,9 @@ impl AggregationRound { /// Add a submission to this round pub fn add_submission(&mut self, submission: PriceSubmission) -> Result<()> { if self.finalized { - return Err(EconomicsError::InvalidPrice("Round already finalized".into())); + return Err(EconomicsError::InvalidPrice( + "Round already finalized".into(), + )); } if Utc::now() >= self.deadline { return Err(EconomicsError::InvalidPrice("Round deadline passed".into())); @@ -122,7 +124,11 @@ impl AggregationRound { } // Check for duplicate submission from same node - if self.submissions.iter().any(|s| s.node_id == submission.node_id) { + if self + .submissions + .iter() + .any(|s| s.node_id == submission.node_id) + { return Err(EconomicsError::InvalidPrice("Duplicate submission".into())); } @@ -231,7 +237,9 @@ impl DecentralizedOracle { /// Update node heartbeat pub fn heartbeat(&mut self, node_id: &str) -> Result<()> { - let node = self.nodes.get_mut(node_id) + let node = self + .nodes + .get_mut(node_id) .ok_or_else(|| EconomicsError::InvalidPrice(format!("Unknown node: {}", node_id)))?; node.last_heartbeat = Utc::now(); Ok(()) @@ -241,11 +249,8 @@ impl DecentralizedOracle { pub fn start_round(&mut self, pair: impl Into) -> u64 { let pair = pair.into(); self.round_counter += 1; - let round = AggregationRound::new( - self.round_counter, - pair.clone(), - self.config.round_duration - ); + let round = + AggregationRound::new(self.round_counter, pair.clone(), self.config.round_duration); self.current_rounds.insert(pair, round); self.round_counter } @@ -253,7 +258,9 @@ impl DecentralizedOracle { /// Submit a price for the current round pub fn submit_price(&mut self, submission: PriceSubmission) -> Result<()> { // Verify node exists and is eligible - let node = self.nodes.get(&submission.node_id) + let node = self + .nodes + .get(&submission.node_id) .ok_or_else(|| EconomicsError::InvalidPrice("Unknown node".into()))?; if !node.is_eligible(self.config.min_stake, self.config.min_reputation) { @@ -264,7 +271,9 @@ impl DecentralizedOracle { // For now, we trust the submission // Add to current round - let round = self.current_rounds.get_mut(&submission.pair) + let round = self + .current_rounds + .get_mut(&submission.pair) .ok_or_else(|| EconomicsError::InvalidPrice("No active round for pair".into()))?; round.add_submission(submission) @@ -274,13 +283,15 @@ impl DecentralizedOracle { pub fn finalize_round(&mut self, pair: &str) -> Result { // First check state and get submissions (immutable borrow) let (is_finalized, existing_price, submissions) = { - let round = self.current_rounds.get(pair) + let round = self + .current_rounds + .get(pair) .ok_or_else(|| EconomicsError::PriceFeedUnavailable(pair.to_string()))?; if round.finalized { - return round.final_price.ok_or_else(|| - EconomicsError::InvalidPrice("Round has no price".into()) - ); + return round + .final_price + .ok_or_else(|| EconomicsError::InvalidPrice("Round has no price".into())); } // Check minimum submissions @@ -292,13 +303,16 @@ impl DecentralizedOracle { ))); } - (round.finalized, round.final_price, round.submissions.clone()) + ( + round.finalized, + round.final_price, + round.submissions.clone(), + ) }; if is_finalized { - return existing_price.ok_or_else(|| - EconomicsError::InvalidPrice("Round has no price".into()) - ); + return existing_price + .ok_or_else(|| EconomicsError::InvalidPrice("Round has no price".into())); } // Filter outliers and aggregate (using cloned submissions) @@ -326,7 +340,11 @@ impl DecentralizedOracle { } /// Aggregate prices from a vector of submissions (owned) - fn aggregate_prices_from_vec(&self, pair: &str, submissions: &[PriceSubmission]) -> Result { + fn aggregate_prices_from_vec( + &self, + pair: &str, + submissions: &[PriceSubmission], + ) -> Result { if submissions.is_empty() { return Err(EconomicsError::PriceFeedUnavailable(pair.to_string())); } @@ -334,15 +352,21 @@ impl DecentralizedOracle { // Filter outliers first let filtered = self.filter_outliers_vec(submissions); if filtered.is_empty() { - return Err(EconomicsError::InvalidPrice("All submissions were outliers".into())); + return Err(EconomicsError::InvalidPrice( + "All submissions were outliers".into(), + )); } let filtered_refs: Vec<_> = filtered.iter().collect(); match self.strategy { AggregationStrategy::Median => self.calculate_median(&filtered_refs), - AggregationStrategy::StakeWeightedMedian => self.calculate_stake_weighted_median(&filtered_refs), + AggregationStrategy::StakeWeightedMedian => { + self.calculate_stake_weighted_median(&filtered_refs) + } AggregationStrategy::TrimmedMean => self.calculate_trimmed_mean(&filtered_refs), - AggregationStrategy::ReputationWeighted => self.calculate_reputation_weighted(&filtered_refs), + AggregationStrategy::ReputationWeighted => { + self.calculate_reputation_weighted(&filtered_refs) + } } } @@ -376,13 +400,14 @@ impl DecentralizedOracle { } /// Calculate stake-weighted median - fn calculate_stake_weighted_median(&self, submissions: &[&PriceSubmission]) -> Result { + fn calculate_stake_weighted_median( + &self, + submissions: &[&PriceSubmission], + ) -> Result { // Get stake for each submission let mut weighted: Vec<(SynorDecimal, SynorDecimal)> = submissions .iter() - .filter_map(|s| { - self.nodes.get(&s.node_id).map(|n| (s.price, n.stake)) - }) + .filter_map(|s| self.nodes.get(&s.node_id).map(|n| (s.price, n.stake))) .collect(); if weighted.is_empty() { @@ -424,12 +449,17 @@ impl DecentralizedOracle { } /// Calculate reputation-weighted average - fn calculate_reputation_weighted(&self, submissions: &[&PriceSubmission]) -> Result { + fn calculate_reputation_weighted( + &self, + submissions: &[&PriceSubmission], + ) -> Result { let mut weighted_sum = Decimal::ZERO; let mut total_weight = Decimal::ZERO; for sub in submissions { - let reputation = self.nodes.get(&sub.node_id) + let reputation = self + .nodes + .get(&sub.node_id) .map(|n| n.reputation) .unwrap_or(0.5); @@ -448,7 +478,9 @@ impl DecentralizedOracle { /// Update node reputations based on submission accuracy fn update_reputations(&mut self, _pair: &str, final_price: SynorDecimal) { // Get submissions from current round before it was moved - let submissions: Vec<_> = self.history.last() + let submissions: Vec<_> = self + .history + .last() .map(|r| r.submissions.clone()) .unwrap_or_default(); @@ -457,7 +489,8 @@ impl DecentralizedOracle { let deviation = (sub.price - final_price).abs() / final_price; // Increase reputation for accurate submissions, decrease for inaccurate - if deviation <= Decimal::new(1, 2) { // Within 1% + if deviation <= Decimal::new(1, 2) { + // Within 1% node.reputation = (node.reputation + 0.01).min(1.0); } else if deviation > self.config.max_deviation { node.reputation = (node.reputation - 0.05).max(0.0); @@ -473,7 +506,8 @@ impl DecentralizedOracle { /// Get number of active nodes pub fn active_node_count(&self) -> usize { - self.nodes.values() + self.nodes + .values() .filter(|n| n.is_eligible(self.config.min_stake, self.config.min_reputation)) .count() } @@ -492,20 +526,23 @@ impl DecentralizedOracle { /// Convert finalized price to TokenPrice pub fn to_token_price(&self, pair: &str) -> Option { - self.history.iter() + self.history + .iter() .rev() .find(|r| r.pair == pair && r.finalized) - .and_then(|r| r.final_price.map(|price| { - let parts: Vec<_> = pair.split('/').collect(); - TokenPrice { - token: parts.get(0).unwrap_or(&"").to_string(), - quote: parts.get(1).unwrap_or(&"").to_string(), - price, - timestamp: r.deadline, - source: PriceSource::Aggregated, - confidence: 1.0, - } - })) + .and_then(|r| { + r.final_price.map(|price| { + let parts: Vec<_> = pair.split('/').collect(); + TokenPrice { + token: parts.get(0).unwrap_or(&"").to_string(), + quote: parts.get(1).unwrap_or(&"").to_string(), + price, + timestamp: r.deadline, + source: PriceSource::Aggregated, + confidence: 1.0, + } + }) + }) } } @@ -521,13 +558,15 @@ mod tests { use rust_decimal_macros::dec; fn create_test_nodes() -> Vec { - (0..5).map(|i| { - OracleNode::new( - format!("node_{}", i), - vec![i as u8; 32], - dec!(10000), // 10k stake - ) - }).collect() + (0..5) + .map(|i| { + OracleNode::new( + format!("node_{}", i), + vec![i as u8; 32], + dec!(10000), // 10k stake + ) + }) + .collect() } #[test] diff --git a/crates/synor-economics/src/oracle/derivatives.rs b/crates/synor-economics/src/oracle/derivatives.rs index 42fd57e..be5dcb0 100644 --- a/crates/synor-economics/src/oracle/derivatives.rs +++ b/crates/synor-economics/src/oracle/derivatives.rs @@ -6,8 +6,8 @@ use crate::error::{EconomicsError, Result}; use crate::SynorDecimal; use chrono::{DateTime, Duration, Timelike, Utc}; -use rust_decimal::Decimal; use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::f64::consts::PI; @@ -178,7 +178,12 @@ impl BlackScholes { } /// Price a European option - pub fn price(&self, contract: &OptionContract, spot: SynorDecimal, vol: f64) -> Result { + pub fn price( + &self, + contract: &OptionContract, + spot: SynorDecimal, + vol: f64, + ) -> Result { if contract.is_expired() { // At expiration, option is worth intrinsic value let intrinsic = contract.intrinsic_value(spot); @@ -208,12 +213,13 @@ impl BlackScholes { }); } - let s = spot.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid spot price".into()) - )?; - let k = contract.strike.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid strike price".into()) - )?; + let s = spot + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid spot price".into()))?; + let k = contract + .strike + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid strike price".into()))?; let t = contract.time_to_expiry(); if t <= 0.0 || vol <= 0.0 { @@ -286,13 +292,10 @@ impl BlackScholes { let theta_common = -(s * vol * (-q * t).exp() * n_prime_d1) / (2.0 * sqrt_t); let theta = match contract.option_type { OptionType::Call => { - theta_common - + q * s * (-q * t).exp() * n_d1 - - r * k * (-r * t).exp() * n_d2 + theta_common + q * s * (-q * t).exp() * n_d1 - r * k * (-r * t).exp() * n_d2 } OptionType::Put => { - theta_common - - q * s * (-q * t).exp() * (1.0 - n_d1) + theta_common - q * s * (-q * t).exp() * (1.0 - n_d1) + r * k * (-r * t).exp() * (1.0 - n_d2) } } / 365.0; // Per day @@ -327,16 +330,16 @@ impl BlackScholes { spot: SynorDecimal, market_price: SynorDecimal, ) -> Result { - let target = market_price.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid market price".into()) - )?; + let target = market_price + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid market price".into()))?; // Initial guess based on time value let intrinsic = contract.intrinsic_value(spot).to_f64().unwrap_or(0.0); let time_value = (target - intrinsic).max(0.0); - let s = spot.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid spot".into()) - )?; + let s = spot + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid spot".into()))?; let t = contract.time_to_expiry(); // Brenner-Subrahmanyam approximation for initial guess @@ -381,7 +384,11 @@ impl BlackScholes { for _ in 0..100 { let mid = (low + high) / 2.0; - let price = self.price(contract, spot, mid)?.price.to_f64().unwrap_or(0.0); + let price = self + .price(contract, spot, mid)? + .price + .to_f64() + .unwrap_or(0.0); if (price - target).abs() < 0.0001 { return Ok(mid); @@ -493,9 +500,9 @@ impl FuturesModel { /// F = S * e^((r + u - y) * T) /// where r = risk-free rate, u = storage cost, y = convenience yield pub fn price(&self, contract: &FuturesContract, spot: SynorDecimal) -> Result { - let s = spot.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid spot price".into()) - )?; + let s = spot + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid spot price".into()))?; let t = contract.time_to_expiry(); if t < 0.0 { @@ -515,8 +522,7 @@ impl FuturesModel { 0.0 }; - let coc = Decimal::from_f64_retain(cost_of_carry * t * s) - .unwrap_or(Decimal::ZERO); + let coc = Decimal::from_f64_retain(cost_of_carry * t * s).unwrap_or(Decimal::ZERO); Ok(FuturesPricing { fair_value, @@ -529,13 +535,18 @@ impl FuturesModel { /// Calculate implied repo rate from futures price /// R = (F/S - 1) / T - pub fn implied_repo_rate(&self, contract: &FuturesContract, spot: SynorDecimal, futures_price: SynorDecimal) -> Result { - let s = spot.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid spot".into()) - )?; - let f = futures_price.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid futures price".into()) - )?; + pub fn implied_repo_rate( + &self, + contract: &FuturesContract, + spot: SynorDecimal, + futures_price: SynorDecimal, + ) -> Result { + let s = spot + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid spot".into()))?; + let f = futures_price + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid futures price".into()))?; let t = contract.time_to_expiry(); if t <= 0.0 || s <= 0.0 { @@ -595,12 +606,12 @@ impl PerpetualModel { mark_price: SynorDecimal, index_price: SynorDecimal, ) -> Result { - let mark = mark_price.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid mark price".into()) - )?; - let index = index_price.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid index price".into()) - )?; + let mark = mark_price + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid mark price".into()))?; + let index = index_price + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid index price".into()))?; if index <= 0.0 { return Err(EconomicsError::InvalidPrice("Invalid index".into())); @@ -640,7 +651,8 @@ impl PerpetualModel { let hours_since_midnight = now.time().hour(); let next_funding_hour = ((hours_since_midnight / self.funding_interval_hours) + 1) * self.funding_interval_hours; - let next_funding = now.date_naive() + let next_funding = now + .date_naive() .and_hms_opt(next_funding_hour % 24, 0, 0) .map(|dt| DateTime::from_naive_utc_and_offset(dt, Utc)) .unwrap_or(now + Duration::hours(self.funding_interval_hours as i64)); @@ -721,7 +733,8 @@ impl DerivativesOracle { /// Set volatility surface for an underlying pub fn set_vol_surface(&mut self, surface: VolatilitySurface) { - self.vol_surfaces.insert(surface.underlying.clone(), surface); + self.vol_surfaces + .insert(surface.underlying.clone(), surface); } /// Price an option using the volatility surface @@ -730,12 +743,13 @@ impl DerivativesOracle { contract: &OptionContract, spot: SynorDecimal, ) -> Result { - let s = spot.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid spot".into()) - )?; - let k = contract.strike.to_f64().ok_or_else(|| - EconomicsError::InvalidPrice("Invalid strike".into()) - )?; + let s = spot + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid spot".into()))?; + let k = contract + .strike + .to_f64() + .ok_or_else(|| EconomicsError::InvalidPrice("Invalid strike".into()))?; // Get volatility from surface or use default let vol = if let Some(surface) = self.vol_surfaces.get(&contract.underlying) { @@ -765,7 +779,8 @@ impl DerivativesOracle { mark_price: SynorDecimal, open_interest: SynorDecimal, ) -> Result { - self.perpetual_model.price(index_price, mark_price, open_interest) + self.perpetual_model + .price(index_price, mark_price, open_interest) } /// Calculate implied vol from market price @@ -775,7 +790,8 @@ impl DerivativesOracle { spot: SynorDecimal, market_price: SynorDecimal, ) -> Result { - self.options_model.implied_volatility(contract, spot, market_price) + self.options_model + .implied_volatility(contract, spot, market_price) } } @@ -792,8 +808,18 @@ mod tests { #[test] fn test_option_intrinsic_value() { - let call = OptionContract::new("ETH", dec!(2000), Utc::now() + Duration::days(30), OptionType::Call); - let put = OptionContract::new("ETH", dec!(2000), Utc::now() + Duration::days(30), OptionType::Put); + let call = OptionContract::new( + "ETH", + dec!(2000), + Utc::now() + Duration::days(30), + OptionType::Call, + ); + let put = OptionContract::new( + "ETH", + dec!(2000), + Utc::now() + Duration::days(30), + OptionType::Put, + ); // ITM call assert_eq!(call.intrinsic_value(dec!(2100)), dec!(100)); @@ -880,7 +906,9 @@ mod tests { let pricing = model.price(&contract, dec!(2000), vol).unwrap(); // Calculate IV from that price - let iv = model.implied_volatility(&contract, dec!(2000), pricing.price).unwrap(); + let iv = model + .implied_volatility(&contract, dec!(2000), pricing.price) + .unwrap(); // Should match original vol assert!((iv - vol).abs() < 0.01); diff --git a/crates/synor-economics/src/oracle/liquidation.rs b/crates/synor-economics/src/oracle/liquidation.rs index b214bae..5587f69 100644 --- a/crates/synor-economics/src/oracle/liquidation.rs +++ b/crates/synor-economics/src/oracle/liquidation.rs @@ -40,12 +40,12 @@ impl CollateralAsset { pub fn standard(symbol: impl Into) -> Self { Self { symbol: symbol.into(), - collateral_factor: Decimal::new(75, 2), // 75% - liquidation_threshold: Decimal::new(80, 2), // 80% - liquidation_bonus: Decimal::new(5, 2), // 5% + collateral_factor: Decimal::new(75, 2), // 75% + liquidation_threshold: Decimal::new(80, 2), // 80% + liquidation_bonus: Decimal::new(5, 2), // 5% supply_cap: None, borrow_enabled: true, - reserve_factor: Decimal::new(10, 2), // 10% + reserve_factor: Decimal::new(10, 2), // 10% volatility_multiplier: Decimal::ONE, } } @@ -54,12 +54,12 @@ impl CollateralAsset { pub fn stablecoin(symbol: impl Into) -> Self { Self { symbol: symbol.into(), - collateral_factor: Decimal::new(90, 2), // 90% - liquidation_threshold: Decimal::new(95, 2), // 95% - liquidation_bonus: Decimal::new(2, 2), // 2% + collateral_factor: Decimal::new(90, 2), // 90% + liquidation_threshold: Decimal::new(95, 2), // 95% + liquidation_bonus: Decimal::new(2, 2), // 2% supply_cap: None, borrow_enabled: true, - reserve_factor: Decimal::new(5, 2), // 5% + reserve_factor: Decimal::new(5, 2), // 5% volatility_multiplier: Decimal::ONE, } } @@ -68,13 +68,13 @@ impl CollateralAsset { pub fn volatile(symbol: impl Into) -> Self { Self { symbol: symbol.into(), - collateral_factor: Decimal::new(50, 2), // 50% - liquidation_threshold: Decimal::new(65, 2), // 65% - liquidation_bonus: Decimal::new(10, 2), // 10% + collateral_factor: Decimal::new(50, 2), // 50% + liquidation_threshold: Decimal::new(65, 2), // 65% + liquidation_bonus: Decimal::new(10, 2), // 10% supply_cap: None, borrow_enabled: true, - reserve_factor: Decimal::new(20, 2), // 20% - volatility_multiplier: Decimal::new(12, 1), // 1.2x + reserve_factor: Decimal::new(20, 2), // 20% + volatility_multiplier: Decimal::new(12, 1), // 1.2x } } } @@ -131,7 +131,11 @@ impl LendingPosition { /// Withdraw collateral pub fn withdraw(&mut self, asset: impl Into, amount: SynorDecimal) -> Result<()> { let asset = asset.into(); - let current = self.collateral.get(&asset).copied().unwrap_or(Decimal::ZERO); + let current = self + .collateral + .get(&asset) + .copied() + .unwrap_or(Decimal::ZERO); if amount > current { return Err(EconomicsError::InsufficientFunds { required: amount, @@ -233,10 +237,10 @@ pub struct LiquidationOracleConfig { impl Default for LiquidationOracleConfig { fn default() -> Self { Self { - max_price_age: 60, // 1 minute (stricter than general oracle) + max_price_age: 60, // 1 minute (stricter than general oracle) min_confidence: 0.9, min_sources: 2, - liquidation_grace_period: 300, // 5 minutes + liquidation_grace_period: 300, // 5 minutes min_liquidation_amount: Decimal::new(10, 0), // $10 max_liquidation_pct: Decimal::new(50, 2), // 50% at a time partial_liquidation: true, @@ -289,7 +293,8 @@ impl LiquidationOracle { /// Create a new position pub fn create_position(&mut self, account_id: impl Into) -> &mut LendingPosition { let account_id = account_id.into(); - self.positions.entry(account_id.clone()) + self.positions + .entry(account_id.clone()) .or_insert_with(|| LendingPosition::new(account_id)) } @@ -320,7 +325,8 @@ impl LiquidationOracle { freshness_remaining: self.config.max_price_age - age, }; - self.price_cache.insert(asset.to_string(), liq_price.clone()); + self.price_cache + .insert(asset.to_string(), liq_price.clone()); Ok(liq_price) } @@ -328,7 +334,9 @@ impl LiquidationOracle { pub fn calculate_health(&mut self, account_id: &str) -> Result { // Clone position data to avoid borrow conflicts with get_liquidation_price let (collateral, borrows, interest_owed) = { - let position = self.positions.get(account_id) + let position = self + .positions + .get(account_id) .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?; ( position.collateral.clone(), @@ -352,7 +360,9 @@ impl LiquidationOracle { }); } - let asset_config = self.assets.get(asset) + let asset_config = self + .assets + .get(asset) .ok_or_else(|| EconomicsError::InvalidPrice(format!("Unknown asset: {}", asset)))?; let value = *amount * price.price; @@ -426,24 +436,37 @@ impl LiquidationOracle { return Err(EconomicsError::InvalidPrice("Position is healthy".into())); } - let position = self.positions.get(account_id) + let position = self + .positions + .get(account_id) .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?; - let debt_amount = position.borrows.get(debt_asset).copied().unwrap_or(Decimal::ZERO); - let collateral_amount = position.collateral.get(collateral_asset).copied().unwrap_or(Decimal::ZERO); + let debt_amount = position + .borrows + .get(debt_asset) + .copied() + .unwrap_or(Decimal::ZERO); + let collateral_amount = position + .collateral + .get(collateral_asset) + .copied() + .unwrap_or(Decimal::ZERO); if debt_amount == Decimal::ZERO { return Err(EconomicsError::InvalidPrice("No debt to repay".into())); } if collateral_amount == Decimal::ZERO { - return Err(EconomicsError::InvalidPrice("No collateral to seize".into())); + return Err(EconomicsError::InvalidPrice( + "No collateral to seize".into(), + )); } let debt_price = self.get_liquidation_price(debt_asset)?; let collateral_price = self.get_liquidation_price(collateral_asset)?; - let collateral_config = self.assets.get(collateral_asset) - .ok_or_else(|| EconomicsError::InvalidPrice(format!("Unknown asset: {}", collateral_asset)))?; + let collateral_config = self.assets.get(collateral_asset).ok_or_else(|| { + EconomicsError::InvalidPrice(format!("Unknown asset: {}", collateral_asset)) + })?; // Max debt repayable = close_factor * total_debt let max_debt_repay = debt_amount * self.config.close_factor; @@ -458,12 +481,14 @@ impl LiquidationOracle { let actual_collateral_seized = collateral_to_seize.min(collateral_amount); let actual_debt_repaid = if actual_collateral_seized < collateral_to_seize { // Partial liquidation - (actual_collateral_seized * collateral_price.price) / (bonus_multiplier * debt_price.price) + (actual_collateral_seized * collateral_price.price) + / (bonus_multiplier * debt_price.price) } else { max_debt_repay }; - let bonus_amount = actual_collateral_seized * collateral_config.liquidation_bonus / bonus_multiplier; + let bonus_amount = + actual_collateral_seized * collateral_config.liquidation_bonus / bonus_multiplier; Ok(LiquidationCalculation { account_id: account_id.to_string(), @@ -489,7 +514,9 @@ impl LiquidationOracle { let calc = self.calculate_liquidation(account_id, debt_asset, collateral_asset)?; // Update position - let position = self.positions.get_mut(account_id) + let position = self + .positions + .get_mut(account_id) .ok_or_else(|| EconomicsError::AccountNotFound(account_id.to_string()))?; // Reduce debt @@ -549,7 +576,9 @@ impl LiquidationOracle { // Protocol gets a portion of the liquidation bonus if let Some(asset_config) = self.assets.get(&event.collateral_asset) { let protocol_share = event.bonus_amount * asset_config.reserve_factor; - *reserves.entry(event.collateral_asset.clone()).or_insert(Decimal::ZERO) += protocol_share; + *reserves + .entry(event.collateral_asset.clone()) + .or_insert(Decimal::ZERO) += protocol_share; } } @@ -561,15 +590,18 @@ impl LiquidationOracle { let total_positions = self.positions.len(); let total_liquidations = self.liquidation_history.len(); - let total_debt_liquidated: SynorDecimal = self.liquidation_history.iter() - .map(|e| e.debt_amount) - .sum(); + let total_debt_liquidated: SynorDecimal = + self.liquidation_history.iter().map(|e| e.debt_amount).sum(); - let total_collateral_seized: SynorDecimal = self.liquidation_history.iter() + let total_collateral_seized: SynorDecimal = self + .liquidation_history + .iter() .map(|e| e.collateral_amount) .sum(); - let unique_liquidated: std::collections::HashSet<_> = self.liquidation_history.iter() + let unique_liquidated: std::collections::HashSet<_> = self + .liquidation_history + .iter() .map(|e| &e.account_id) .collect(); @@ -617,18 +649,60 @@ mod tests { let mut price_oracle = PriceOracle::with_config(OracleConfig::default()); // Add prices from multiple sources for test validity - price_oracle.update_price(TokenPrice::new("ETH", "USD", dec!(2000), PriceSource::Internal)).unwrap(); - price_oracle.update_price(TokenPrice::new("ETH", "USD", dec!(2000), PriceSource::Aggregated)).unwrap(); - price_oracle.update_price(TokenPrice::new("SYNOR", "USD", dec!(1), PriceSource::Internal)).unwrap(); - price_oracle.update_price(TokenPrice::new("SYNOR", "USD", dec!(1), PriceSource::Aggregated)).unwrap(); - price_oracle.update_price(TokenPrice::new("USDC", "USD", dec!(1), PriceSource::Internal)).unwrap(); - price_oracle.update_price(TokenPrice::new("USDC", "USD", dec!(1), PriceSource::Aggregated)).unwrap(); + price_oracle + .update_price(TokenPrice::new( + "ETH", + "USD", + dec!(2000), + PriceSource::Internal, + )) + .unwrap(); + price_oracle + .update_price(TokenPrice::new( + "ETH", + "USD", + dec!(2000), + PriceSource::Aggregated, + )) + .unwrap(); + price_oracle + .update_price(TokenPrice::new( + "SYNOR", + "USD", + dec!(1), + PriceSource::Internal, + )) + .unwrap(); + price_oracle + .update_price(TokenPrice::new( + "SYNOR", + "USD", + dec!(1), + PriceSource::Aggregated, + )) + .unwrap(); + price_oracle + .update_price(TokenPrice::new( + "USDC", + "USD", + dec!(1), + PriceSource::Internal, + )) + .unwrap(); + price_oracle + .update_price(TokenPrice::new( + "USDC", + "USD", + dec!(1), + PriceSource::Aggregated, + )) + .unwrap(); // Use test config with relaxed freshness requirements let test_config = LiquidationOracleConfig { - max_price_age: 3600, // 1 hour for tests - min_confidence: 0.5, // Lower confidence threshold - min_sources: 1, // Single source OK for tests + max_price_age: 3600, // 1 hour for tests + min_confidence: 0.5, // Lower confidence threshold + min_sources: 1, // Single source OK for tests ..Default::default() }; @@ -660,8 +734,8 @@ mod tests { // Create position with good health let pos = oracle.create_position("user1"); - pos.deposit("ETH", dec!(1)); // $2000 worth - pos.borrow("USDC", dec!(500)); // Borrow $500 + pos.deposit("ETH", dec!(1)); // $2000 worth + pos.borrow("USDC", dec!(500)); // Borrow $500 let health = oracle.calculate_health("user1").unwrap(); @@ -678,8 +752,8 @@ mod tests { // Create position close to liquidation let pos = oracle.create_position("user2"); - pos.deposit("ETH", dec!(1)); // $2000 worth - pos.borrow("USDC", dec!(1500)); // Borrow $1500 + pos.deposit("ETH", dec!(1)); // $2000 worth + pos.borrow("USDC", dec!(1500)); // Borrow $1500 let health = oracle.calculate_health("user2").unwrap(); @@ -699,7 +773,9 @@ mod tests { pos.deposit("ETH", dec!(1)); pos.borrow("USDC", dec!(1500)); - let calc = oracle.calculate_liquidation("user3", "USDC", "ETH").unwrap(); + let calc = oracle + .calculate_liquidation("user3", "USDC", "ETH") + .unwrap(); // Should be able to liquidate assert!(calc.debt_to_repay > Decimal::ZERO); diff --git a/crates/synor-economics/src/oracle/mod.rs b/crates/synor-economics/src/oracle/mod.rs index e0abcd8..c184a00 100644 --- a/crates/synor-economics/src/oracle/mod.rs +++ b/crates/synor-economics/src/oracle/mod.rs @@ -49,8 +49,7 @@ pub use price_feed::{ PriceSource, }; pub use twap::{ - OnChainTwap, OnChainTwapFactory, TwapCalculator, TwapConfig, TwapObservation, - TwapOracleBuilder, + OnChainTwap, OnChainTwapFactory, TwapCalculator, TwapConfig, TwapObservation, TwapOracleBuilder, }; use crate::error::{EconomicsError, Result}; @@ -241,7 +240,10 @@ impl PriceOracle { /// Get price history for a pair pub fn get_price_history(&self, token: &str, quote: &str) -> Vec { let pair_key = format!("{}/{}", token, quote); - self.price_history.get(&pair_key).cloned().unwrap_or_default() + self.price_history + .get(&pair_key) + .cloned() + .unwrap_or_default() } /// Fetch prices from all configured feeds @@ -403,7 +405,9 @@ impl PriceOracle { } let healthy = !pairs_status.is_empty() - && pairs_status.values().all(|s| !s.is_stale && s.price_count > 0); + && pairs_status + .values() + .all(|s| !s.is_stale && s.price_count > 0); OracleHealthStatus { healthy, @@ -443,7 +447,10 @@ impl PriceOracleBuilder { /// Add a mock price feed (for testing) pub fn with_mock_feed(mut self, base_price: SynorDecimal) -> Self { use price_feed::MockPriceFeed; - self.feeds.push(Box::new(MockPriceFeed::new(PriceSource::Internal, base_price))); + self.feeds.push(Box::new(MockPriceFeed::new( + PriceSource::Internal, + base_price, + ))); self } @@ -455,9 +462,14 @@ impl PriceOracleBuilder { } /// Add Chainlink oracle feed - pub fn with_chainlink(mut self, contract_address: impl Into, rpc_url: impl Into) -> Self { + pub fn with_chainlink( + mut self, + contract_address: impl Into, + rpc_url: impl Into, + ) -> Self { use price_feed::ChainlinkFeed; - self.feeds.push(Box::new(ChainlinkFeed::new(contract_address, rpc_url))); + self.feeds + .push(Box::new(ChainlinkFeed::new(contract_address, rpc_url))); self } @@ -508,8 +520,14 @@ impl OracleFactory { let mut oracle = PriceOracle::new(); // Add multiple mock feeds with slight variations for testing - oracle.add_feed(Box::new(MockPriceFeed::new(PriceSource::Internal, base_price))); - oracle.add_feed(Box::new(MockPriceFeed::new(PriceSource::SynorDex, base_price))); + oracle.add_feed(Box::new(MockPriceFeed::new( + PriceSource::Internal, + base_price, + ))); + oracle.add_feed(Box::new(MockPriceFeed::new( + PriceSource::SynorDex, + base_price, + ))); oracle } diff --git a/crates/synor-economics/src/oracle/price_feed.rs b/crates/synor-economics/src/oracle/price_feed.rs index 31d6e26..687c233 100644 --- a/crates/synor-economics/src/oracle/price_feed.rs +++ b/crates/synor-economics/src/oracle/price_feed.rs @@ -110,7 +110,8 @@ impl PriceFeed for MockPriceFeed { async fn fetch_price(&self, token: &str, quote: &str) -> Result { // Add small random variance let variance = (rand_simple() * 2.0 - 1.0) * self.volatility; - let price = self.base_price * (Decimal::ONE + Decimal::from_f64_retain(variance).unwrap_or_default()); + let price = self.base_price + * (Decimal::ONE + Decimal::from_f64_retain(variance).unwrap_or_default()); Ok(TokenPrice { token: token.to_string(), @@ -258,9 +259,12 @@ impl PriceFeed for CoinGeckoFeed { "SYNOR" => "synor", // Would need actual CoinGecko ID "BTC" => "bitcoin", "ETH" => "ethereum", - _ => return Err(EconomicsError::PriceFeedUnavailable( - format!("Token {} not supported on CoinGecko", token) - )), + _ => { + return Err(EconomicsError::PriceFeedUnavailable(format!( + "Token {} not supported on CoinGecko", + token + ))) + } }; let quote_currency = quote.to_lowercase(); @@ -294,8 +298,7 @@ impl PriceFeed for CoinGeckoFeed { Ok(TokenPrice { token: token.to_string(), quote: quote.to_string(), - price: Decimal::from_f64_retain(price) - .unwrap_or_default(), + price: Decimal::from_f64_retain(price).unwrap_or_default(), timestamp: Utc::now(), source: PriceSource::CoinGecko, confidence: 0.90, diff --git a/crates/synor-economics/src/oracle/twap.rs b/crates/synor-economics/src/oracle/twap.rs index d5f9961..29c08c8 100644 --- a/crates/synor-economics/src/oracle/twap.rs +++ b/crates/synor-economics/src/oracle/twap.rs @@ -116,8 +116,8 @@ impl TwapCalculator { let duration = (interval_end - interval_start).num_seconds() as f64; if duration > 0.0 { - let weight = Decimal::from_f64_retain(duration / total_duration) - .unwrap_or(Decimal::ZERO); + let weight = + Decimal::from_f64_retain(duration / total_duration).unwrap_or(Decimal::ZERO); weighted_sum += price.price * weight; total_weight += weight; @@ -343,7 +343,8 @@ impl OnChainTwap { /// Apply the pending cardinality increase (called during next observation) pub fn apply_cardinality_growth(&mut self) { if self.cardinality_next > self.cardinality { - self.observations.reserve(self.cardinality_next - self.cardinality); + self.observations + .reserve(self.cardinality_next - self.cardinality); self.cardinality = self.cardinality_next; } } @@ -396,7 +397,12 @@ impl TwapOracleBuilder { } /// Add an initial observation - pub fn with_observation(mut self, timestamp: DateTime, price_cumulative: SynorDecimal, spl_cumulative: SynorDecimal) -> Self { + pub fn with_observation( + mut self, + timestamp: DateTime, + price_cumulative: SynorDecimal, + spl_cumulative: SynorDecimal, + ) -> Self { self.initial_observations.push(TwapObservation { timestamp, price_cumulative, diff --git a/crates/synor-economics/src/pricing/discounts.rs b/crates/synor-economics/src/pricing/discounts.rs index bdf2b84..3306385 100644 --- a/crates/synor-economics/src/pricing/discounts.rs +++ b/crates/synor-economics/src/pricing/discounts.rs @@ -76,7 +76,11 @@ pub struct Discount { impl Discount { /// Create a new percentage discount - pub fn percentage(code: impl Into, name: impl Into, percentage: SynorDecimal) -> Self { + pub fn percentage( + code: impl Into, + name: impl Into, + percentage: SynorDecimal, + ) -> Self { Self { code: code.into(), name: name.into(), @@ -96,7 +100,11 @@ impl Discount { } /// Create a fixed amount discount - pub fn fixed_amount(code: impl Into, name: impl Into, amount: SynorDecimal) -> Self { + pub fn fixed_amount( + code: impl Into, + name: impl Into, + amount: SynorDecimal, + ) -> Self { Self { code: code.into(), name: name.into(), @@ -120,7 +128,10 @@ impl Discount { Self { code: format!("VOLUME_{}", min_spend), name: format!("Volume Discount ({} SYNOR+)", min_spend), - description: format!("{}% off when spending {} SYNOR or more", percentage, min_spend), + description: format!( + "{}% off when spending {} SYNOR or more", + percentage, min_spend + ), discount_type: DiscountType::Volume, value: percentage, min_spend: Some(min_spend), @@ -260,9 +271,11 @@ impl Discount { } let discount = match self.discount_type { - DiscountType::Percentage | DiscountType::Volume | DiscountType::Loyalty | DiscountType::Referral | DiscountType::Partner => { - amount * (self.value / Decimal::ONE_HUNDRED) - } + DiscountType::Percentage + | DiscountType::Volume + | DiscountType::Loyalty + | DiscountType::Referral + | DiscountType::Partner => amount * (self.value / Decimal::ONE_HUNDRED), DiscountType::FixedAmount | DiscountType::Promotional => { self.value.min(amount) // Can't discount more than amount } @@ -298,7 +311,7 @@ impl Discount { /// Volume discount tiers pub fn standard_volume_discounts() -> Vec { vec![ - Discount::volume(Decimal::new(100, 0), Decimal::new(5, 0)), // 5% at 100+ SYNOR + Discount::volume(Decimal::new(100, 0), Decimal::new(5, 0)), // 5% at 100+ SYNOR Discount::volume(Decimal::new(500, 0), Decimal::new(10, 0)), // 10% at 500+ SYNOR Discount::volume(Decimal::new(1000, 0), Decimal::new(15, 0)), // 15% at 1000+ SYNOR Discount::volume(Decimal::new(5000, 0), Decimal::new(20, 0)), // 20% at 5000+ SYNOR @@ -309,11 +322,7 @@ pub fn standard_volume_discounts() -> Vec { pub fn find_best_volume_discount(amount: SynorDecimal) -> Option { standard_volume_discounts() .into_iter() - .filter(|d| { - d.min_spend - .map(|min| amount >= min) - .unwrap_or(false) - }) + .filter(|d| d.min_spend.map(|min| amount >= min).unwrap_or(false)) .max_by(|a, b| a.value.cmp(&b.value)) } @@ -378,8 +387,8 @@ mod tests { #[test] fn test_discount_usage_limit() { - let mut discount = Discount::percentage("LIMITED", "Limited Use", dec!(10)) - .with_max_uses(2); + let mut discount = + Discount::percentage("LIMITED", "Limited Use", dec!(10)).with_max_uses(2); assert!(discount.use_discount()); assert!(discount.use_discount()); diff --git a/crates/synor-economics/src/pricing/mod.rs b/crates/synor-economics/src/pricing/mod.rs index 4dd62d4..0a98a30 100644 --- a/crates/synor-economics/src/pricing/mod.rs +++ b/crates/synor-economics/src/pricing/mod.rs @@ -198,15 +198,9 @@ impl PricingEngine { .get(&service_type) .ok_or_else(|| EconomicsError::ServiceNotConfigured(service_type.to_string()))?; - let unit_price = pricing - .base_prices - .get(&resource_unit) - .ok_or_else(|| { - EconomicsError::ServiceNotConfigured(format!( - "{} - {}", - service_type, resource_unit - )) - })?; + let unit_price = pricing.base_prices.get(&resource_unit).ok_or_else(|| { + EconomicsError::ServiceNotConfigured(format!("{} - {}", service_type, resource_unit)) + })?; let cost = amount * unit_price; @@ -357,7 +351,8 @@ impl PricingEngine { storage: ServicePricingSummary { gb_month: self.get_base_price(ServiceType::Storage, ResourceUnit::GbMonth), retrieval_gb: self.get_base_price(ServiceType::Storage, ResourceUnit::BandwidthGb), - free_storage_gb: self.get_free_allocation(ServiceType::Storage, ResourceUnit::GbMonth), + free_storage_gb: self + .get_free_allocation(ServiceType::Storage, ResourceUnit::GbMonth), }, hosting: HostingPricingSummary { bandwidth_gb: self.get_base_price(ServiceType::Hosting, ResourceUnit::BandwidthGb), @@ -377,7 +372,8 @@ impl PricingEngine { .get_free_allocation(ServiceType::Database, ResourceUnit::Queries), }, compute: ComputePricingSummary { - cpu_core_hour: self.get_base_price(ServiceType::Compute, ResourceUnit::CpuCoreHours), + cpu_core_hour: self + .get_base_price(ServiceType::Compute, ResourceUnit::CpuCoreHours), gpu_hour: self.get_base_price(ServiceType::Compute, ResourceUnit::GpuHours), memory_gb_hour: self .get_base_price(ServiceType::Compute, ResourceUnit::MemoryGbHours), @@ -472,7 +468,11 @@ mod tests { // 10 million queries let cost = engine - .calculate_cost(ServiceType::Database, ResourceUnit::Queries, dec!(10_000_000)) + .calculate_cost( + ServiceType::Database, + ResourceUnit::Queries, + dec!(10_000_000), + ) .unwrap(); assert_eq!(cost, dec!(0.10)); // 10M * 0.00000001 @@ -484,7 +484,9 @@ mod tests { // Premium tier gets 20% discount let base_cost = dec!(100); - let discount = engine.calculate_tier_discount("premium", base_cost).unwrap(); + let discount = engine + .calculate_tier_discount("premium", base_cost) + .unwrap(); assert_eq!(discount, dec!(20)); // 20% } diff --git a/crates/synor-economics/src/pricing/tiers.rs b/crates/synor-economics/src/pricing/tiers.rs index c18d0be..3258c59 100644 --- a/crates/synor-economics/src/pricing/tiers.rs +++ b/crates/synor-economics/src/pricing/tiers.rs @@ -108,8 +108,8 @@ impl PricingTier { discount_percentage: Decimal::new(30, 0), // 30% discount priority_support: true, sla_percentage: Decimal::new(9999, 2), // 99.99% SLA - custom_domain_limit: 0, // Unlimited - api_rate_limit: 0, // Unlimited + custom_domain_limit: 0, // Unlimited + api_rate_limit: 0, // Unlimited features: vec![ "Everything in Premium".to_string(), "30%+ Usage Discount".to_string(), @@ -147,7 +147,9 @@ impl PricingTier { /// Check if this tier has a feature pub fn has_feature(&self, feature: &str) -> bool { - self.features.iter().any(|f| f.to_lowercase().contains(&feature.to_lowercase())) + self.features + .iter() + .any(|f| f.to_lowercase().contains(&feature.to_lowercase())) } /// Calculate effective monthly cost including usage discount @@ -163,7 +165,9 @@ impl PricingTier { let other_cost = other.effective_cost(monthly_usage); // Upgrade if other tier is cheaper or offers significant benefits - other_cost < current_cost || (other.sla_percentage > self.sla_percentage && other_cost <= current_cost * Decimal::new(12, 1)) + other_cost < current_cost + || (other.sla_percentage > self.sla_percentage + && other_cost <= current_cost * Decimal::new(12, 1)) } } diff --git a/crates/synor-gateway/src/auth.rs b/crates/synor-gateway/src/auth.rs index 7de0a05..0f2d8b5 100644 --- a/crates/synor-gateway/src/auth.rs +++ b/crates/synor-gateway/src/auth.rs @@ -181,7 +181,12 @@ impl AuthService { } /// Generate a JWT token. - pub fn generate_token(&self, user_id: &str, tier: ApiKeyTier, permissions: Permissions) -> Result { + pub fn generate_token( + &self, + user_id: &str, + tier: ApiKeyTier, + permissions: Permissions, + ) -> Result { let now = Utc::now(); let exp = now + self.jwt_expiration; @@ -215,8 +220,7 @@ impl AuthService { })?; let claims = token_data.claims; - let expires_at = DateTime::from_timestamp(claims.exp, 0) - .map(|dt| dt.with_timezone(&Utc)); + let expires_at = DateTime::from_timestamp(claims.exp, 0).map(|dt| dt.with_timezone(&Utc)); Ok(AuthContext { user_id: claims.sub, @@ -278,9 +282,7 @@ impl AuthService { // Try API key header if let Some(api_key) = headers.get("X-API-Key") { - let key = api_key - .to_str() - .map_err(|_| ApiError::InvalidApiKey)?; + let key = api_key.to_str().map_err(|_| ApiError::InvalidApiKey)?; return self.validate_api_key(key).await; } @@ -295,8 +297,7 @@ impl AuthService { let decoded = BASE64 .decode(encoded) .map_err(|_| ApiError::InvalidApiKey)?; - let key = String::from_utf8(decoded) - .map_err(|_| ApiError::InvalidApiKey)?; + let key = String::from_utf8(decoded).map_err(|_| ApiError::InvalidApiKey)?; return self.validate_api_key(&key).await; } } @@ -318,7 +319,9 @@ where fn from_request_parts<'life0, 'life1, 'async_trait>( parts: &'life0 mut Parts, _state: &'life1 S, - ) -> std::pin::Pin> + Send + 'async_trait>> + ) -> std::pin::Pin< + Box> + Send + 'async_trait>, + > where 'life0: 'async_trait, 'life1: 'async_trait, @@ -351,7 +354,9 @@ where fn from_request_parts<'life0, 'life1, 'async_trait>( parts: &'life0 mut Parts, _state: &'life1 S, - ) -> std::pin::Pin> + Send + 'async_trait>> + ) -> std::pin::Pin< + Box> + Send + 'async_trait>, + > where 'life0: 'async_trait, 'life1: 'async_trait, @@ -359,10 +364,7 @@ where { Box::pin(async move { // Get auth service from extensions - let auth_service = parts - .extensions - .get::() - .cloned(); + let auth_service = parts.extensions.get::().cloned(); if let Some(auth_service) = auth_service { match auth_service.authenticate(&parts.headers).await { @@ -377,10 +379,7 @@ where } /// Require specific permissions. -pub fn require_permission( - context: &AuthContext, - permission: &str, -) -> Result<(), ApiError> { +pub fn require_permission(context: &AuthContext, permission: &str) -> Result<(), ApiError> { let has_permission = match permission { "read" => context.can_read(), "write" => context.can_write(), @@ -397,10 +396,7 @@ pub fn require_permission( } /// Require access to a specific service. -pub fn require_service_access( - context: &AuthContext, - service: &str, -) -> Result<(), ApiError> { +pub fn require_service_access(context: &AuthContext, service: &str) -> Result<(), ApiError> { if context.can_access_service(service) { Ok(()) } else { diff --git a/crates/synor-gateway/src/config.rs b/crates/synor-gateway/src/config.rs index 5d49337..a258f17 100644 --- a/crates/synor-gateway/src/config.rs +++ b/crates/synor-gateway/src/config.rs @@ -160,10 +160,26 @@ pub struct RateLimitTiers { impl Default for RateLimitTiers { fn default() -> Self { Self { - free: TierConfig { rpm: 60, burst: 10, concurrent: 5 }, - developer: TierConfig { rpm: 600, burst: 100, concurrent: 20 }, - pro: TierConfig { rpm: 6000, burst: 1000, concurrent: 100 }, - enterprise: TierConfig { rpm: 0, burst: 0, concurrent: 0 }, // Unlimited + free: TierConfig { + rpm: 60, + burst: 10, + concurrent: 5, + }, + developer: TierConfig { + rpm: 600, + burst: 100, + concurrent: 20, + }, + pro: TierConfig { + rpm: 6000, + burst: 1000, + concurrent: 100, + }, + enterprise: TierConfig { + rpm: 0, + burst: 0, + concurrent: 0, + }, // Unlimited } } } diff --git a/crates/synor-gateway/src/error.rs b/crates/synor-gateway/src/error.rs index 91cb8b9..2a87e50 100644 --- a/crates/synor-gateway/src/error.rs +++ b/crates/synor-gateway/src/error.rs @@ -170,9 +170,7 @@ impl ApiError { | Self::ContractError(_) => StatusCode::UNPROCESSABLE_ENTITY, // 429 Too Many Requests - Self::RateLimitExceeded | Self::TooManyRequests { .. } => { - StatusCode::TOO_MANY_REQUESTS - } + Self::RateLimitExceeded | Self::TooManyRequests { .. } => StatusCode::TOO_MANY_REQUESTS, // 500 Internal Server Error Self::InternalError | Self::Custom(_) => StatusCode::INTERNAL_SERVER_ERROR, @@ -222,7 +220,10 @@ impl ApiError { /// Build error details with optional extra information. pub fn to_details(&self) -> ErrorDetails { let details = match self { - Self::InsufficientBalance { required, available } => Some(serde_json::json!({ + Self::InsufficientBalance { + required, + available, + } => Some(serde_json::json!({ "required": required, "available": available })), @@ -257,10 +258,9 @@ impl IntoResponse for ApiError { // Add rate limit headers for 429 errors if let Self::TooManyRequests { retry_after } = &self { - response.headers_mut().insert( - "Retry-After", - retry_after.to_string().parse().unwrap(), - ); + response + .headers_mut() + .insert("Retry-After", retry_after.to_string().parse().unwrap()); } response diff --git a/crates/synor-gateway/src/middleware.rs b/crates/synor-gateway/src/middleware.rs index 04b42d9..dc30477 100644 --- a/crates/synor-gateway/src/middleware.rs +++ b/crates/synor-gateway/src/middleware.rs @@ -144,7 +144,8 @@ pub async fn timing_middleware(request: Request, next: Next) -> Response { // Update metrics metrics::counter!("http_requests_total", "method" => method.to_string(), "status" => status.as_u16().to_string()).increment(1); - metrics::histogram!("http_request_duration_seconds", "method" => method.to_string()).record(duration.as_secs_f64()); + metrics::histogram!("http_request_duration_seconds", "method" => method.to_string()) + .record(duration.as_secs_f64()); response } @@ -169,7 +170,10 @@ impl RateLimiterState { } /// Get or create a rate limiter for an IP. - pub async fn get_ip_limiter(&self, ip: &str) -> Arc> { + pub async fn get_ip_limiter( + &self, + ip: &str, + ) -> Arc> { { let limiters = self.ip_limiters.read().await; if let Some(limiter) = limiters.get(ip) { @@ -189,7 +193,11 @@ impl RateLimiterState { } /// Get or create a rate limiter for an API key. - pub async fn get_key_limiter(&self, key_id: &str, tier: ApiKeyTier) -> Arc> { + pub async fn get_key_limiter( + &self, + key_id: &str, + tier: ApiKeyTier, + ) -> Arc> { { let limiters = self.key_limiters.read().await; if let Some(limiter) = limiters.get(key_id) { @@ -255,9 +263,7 @@ pub async fn rate_limit_middleware( // Use a fixed retry time since we can't easily convert to quanta's instant let retry_after = 60; // Default to 60 seconds - Err(ApiError::TooManyRequests { - retry_after, - }) + Err(ApiError::TooManyRequests { retry_after }) } } } @@ -274,10 +280,7 @@ pub async fn auth_middleware( } /// API version middleware - validates version prefix. -pub async fn version_middleware( - request: Request, - next: Next, -) -> Result { +pub async fn version_middleware(request: Request, next: Next) -> Result { let path = request.uri().path(); // Skip version check for health, metrics, and docs @@ -307,22 +310,13 @@ pub async fn security_headers_middleware(request: Request, next: Next) -> Respon let headers = response.headers_mut(); // Prevent XSS - headers.insert( - "X-Content-Type-Options", - "nosniff".parse().unwrap(), - ); + headers.insert("X-Content-Type-Options", "nosniff".parse().unwrap()); // Prevent clickjacking - headers.insert( - "X-Frame-Options", - "DENY".parse().unwrap(), - ); + headers.insert("X-Frame-Options", "DENY".parse().unwrap()); // Enable XSS filter - headers.insert( - "X-XSS-Protection", - "1; mode=block".parse().unwrap(), - ); + headers.insert("X-XSS-Protection", "1; mode=block".parse().unwrap()); // Strict transport security (HTTPS) headers.insert( diff --git a/crates/synor-gateway/src/routes/compiler.rs b/crates/synor-gateway/src/routes/compiler.rs index 19ed54b..871f787 100644 --- a/crates/synor-gateway/src/routes/compiler.rs +++ b/crates/synor-gateway/src/routes/compiler.rs @@ -6,11 +6,7 @@ //! - Contract analysis and validation //! - Security scanning -use axum::{ - extract::State, - routing::post, - Json, Router, -}; +use axum::{extract::State, routing::post, Json, Router}; use serde::{Deserialize, Serialize}; use crate::{ @@ -44,7 +40,7 @@ pub fn router() -> Router { // Types #[derive(Debug, Deserialize)] pub struct CompileRequest { - pub wasm: String, // base64 encoded WASM + pub wasm: String, // base64 encoded WASM pub optimization_level: Option, // none, basic, size, aggressive pub strip_debug: Option, pub strip_names: Option, @@ -216,16 +212,14 @@ async fn compile_contract( abi: Some(ContractAbi { name: "MyContract".to_string(), version: "1.0.0".to_string(), - functions: vec![ - AbiFunction { - name: "init".to_string(), - selector: "0x12345678".to_string(), - inputs: vec![], - outputs: vec![], - view: false, - payable: false, - }, - ], + functions: vec![AbiFunction { + name: "init".to_string(), + selector: "0x12345678".to_string(), + inputs: vec![], + outputs: vec![], + view: false, + payable: false, + }], events: vec![], errors: vec![], }), @@ -342,23 +336,19 @@ async fn analyze_contract( imports: 100, total: 5000, }, - functions: vec![ - FunctionAnalysis { - name: "init".to_string(), - size: 500, - instruction_count: 50, - local_count: 3, - exported: true, - estimated_gas: 10000, - }, - ], - imports: vec![ - ImportInfo { - module: "env".to_string(), - name: "memory".to_string(), - kind: "memory".to_string(), - }, - ], + functions: vec![FunctionAnalysis { + name: "init".to_string(), + size: 500, + instruction_count: 50, + local_count: 3, + exported: true, + estimated_gas: 10000, + }], + imports: vec![ImportInfo { + module: "env".to_string(), + name: "memory".to_string(), + kind: "memory".to_string(), + }], gas_analysis: GasAnalysis { deployment_gas: 100000, memory_init_gas: 5000, @@ -378,14 +368,12 @@ async fn security_scan( let result = SecurityScanResult { score: 85, - issues: vec![ - SecurityIssue { - severity: "low".to_string(), - issue_type: "unbounded_loop".to_string(), - description: "Potential unbounded loop detected".to_string(), - location: Some("function:process".to_string()), - }, - ], + issues: vec![SecurityIssue { + severity: "low".to_string(), + issue_type: "unbounded_loop".to_string(), + description: "Potential unbounded loop detected".to_string(), + location: Some("function:process".to_string()), + }], recommendations: vec![ "Add loop iteration limits".to_string(), "Consider using checked arithmetic".to_string(), diff --git a/crates/synor-gateway/src/routes/dex.rs b/crates/synor-gateway/src/routes/dex.rs index bf1b558..0dc78ff 100644 --- a/crates/synor-gateway/src/routes/dex.rs +++ b/crates/synor-gateway/src/routes/dex.rs @@ -122,17 +122,15 @@ async fn list_markets( ) -> ApiResult>>> { require_permission(&auth, "read")?; - let markets = vec![ - Market { - symbol: "ETH-USDC".to_string(), - base_asset: "ETH".to_string(), - quote_asset: "USDC".to_string(), - last_price: "3000.00".to_string(), - change_24h: "2.5".to_string(), - volume_24h: "10000000".to_string(), - status: "active".to_string(), - }, - ]; + let markets = vec![Market { + symbol: "ETH-USDC".to_string(), + base_asset: "ETH".to_string(), + quote_asset: "USDC".to_string(), + last_price: "3000.00".to_string(), + change_24h: "2.5".to_string(), + volume_24h: "10000000".to_string(), + status: "active".to_string(), + }]; Ok(Json(ApiResponse::success(markets))) } @@ -165,8 +163,14 @@ async fn get_orderbook( require_permission(&auth, "read")?; let orderbook = Orderbook { - bids: vec![OrderbookEntry { price: "2999.00".to_string(), quantity: "1.5".to_string() }], - asks: vec![OrderbookEntry { price: "3001.00".to_string(), quantity: "2.0".to_string() }], + bids: vec![OrderbookEntry { + price: "2999.00".to_string(), + quantity: "1.5".to_string(), + }], + asks: vec![OrderbookEntry { + price: "3001.00".to_string(), + quantity: "2.0".to_string(), + }], spread: "2.00".to_string(), }; @@ -286,7 +290,9 @@ async fn place_perp_order( Json(req): Json, ) -> ApiResult>> { require_permission(&auth, "write")?; - Ok(Json(ApiResponse::success(serde_json::json!({"order_id": "perp_123"})))) + Ok(Json(ApiResponse::success( + serde_json::json!({"order_id": "perp_123"}), + ))) } async fn list_pools( @@ -295,18 +301,16 @@ async fn list_pools( ) -> ApiResult>>> { require_permission(&auth, "read")?; - let pools = vec![ - Pool { - pool_id: "ETH-USDC".to_string(), - name: "ETH/USDC".to_string(), - token_a: "ETH".to_string(), - token_b: "USDC".to_string(), - reserve_a: "1000".to_string(), - reserve_b: "3000000".to_string(), - tvl: "6000000".to_string(), - apr: "15.5".to_string(), - }, - ]; + let pools = vec![Pool { + pool_id: "ETH-USDC".to_string(), + name: "ETH/USDC".to_string(), + token_a: "ETH".to_string(), + token_b: "USDC".to_string(), + reserve_a: "1000".to_string(), + reserve_b: "3000000".to_string(), + tvl: "6000000".to_string(), + apr: "15.5".to_string(), + }]; Ok(Json(ApiResponse::success(pools))) } diff --git a/crates/synor-gateway/src/routes/ibc.rs b/crates/synor-gateway/src/routes/ibc.rs index c949bba..4c7acce 100644 --- a/crates/synor-gateway/src/routes/ibc.rs +++ b/crates/synor-gateway/src/routes/ibc.rs @@ -128,16 +128,14 @@ async fn list_chains( ) -> ApiResult>>> { require_permission(&auth, "read")?; - let chains = vec![ - Chain { - chain_id: "cosmoshub-4".to_string(), - name: "Cosmos Hub".to_string(), - status: "active".to_string(), - rpc_endpoint: "https://rpc.cosmos.network".to_string(), - latest_height: 18000000, - active_channels: 50, - }, - ]; + let chains = vec![Chain { + chain_id: "cosmoshub-4".to_string(), + name: "Cosmos Hub".to_string(), + status: "active".to_string(), + rpc_endpoint: "https://rpc.cosmos.network".to_string(), + latest_height: 18000000, + active_channels: 50, + }]; Ok(Json(ApiResponse::success(chains))) } @@ -280,15 +278,13 @@ async fn get_routes( ) -> ApiResult>>> { require_permission(&auth, "read")?; - let routes = vec![ - TransferRoute { - source_chain: "cosmoshub-4".to_string(), - dest_chain: "synor-mainnet".to_string(), - channel_id: "channel-0".to_string(), - estimated_time: "30s".to_string(), - fee: "0.001 ATOM".to_string(), - }, - ]; + let routes = vec![TransferRoute { + source_chain: "cosmoshub-4".to_string(), + dest_chain: "synor-mainnet".to_string(), + channel_id: "channel-0".to_string(), + estimated_time: "30s".to_string(), + fee: "0.001 ATOM".to_string(), + }]; Ok(Json(ApiResponse::success(routes))) } diff --git a/crates/synor-gateway/src/routes/rpc.rs b/crates/synor-gateway/src/routes/rpc.rs index d823553..c8ddc4b 100644 --- a/crates/synor-gateway/src/routes/rpc.rs +++ b/crates/synor-gateway/src/routes/rpc.rs @@ -303,13 +303,11 @@ async fn get_peers( ) -> ApiResult>>> { require_permission(&auth, "read")?; - let peers = vec![ - serde_json::json!({ - "id": "peer1", - "address": "192.168.1.1:16100", - "connected_since": 1705312200 - }) - ]; + let peers = vec![serde_json::json!({ + "id": "peer1", + "address": "192.168.1.1:16100", + "connected_since": 1705312200 + })]; Ok(Json(ApiResponse::success(peers))) } diff --git a/crates/synor-gateway/src/routes/storage.rs b/crates/synor-gateway/src/routes/storage.rs index 77c1496..6ed854f 100644 --- a/crates/synor-gateway/src/routes/storage.rs +++ b/crates/synor-gateway/src/routes/storage.rs @@ -247,14 +247,12 @@ async fn list_directory( ) -> ApiResult>>> { require_permission(&auth, "read")?; - let entries = vec![ - DirectoryEntry { - name: "file1.txt".to_string(), - cid: "bafyfile1...".to_string(), - size: 1024, - is_directory: false, - }, - ]; + let entries = vec![DirectoryEntry { + name: "file1.txt".to_string(), + cid: "bafyfile1...".to_string(), + size: 1024, + is_directory: false, + }]; Ok(Json(ApiResponse::success(entries))) } diff --git a/crates/synor-gateway/src/routes/wallet.rs b/crates/synor-gateway/src/routes/wallet.rs index f33ae5a..25f04b0 100644 --- a/crates/synor-gateway/src/routes/wallet.rs +++ b/crates/synor-gateway/src/routes/wallet.rs @@ -409,7 +409,10 @@ async fn list_addresses( ]; let pagination_meta = pagination.to_meta(addresses.len() as u64); - Ok(Json(ApiResponse::success_paginated(addresses, pagination_meta))) + Ok(Json(ApiResponse::success_paginated( + addresses, + pagination_meta, + ))) } /// Generate a stealth address. @@ -477,7 +480,9 @@ async fn get_balances( require_permission(&auth, "read")?; if req.addresses.is_empty() { - return Err(ApiError::ValidationError("addresses cannot be empty".to_string())); + return Err(ApiError::ValidationError( + "addresses cannot be empty".to_string(), + )); } if req.addresses.len() > 100 { @@ -585,12 +590,15 @@ async fn send_transaction( } // Validate amount - let amount: f64 = req.amount.parse().map_err(|_| { - ApiError::ValidationError("Invalid amount format".to_string()) - })?; + let amount: f64 = req + .amount + .parse() + .map_err(|_| ApiError::ValidationError("Invalid amount format".to_string()))?; if amount <= 0.0 { - return Err(ApiError::ValidationError("Amount must be positive".to_string())); + return Err(ApiError::ValidationError( + "Amount must be positive".to_string(), + )); } // In production, build, sign, and broadcast the transaction diff --git a/crates/synor-gateway/src/routes/zk.rs b/crates/synor-gateway/src/routes/zk.rs index e83099a..b8d702a 100644 --- a/crates/synor-gateway/src/routes/zk.rs +++ b/crates/synor-gateway/src/routes/zk.rs @@ -139,16 +139,14 @@ async fn list_circuits( ) -> ApiResult>>> { require_permission(&auth, "read")?; - let circuits = vec![ - Circuit { - circuit_id: "multiplier-v1".to_string(), - name: "Multiplier".to_string(), - constraints: 1, - public_inputs: 1, - private_inputs: 2, - outputs: 1, - }, - ]; + let circuits = vec![Circuit { + circuit_id: "multiplier-v1".to_string(), + name: "Multiplier".to_string(), + constraints: 1, + public_inputs: 1, + private_inputs: 2, + outputs: 1, + }]; let meta = pagination.to_meta(circuits.len() as u64); Ok(Json(ApiResponse::success_paginated(circuits, meta))) diff --git a/crates/synor-gateway/src/server.rs b/crates/synor-gateway/src/server.rs index 1d959e3..9be44eb 100644 --- a/crates/synor-gateway/src/server.rs +++ b/crates/synor-gateway/src/server.rs @@ -17,15 +17,9 @@ use axum::{ Router, }; use std::{net::SocketAddr, sync::Arc}; -use tokio::{ - net::TcpListener, - signal, - sync::oneshot, -}; +use tokio::{net::TcpListener, signal, sync::oneshot}; use tower_http::{ - compression::CompressionLayer, - limit::RequestBodyLimitLayer, - timeout::TimeoutLayer, + compression::CompressionLayer, limit::RequestBodyLimitLayer, timeout::TimeoutLayer, trace::TraceLayer, }; use tracing::info; diff --git a/crates/synor-gateway/src/versioning.rs b/crates/synor-gateway/src/versioning.rs index ac2fae8..26ad260 100644 --- a/crates/synor-gateway/src/versioning.rs +++ b/crates/synor-gateway/src/versioning.rs @@ -164,7 +164,8 @@ impl VersionRegistry { /// Register a version. pub fn register(&mut self, info: VersionInfo) { - self.versions.insert((info.version.major, info.version.minor), info); + self.versions + .insert((info.version.major, info.version.minor), info); } /// Get version info. @@ -330,10 +331,7 @@ pub async fn version_middleware(req: Request, next: Next) -> Response { // Add deprecation headers if needed if let Some(info) = registry.get(&extracted.version) { if info.is_deprecated { - headers.insert( - X_API_DEPRECATED.clone(), - HeaderValue::from_static("true"), - ); + headers.insert(X_API_DEPRECATED.clone(), HeaderValue::from_static("true")); if let Some(deprecated_at) = &info.deprecated_at { if let Ok(v) = HeaderValue::from_str(&deprecated_at.to_rfc3339()) { @@ -427,8 +425,8 @@ impl VersionsResponse { // Routes // ============================================================================ -use axum::{routing::get, Json, Router}; use crate::routes::AppState; +use axum::{routing::get, Json, Router}; /// Build version routes. pub fn router() -> Router { diff --git a/crates/synor-gateway/src/websocket.rs b/crates/synor-gateway/src/websocket.rs index 3367d32..8c4d405 100644 --- a/crates/synor-gateway/src/websocket.rs +++ b/crates/synor-gateway/src/websocket.rs @@ -593,11 +593,7 @@ async fn ws_blocks_handler( ws.on_upgrade(move |socket| handle_blocks_socket(socket, state, auth)) } -async fn handle_blocks_socket( - socket: WebSocket, - state: AppState, - _auth: Option, -) { +async fn handle_blocks_socket(socket: WebSocket, state: AppState, _auth: Option) { let ws_state = &state.websocket; ws_state.broadcaster.add_connection().await; @@ -834,11 +830,7 @@ async fn ws_markets_handler( ws.on_upgrade(move |socket| handle_markets_socket(socket, state, auth)) } -async fn handle_markets_socket( - socket: WebSocket, - state: AppState, - _auth: Option, -) { +async fn handle_markets_socket(socket: WebSocket, state: AppState, _auth: Option) { let ws_state = &state.websocket; ws_state.broadcaster.add_connection().await; diff --git a/crates/synor-hosting/src/bin/hosting-gateway.rs b/crates/synor-hosting/src/bin/hosting-gateway.rs index f8c3d92..20e8543 100644 --- a/crates/synor-hosting/src/bin/hosting-gateway.rs +++ b/crates/synor-hosting/src/bin/hosting-gateway.rs @@ -7,8 +7,8 @@ //! hosting-gateway --domain synor.cc --storage-url http://localhost:8180 //! hosting-gateway --config /path/to/config.toml -use synor_hosting::{HostingGateway, GatewayConfig}; use std::net::SocketAddr; +use synor_hosting::{GatewayConfig, HostingGateway}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -19,28 +19,32 @@ async fn main() -> Result<(), Box> { // Parse command line arguments let args: Vec = std::env::args().collect(); - let listen_addr: SocketAddr = args.iter() + let listen_addr: SocketAddr = args + .iter() .position(|a| a == "--listen" || a == "-l") .and_then(|i| args.get(i + 1)) .and_then(|s| s.parse().ok()) .or_else(|| std::env::var("LISTEN_ADDR").ok()?.parse().ok()) .unwrap_or_else(|| "0.0.0.0:8080".parse().unwrap()); - let hosting_domain = args.iter() + let hosting_domain = args + .iter() .position(|a| a == "--domain" || a == "-d") .and_then(|i| args.get(i + 1)) .cloned() .or_else(|| std::env::var("HOSTING_DOMAIN").ok()) .unwrap_or_else(|| "synor.cc".to_string()); - let storage_url = args.iter() + let storage_url = args + .iter() .position(|a| a == "--storage-url" || a == "-s") .and_then(|i| args.get(i + 1)) .cloned() .or_else(|| std::env::var("STORAGE_GATEWAY_URL").ok()) .unwrap_or_else(|| "http://localhost:8180".to_string()); - let rate_limit: u32 = args.iter() + let rate_limit: u32 = args + .iter() .position(|a| a == "--rate-limit") .and_then(|i| args.get(i + 1)) .and_then(|s| s.parse().ok()) diff --git a/crates/synor-hosting/src/compute.rs b/crates/synor-hosting/src/compute.rs index 4761704..62f7403 100644 --- a/crates/synor-hosting/src/compute.rs +++ b/crates/synor-hosting/src/compute.rs @@ -233,11 +233,7 @@ impl EdgeCompute { } /// Run AI inference at the edge. - pub async fn inference( - &self, - _model: &str, - _input: &[u8], - ) -> Result, EdgeError> { + pub async fn inference(&self, _model: &str, _input: &[u8]) -> Result, EdgeError> { if !self.enabled { return Err(EdgeError::NotEnabled); } diff --git a/crates/synor-hosting/src/config.rs b/crates/synor-hosting/src/config.rs index 7fe33b9..a0295df 100644 --- a/crates/synor-hosting/src/config.rs +++ b/crates/synor-hosting/src/config.rs @@ -3,7 +3,7 @@ //! Parses and manages the synor.json configuration file that users //! include in their deployed projects. -use crate::router::{RouteConfig, Redirect}; +use crate::router::{Redirect, RouteConfig}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -172,13 +172,17 @@ impl SynorJson { if let Some(routes) = &self.routes { if routes.spa { // SPA mode: all non-file paths go to index.html - config.routes.insert("/*".to_string(), "/index.html".to_string()); + config + .routes + .insert("/*".to_string(), "/index.html".to_string()); } // Process rewrites if let Some(rewrites) = &routes.rewrites { for rewrite in rewrites { - config.routes.insert(rewrite.source.clone(), rewrite.destination.clone()); + config + .routes + .insert(rewrite.source.clone(), rewrite.destination.clone()); } } } @@ -275,11 +279,17 @@ mod tests { let config = SynorJson::from_json(json).unwrap(); assert_eq!(config.name, Some("my-blog".to_string())); - assert_eq!(config.build.as_ref().unwrap().command, Some("npm run build".to_string())); + assert_eq!( + config.build.as_ref().unwrap().command, + Some("npm run build".to_string()) + ); assert!(config.routes.as_ref().unwrap().spa); assert_eq!(config.headers.as_ref().unwrap().len(), 1); assert_eq!(config.redirects.as_ref().unwrap().len(), 1); - assert_eq!(config.error_pages.as_ref().unwrap().get(&404), Some(&"/404.html".to_string())); + assert_eq!( + config.error_pages.as_ref().unwrap().get(&404), + Some(&"/404.html".to_string()) + ); } #[test] @@ -301,7 +311,10 @@ mod tests { let route_config = synor_json.to_route_config(); // SPA mode should add /* -> /index.html - assert_eq!(route_config.routes.get("/*"), Some(&"/index.html".to_string())); + assert_eq!( + route_config.routes.get("/*"), + Some(&"/index.html".to_string()) + ); // Redirect should be converted assert_eq!(route_config.redirects.len(), 1); diff --git a/crates/synor-hosting/src/domain.rs b/crates/synor-hosting/src/domain.rs index 4701187..e02a313 100644 --- a/crates/synor-hosting/src/domain.rs +++ b/crates/synor-hosting/src/domain.rs @@ -113,40 +113,40 @@ impl DomainVerifier { /// Get verification instructions for a domain pub fn get_instructions(&self, domain: &str) -> Result { - let record = self.pending.get(domain) + let record = self + .pending + .get(domain) .ok_or_else(|| Error::DomainNotFound(domain.to_string()))?; match record.method { - VerificationMethod::Cname => { - Ok(VerificationInstructions { - method: VerificationMethod::Cname, - record_type: "CNAME".to_string(), - record_name: record.domain.clone(), - record_value: format!("{}.{}", record.name, self.hosting_domain), - instructions: format!( - "Add a CNAME record:\n {} CNAME {}.{}", - record.domain, record.name, self.hosting_domain - ), - }) - } - VerificationMethod::Txt => { - Ok(VerificationInstructions { - method: VerificationMethod::Txt, - record_type: "TXT".to_string(), - record_name: format!("_synor.{}", record.domain), - record_value: format!("{}{}", self.token_prefix, record.token), - instructions: format!( - "Add a TXT record:\n _synor.{} TXT \"{}{}\"", - record.domain, self.token_prefix, record.token - ), - }) - } + VerificationMethod::Cname => Ok(VerificationInstructions { + method: VerificationMethod::Cname, + record_type: "CNAME".to_string(), + record_name: record.domain.clone(), + record_value: format!("{}.{}", record.name, self.hosting_domain), + instructions: format!( + "Add a CNAME record:\n {} CNAME {}.{}", + record.domain, record.name, self.hosting_domain + ), + }), + VerificationMethod::Txt => Ok(VerificationInstructions { + method: VerificationMethod::Txt, + record_type: "TXT".to_string(), + record_name: format!("_synor.{}", record.domain), + record_value: format!("{}{}", self.token_prefix, record.token), + instructions: format!( + "Add a TXT record:\n _synor.{} TXT \"{}{}\"", + record.domain, self.token_prefix, record.token + ), + }), } } /// Verify a domain (check DNS records) pub fn verify(&mut self, domain: &str, current_block: u64) -> Result { - let record = self.pending.get(domain) + let record = self + .pending + .get(domain) .ok_or_else(|| Error::DomainNotFound(domain.to_string()))? .clone(); @@ -161,7 +161,8 @@ impl DomainVerifier { verified_record.expires_at = current_block + self.verification_expiry; self.pending.remove(domain); - self.verified.insert(domain.to_string(), verified_record.clone()); + self.verified + .insert(domain.to_string(), verified_record.clone()); Ok(verified_record) } else { @@ -212,13 +213,17 @@ impl DomainVerifier { } if label.starts_with('-') || label.ends_with('-') { - return Err(Error::InvalidDomain("Label cannot start or end with hyphen".into())); + return Err(Error::InvalidDomain( + "Label cannot start or end with hyphen".into(), + )); } } // Must have at least one dot (not just a TLD) if !domain.contains('.') { - return Err(Error::InvalidDomain("Domain must have at least one dot".into())); + return Err(Error::InvalidDomain( + "Domain must have at least one dot".into(), + )); } Ok(()) @@ -241,8 +246,8 @@ impl DomainVerifier { /// Check DNS records (real implementation with trust-dns) #[cfg(feature = "dns")] fn check_dns(&self, record: &DomainRecord) -> Result { - use trust_dns_resolver::Resolver; use trust_dns_resolver::config::*; + use trust_dns_resolver::Resolver; let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()) .map_err(|e| Error::Dns(e.to_string()))?; @@ -250,7 +255,8 @@ impl DomainVerifier { match record.method { VerificationMethod::Cname => { let expected = format!("{}.{}.", record.name, self.hosting_domain); - let response = resolver.lookup(&record.domain, RecordType::CNAME) + let response = resolver + .lookup(&record.domain, RecordType::CNAME) .map_err(|e| Error::Dns(e.to_string()))?; for cname in response.iter() { @@ -264,7 +270,8 @@ impl DomainVerifier { let txt_domain = format!("_synor.{}", record.domain); let expected = format!("{}{}", self.token_prefix, record.token); - let response = resolver.txt_lookup(&txt_domain) + let response = resolver + .txt_lookup(&txt_domain) .map_err(|e| Error::Dns(e.to_string()))?; for txt in response.iter() { @@ -359,12 +366,22 @@ mod tests { // No TLD assert!(verifier - .request_verification("myapp".to_string(), "myapp".to_string(), VerificationMethod::Cname, 0) + .request_verification( + "myapp".to_string(), + "myapp".to_string(), + VerificationMethod::Cname, + 0 + ) .is_err()); // Empty assert!(verifier - .request_verification("".to_string(), "myapp".to_string(), VerificationMethod::Cname, 0) + .request_verification( + "".to_string(), + "myapp".to_string(), + VerificationMethod::Cname, + 0 + ) .is_err()); } } diff --git a/crates/synor-hosting/src/lib.rs b/crates/synor-hosting/src/lib.rs index 3e24e0a..a852e37 100644 --- a/crates/synor-hosting/src/lib.rs +++ b/crates/synor-hosting/src/lib.rs @@ -33,35 +33,67 @@ pub mod router; #[cfg(feature = "server")] pub mod server; -pub use registry::{NameRegistry, NameRecord, RegistrationRequest}; -pub use domain::{DomainVerifier, DomainRecord, VerificationMethod}; -pub use router::{HostingRouter, RouteConfig}; pub use config::SynorJson; +pub use domain::{DomainRecord, DomainVerifier, VerificationMethod}; pub use error::{Error, Result}; +pub use registry::{NameRecord, NameRegistry, RegistrationRequest}; +pub use router::{HostingRouter, RouteConfig}; #[cfg(feature = "server")] -pub use server::{HostingGateway, GatewayConfig}; +pub use server::{GatewayConfig, HostingGateway}; /// Reserved names that cannot be registered pub const RESERVED_NAMES: &[&str] = &[ - "synor", "admin", "api", "gateway", "www", "mail", "ftp", "ssh", - "cdn", "static", "assets", "app", "web", "blog", "docs", "help", - "support", "status", "system", "root", "test", "dev", "staging", - "prod", "production", "null", "undefined", "localhost", "local", + "synor", + "admin", + "api", + "gateway", + "www", + "mail", + "ftp", + "ssh", + "cdn", + "static", + "assets", + "app", + "web", + "blog", + "docs", + "help", + "support", + "status", + "system", + "root", + "test", + "dev", + "staging", + "prod", + "production", + "null", + "undefined", + "localhost", + "local", ]; /// Validate a name according to registry rules pub fn validate_name(name: &str) -> Result<()> { // Length check if name.len() < 3 { - return Err(Error::InvalidName("Name must be at least 3 characters".into())); + return Err(Error::InvalidName( + "Name must be at least 3 characters".into(), + )); } if name.len() > 63 { - return Err(Error::InvalidName("Name must be at most 63 characters".into())); + return Err(Error::InvalidName( + "Name must be at most 63 characters".into(), + )); } // Character check - if !name.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') { + if !name + .chars() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') + { return Err(Error::InvalidName( "Name must contain only lowercase letters, numbers, and hyphens".into(), )); @@ -69,12 +101,16 @@ pub fn validate_name(name: &str) -> Result<()> { // Cannot start or end with hyphen if name.starts_with('-') || name.ends_with('-') { - return Err(Error::InvalidName("Name cannot start or end with hyphen".into())); + return Err(Error::InvalidName( + "Name cannot start or end with hyphen".into(), + )); } // Cannot have consecutive hyphens if name.contains("--") { - return Err(Error::InvalidName("Name cannot contain consecutive hyphens".into())); + return Err(Error::InvalidName( + "Name cannot contain consecutive hyphens".into(), + )); } // Reserved name check diff --git a/crates/synor-hosting/src/registry.rs b/crates/synor-hosting/src/registry.rs index 6265499..b877155 100644 --- a/crates/synor-hosting/src/registry.rs +++ b/crates/synor-hosting/src/registry.rs @@ -5,9 +5,9 @@ use crate::error::{Error, Result}; use crate::validate_name; -use synor_storage::ContentId; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use synor_storage::ContentId; /// Address type (32 bytes) pub type Address = [u8; 32]; @@ -67,7 +67,7 @@ impl NameRegistry { domain_to_name: HashMap::new(), registration_fee: 10_000_000_000, // 10 SYNOR renewal_fee: 5_000_000_000, // 5 SYNOR - grace_period: 432_000, // ~30 days at 6s blocks + grace_period: 432_000, // ~30 days at 6s blocks } } @@ -95,9 +95,7 @@ impl NameRegistry { // Check if name is available if let Some(existing) = self.records.get(&request.name) { // Check if expired and past grace period - if existing.expires_at > 0 - && current_block > existing.expires_at + self.grace_period - { + if existing.expires_at > 0 && current_block > existing.expires_at + self.grace_period { // Name can be re-registered } else { return Err(Error::NameTaken(request.name)); @@ -207,7 +205,10 @@ impl NameRegistry { /// Get full record for a name pub fn get_record(&self, name: &str, current_block: u64) -> Result<&NameRecord> { - let record = self.records.get(name).ok_or_else(|| Error::NameNotFound(name.to_string()))?; + let record = self + .records + .get(name) + .ok_or_else(|| Error::NameNotFound(name.to_string()))?; if !self.is_valid(record, current_block) { return Err(Error::NameExpired(name.to_string())); @@ -218,7 +219,10 @@ impl NameRegistry { /// Get mutable record fn get_record_mut(&mut self, name: &str, current_block: u64) -> Result<&mut NameRecord> { - let record = self.records.get(name).ok_or_else(|| Error::NameNotFound(name.to_string()))?; + let record = self + .records + .get(name) + .ok_or_else(|| Error::NameNotFound(name.to_string()))?; // Check validity before returning mutable reference if !self.is_valid(record, current_block) { @@ -241,8 +245,7 @@ impl NameRegistry { match self.records.get(name) { None => validate_name(name).is_ok(), Some(record) => { - record.expires_at > 0 - && current_block > record.expires_at + self.grace_period + record.expires_at > 0 && current_block > record.expires_at + self.grace_period } } } @@ -431,7 +434,9 @@ mod tests { }; registry.register(request, owner, 0).unwrap(); - registry.add_custom_domain("myapp", "myapp.com".to_string(), owner, 0).unwrap(); + registry + .add_custom_domain("myapp", "myapp.com".to_string(), owner, 0) + .unwrap(); assert_eq!(registry.resolve_domain("myapp.com"), Some("myapp")); diff --git a/crates/synor-hosting/src/router.rs b/crates/synor-hosting/src/router.rs index 2b7bacb..5206af6 100644 --- a/crates/synor-hosting/src/router.rs +++ b/crates/synor-hosting/src/router.rs @@ -2,12 +2,12 @@ //! //! Handles subdomain-based routing, SPA support, and custom domains. -use crate::registry::NameRegistry; use crate::domain::DomainVerifier; use crate::error::{Error, Result}; -use synor_storage::ContentId; +use crate::registry::NameRegistry; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use synor_storage::ContentId; /// Route configuration for a hosted site #[derive(Debug, Clone, Serialize, Deserialize)] @@ -124,7 +124,9 @@ impl HostingRouter { let name = self.parse_host(host)?; // Resolve name to CID - let cid = self.registry.resolve(&name, self.current_block) + let cid = self + .registry + .resolve(&name, self.current_block) .ok_or_else(|| Error::NameNotFound(name.clone()))?; // Get route config (or default) @@ -288,7 +290,9 @@ mod tests { router.registry_mut().register(request, owner, 0).unwrap(); // Deep path should fallback to index.html - let resolved = router.route("myapp.synor.network", "/dashboard/settings").unwrap(); + let resolved = router + .route("myapp.synor.network", "/dashboard/settings") + .unwrap(); assert_eq!(resolved.path, "/index.html"); assert!(resolved.is_fallback); } diff --git a/crates/synor-hosting/src/server/handler.rs b/crates/synor-hosting/src/server/handler.rs index 2d7c1b1..d4fd995 100644 --- a/crates/synor-hosting/src/server/handler.rs +++ b/crates/synor-hosting/src/server/handler.rs @@ -99,10 +99,12 @@ pub async fn handle_request( // Add cache headers if resolved.is_fallback { // SPA fallback - don't cache - builder = builder.header(header::CACHE_CONTROL, "no-cache, no-store, must-revalidate"); + builder = builder + .header(header::CACHE_CONTROL, "no-cache, no-store, must-revalidate"); } else if is_immutable_asset(&resolved.path) { // Hashed assets - cache forever - builder = builder.header(header::CACHE_CONTROL, "public, max-age=31536000, immutable"); + builder = builder + .header(header::CACHE_CONTROL, "public, max-age=31536000, immutable"); } else { // Regular assets - cache with revalidation builder = builder.header( @@ -125,15 +127,9 @@ pub async fn handle_request( } } } - Err(Error::Redirect { to, status }) => { - redirect_response(&to, status) - } - Err(Error::NameNotFound(_)) | Err(Error::UnknownHost(_)) => { - not_found_response(&host) - } - Err(Error::DomainNotVerified(domain)) => { - domain_not_verified_response(&domain) - } + Err(Error::Redirect { to, status }) => redirect_response(&to, status), + Err(Error::NameNotFound(_)) | Err(Error::UnknownHost(_)) => not_found_response(&host), + Err(Error::DomainNotVerified(domain)) => domain_not_verified_response(&domain), Err(e) => { eprintln!("Routing error: {}", e); error_response(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error") @@ -194,8 +190,7 @@ fn is_immutable_asset(path: &str) -> bool { if parts.len() >= 3 { let potential_hash = parts[parts.len() - 2]; // Check if it looks like a hash (8+ hex or alphanumeric chars) - potential_hash.len() >= 8 - && potential_hash.chars().all(|c| c.is_ascii_alphanumeric()) + potential_hash.len() >= 8 && potential_hash.chars().all(|c| c.is_ascii_alphanumeric()) } else { false } @@ -307,11 +302,20 @@ mod tests { #[test] fn test_guess_content_type() { - assert_eq!(guess_content_type("/index.html"), "text/html; charset=utf-8"); + assert_eq!( + guess_content_type("/index.html"), + "text/html; charset=utf-8" + ); assert_eq!(guess_content_type("/style.css"), "text/css; charset=utf-8"); - assert_eq!(guess_content_type("/app.js"), "application/javascript; charset=utf-8"); + assert_eq!( + guess_content_type("/app.js"), + "application/javascript; charset=utf-8" + ); assert_eq!(guess_content_type("/logo.png"), "image/png"); - assert_eq!(guess_content_type("/unknown.xyz"), "application/octet-stream"); + assert_eq!( + guess_content_type("/unknown.xyz"), + "application/octet-stream" + ); } #[test] diff --git a/crates/synor-hosting/src/server/middleware.rs b/crates/synor-hosting/src/server/middleware.rs index f7867d6..a18c686 100644 --- a/crates/synor-hosting/src/server/middleware.rs +++ b/crates/synor-hosting/src/server/middleware.rs @@ -122,10 +122,10 @@ pub struct CacheControl { impl Default for CacheControl { fn default() -> Self { Self { - default_ttl: 3600, // 1 hour - immutable_ttl: 31536000, // 1 year - html_ttl: 300, // 5 minutes - api_ttl: 60, // 1 minute + default_ttl: 3600, // 1 hour + immutable_ttl: 31536000, // 1 year + html_ttl: 300, // 5 minutes + api_ttl: 60, // 1 minute } } } @@ -151,8 +151,7 @@ impl CacheControl { let parts: Vec<&str> = path.split('.').collect(); if parts.len() >= 3 { let potential_hash = parts[parts.len() - 2]; - potential_hash.len() >= 8 - && potential_hash.chars().all(|c| c.is_ascii_alphanumeric()) + potential_hash.len() >= 8 && potential_hash.chars().all(|c| c.is_ascii_alphanumeric()) } else { false } diff --git a/crates/synor-hosting/src/server/mod.rs b/crates/synor-hosting/src/server/mod.rs index 9fc46c7..58b4394 100644 --- a/crates/synor-hosting/src/server/mod.rs +++ b/crates/synor-hosting/src/server/mod.rs @@ -8,11 +8,11 @@ mod middleware; mod ssl; pub use handler::RequestHandler; -pub use middleware::{RateLimiter, CacheControl}; +pub use middleware::{CacheControl, RateLimiter}; pub use ssl::SslConfig; -use crate::router::HostingRouter; use crate::error::{Error, Result}; +use crate::router::HostingRouter; use axum::extract::State; use std::net::SocketAddr; use std::sync::Arc; @@ -111,10 +111,7 @@ impl HostingGateway { /// Start the gateway server pub async fn start(&self) -> Result<()> { - use axum::{ - Router, - routing::get, - }; + use axum::{routing::get, Router}; let state = Arc::clone(&self.state); let addr = state.config.listen_addr; @@ -149,9 +146,7 @@ async fn health_check() -> &'static str { } /// Gateway info endpoint -async fn gateway_info( - State(state): State>, -) -> axum::Json { +async fn gateway_info(State(state): State>) -> axum::Json { axum::Json(serde_json::json!({ "gateway": "synor-hosting", "version": env!("CARGO_PKG_VERSION"), diff --git a/crates/synor-ibc/src/channel.rs b/crates/synor-ibc/src/channel.rs index 3024008..27393fc 100644 --- a/crates/synor-ibc/src/channel.rs +++ b/crates/synor-ibc/src/channel.rs @@ -25,7 +25,9 @@ use std::collections::HashMap; use std::fmt; /// Port identifier -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub struct PortId(pub String); impl PortId { @@ -52,7 +54,9 @@ impl PortId { /// Validate port ID pub fn validate(&self) -> IbcResult<()> { if self.0.is_empty() { - return Err(IbcError::InvalidIdentifier("port ID cannot be empty".to_string())); + return Err(IbcError::InvalidIdentifier( + "port ID cannot be empty".to_string(), + )); } if self.0.len() > 128 { @@ -60,7 +64,11 @@ impl PortId { } // Alphanumeric and limited special characters - if !self.0.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.') { + if !self + .0 + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.') + { return Err(IbcError::InvalidIdentifier( "port ID contains invalid characters".to_string(), )); @@ -77,7 +85,9 @@ impl fmt::Display for PortId { } /// Channel identifier -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub struct ChannelId(pub String); impl ChannelId { @@ -169,7 +179,10 @@ pub struct ChannelCounterparty { impl ChannelCounterparty { /// Create a new counterparty pub fn new(port_id: PortId, channel_id: Option) -> Self { - Self { port_id, channel_id } + Self { + port_id, + channel_id, + } } } @@ -330,7 +343,10 @@ impl ChannelManager { /// Generate next channel ID for a port fn next_channel_id(&mut self, port_id: &PortId) -> ChannelId { - let seq = self.next_channel_sequence.entry(port_id.clone()).or_insert(0); + let seq = self + .next_channel_sequence + .entry(port_id.clone()) + .or_insert(0); let id = ChannelId::new(*seq); *seq += 1; id @@ -369,11 +385,7 @@ impl ChannelManager { } /// Increment send sequence and return previous value - pub fn increment_send_sequence( - &mut self, - port_id: &PortId, - channel_id: &ChannelId, - ) -> u64 { + pub fn increment_send_sequence(&mut self, port_id: &PortId, channel_id: &ChannelId) -> u64 { let key = (port_id.clone(), channel_id.clone()); let seq = self.sequences.entry(key).or_default(); let current = seq.next_send; @@ -424,11 +436,14 @@ impl ChannelManager { // Store channel let key = (port_id.clone(), channel_id.clone()); self.channels.insert(key.clone(), channel); - self.sequences.insert(key, ChannelSequences { - next_send: 1, - next_recv: 1, - next_ack: 1, - }); + self.sequences.insert( + key, + ChannelSequences { + next_send: 1, + next_recv: 1, + next_ack: 1, + }, + ); tracing::info!( port = %port_id, @@ -476,11 +491,14 @@ impl ChannelManager { // Store channel let key = (port_id.clone(), channel_id.clone()); self.channels.insert(key.clone(), channel); - self.sequences.insert(key, ChannelSequences { - next_send: 1, - next_recv: 1, - next_ack: 1, - }); + self.sequences.insert( + key, + ChannelSequences { + next_send: 1, + next_recv: 1, + next_ack: 1, + }, + ); tracing::info!( port = %port_id, @@ -664,11 +682,15 @@ mod tests { let mut manager = ChannelManager::new(); let port = PortId::transfer(); - assert!(manager.bind_port(port.clone(), "transfer".to_string()).is_ok()); + assert!(manager + .bind_port(port.clone(), "transfer".to_string()) + .is_ok()); assert!(manager.is_port_bound(&port)); // Can't bind same port twice - assert!(manager.bind_port(port.clone(), "other".to_string()).is_err()); + assert!(manager + .bind_port(port.clone(), "other".to_string()) + .is_err()); // Release and rebind assert!(manager.release_port(&port).is_ok()); @@ -684,8 +706,12 @@ mod tests { let conn = ConnectionId::new(0); // Bind ports - manager_a.bind_port(port.clone(), "transfer".to_string()).unwrap(); - manager_b.bind_port(port.clone(), "transfer".to_string()).unwrap(); + manager_a + .bind_port(port.clone(), "transfer".to_string()) + .unwrap(); + manager_b + .bind_port(port.clone(), "transfer".to_string()) + .unwrap(); // Chain A: Init let chan_a = manager_a @@ -741,7 +767,9 @@ mod tests { let port = PortId::transfer(); let conn = ConnectionId::new(0); - manager.bind_port(port.clone(), "transfer".to_string()).unwrap(); + manager + .bind_port(port.clone(), "transfer".to_string()) + .unwrap(); let channel = manager .chan_open_init( diff --git a/crates/synor-ibc/src/client.rs b/crates/synor-ibc/src/client.rs index e34fef2..fbcf980 100644 --- a/crates/synor-ibc/src/client.rs +++ b/crates/synor-ibc/src/client.rs @@ -127,7 +127,7 @@ impl ClientState { trust_level: TrustLevel::default(), trusting_period: 14 * 24 * 60 * 60 * 1_000_000_000, // 14 days unbonding_period: 21 * 24 * 60 * 60 * 1_000_000_000, // 21 days - max_clock_drift: 10 * 1_000_000_000, // 10 seconds + max_clock_drift: 10 * 1_000_000_000, // 10 seconds latest_height, frozen_height: None, allow_update_after_expiry: true, @@ -142,7 +142,9 @@ impl ClientState { /// Check if client is expired pub fn is_expired(&self, current_time: Timestamp, consensus_time: Timestamp) -> bool { - let elapsed = current_time.nanoseconds().saturating_sub(consensus_time.nanoseconds()); + let elapsed = current_time + .nanoseconds() + .saturating_sub(consensus_time.nanoseconds()); elapsed > self.trusting_period } @@ -486,7 +488,9 @@ impl LightClient { .nanoseconds() .saturating_sub(current_time.nanoseconds()); if drift > client_state.max_clock_drift { - return Err(IbcError::InvalidClientState("clock drift too large".to_string())); + return Err(IbcError::InvalidClientState( + "clock drift too large".to_string(), + )); } // Verify signatures (simplified - real implementation would verify BFT signatures) @@ -574,10 +578,7 @@ mod tests { #[test] fn test_client_state_validation() { - let mut state = ClientState::new_synor( - ChainId::synor_testnet(), - Height::from_height(100), - ); + let mut state = ClientState::new_synor(ChainId::synor_testnet(), Height::from_height(100)); assert!(state.validate().is_ok()); diff --git a/crates/synor-ibc/src/commitment.rs b/crates/synor-ibc/src/commitment.rs index 745c564..f0643ec 100644 --- a/crates/synor-ibc/src/commitment.rs +++ b/crates/synor-ibc/src/commitment.rs @@ -3,10 +3,10 @@ //! IBC uses Merkle proofs to verify state on remote chains. //! This module handles commitment paths and proof verification. -use crate::error::{IbcError, IbcResult}; use crate::channel::{ChannelId, PortId}; use crate::client::ClientId; use crate::connection::ConnectionId; +use crate::error::{IbcError, IbcResult}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -206,10 +206,7 @@ pub fn consensus_state_path(client_id: &ClientId, height: &crate::types::Height) /// Connection commitment path pub fn connection_path(conn_id: &ConnectionId) -> MerklePath { - MerklePath::new(vec![ - "connections".to_string(), - conn_id.to_string(), - ]) + MerklePath::new(vec!["connections".to_string(), conn_id.to_string()]) } /// Channel commitment path @@ -241,11 +238,7 @@ pub fn packet_commitment_path( } /// Packet receipt path (for unordered channels) -pub fn packet_receipt_path( - port_id: &PortId, - channel_id: &ChannelId, - sequence: u64, -) -> MerklePath { +pub fn packet_receipt_path(port_id: &PortId, channel_id: &ChannelId, sequence: u64) -> MerklePath { MerklePath::new(vec![ "receipts".to_string(), "ports".to_string(), @@ -314,10 +307,7 @@ mod tests { #[test] fn test_merkle_path() { let path = client_state_path(&ClientId::new("07-tendermint", 0)); - assert_eq!( - path.path(), - "clients/07-tendermint-0/clientState" - ); + assert_eq!(path.path(), "clients/07-tendermint-0/clientState"); } #[test] diff --git a/crates/synor-ibc/src/connection.rs b/crates/synor-ibc/src/connection.rs index 29554f8..cd47b65 100644 --- a/crates/synor-ibc/src/connection.rs +++ b/crates/synor-ibc/src/connection.rs @@ -280,7 +280,8 @@ impl ConnectionManager { }; // Create connection end - let connection = ConnectionEnd::new_init(client_id.clone(), counterparty, versions, delay_period); + let connection = + ConnectionEnd::new_init(client_id.clone(), counterparty, versions, delay_period); connection.validate()?; // Store connection @@ -314,12 +315,9 @@ impl ConnectionManager { counterparty.validate()?; // Verify we have the counterparty's connection ID (clone to avoid borrow issues) - let counterparty_conn_id = counterparty - .connection_id - .clone() - .ok_or_else(|| { - IbcError::InvalidCounterparty("counterparty connection ID required".to_string()) - })?; + let counterparty_conn_id = counterparty.connection_id.clone().ok_or_else(|| { + IbcError::InvalidCounterparty("counterparty connection ID required".to_string()) + })?; // Verify proof of init (simplified - real implementation verifies Merkle proof) if proof_init.is_empty() { @@ -381,7 +379,11 @@ impl ConnectionManager { } // Verify version is compatible - if !connection.versions.iter().any(|v| v.identifier == version.identifier) { + if !connection + .versions + .iter() + .any(|v| v.identifier == version.identifier) + { return Err(IbcError::ConnectionVersionMismatch(format!( "version {} not supported", version.identifier @@ -588,11 +590,7 @@ mod tests { .unwrap(); // Try to confirm without going through TryOpen - let result = manager.conn_open_confirm( - &conn_id, - vec![1, 2, 3], - Height::from_height(100), - ); + let result = manager.conn_open_confirm(&conn_id, vec![1, 2, 3], Height::from_height(100)); assert!(result.is_err()); } diff --git a/crates/synor-ibc/src/handler.rs b/crates/synor-ibc/src/handler.rs index ba17b8b..2c95fdd 100644 --- a/crates/synor-ibc/src/handler.rs +++ b/crates/synor-ibc/src/handler.rs @@ -192,7 +192,13 @@ impl IbcHandler { proof_height: Height, ) -> IbcResult<()> { let mut connections = self.connections.write(); - connections.conn_open_ack(conn_id, counterparty_conn_id, version, proof_try, proof_height) + connections.conn_open_ack( + conn_id, + counterparty_conn_id, + version, + proof_try, + proof_height, + ) } /// Confirm a connection (ConnOpenConfirm) @@ -362,12 +368,14 @@ impl IbcHandler { let counterparty = &channel.counterparty; let dest_port = counterparty.port_id.clone(); - let dest_channel = counterparty.channel_id.clone().ok_or_else(|| { - IbcError::InvalidChannelState { - expected: "counterparty channel set".to_string(), - actual: "none".to_string(), - } - })?; + let dest_channel = + counterparty + .channel_id + .clone() + .ok_or_else(|| IbcError::InvalidChannelState { + expected: "counterparty channel set".to_string(), + actual: "none".to_string(), + })?; // Get sequence number let mut channels = self.channels.write(); @@ -431,7 +439,9 @@ impl IbcHandler { // Verify proof (simplified) if proof_commitment.is_empty() { - return Err(IbcError::MissingProof("proof_commitment required".to_string())); + return Err(IbcError::MissingProof( + "proof_commitment required".to_string(), + )); } // Handle based on channel ordering @@ -445,7 +455,9 @@ impl IbcHandler { } ChannelOrder::Ordered => { // Verify sequence - let expected = channels.get_sequences(&packet.dest_port, &packet.dest_channel).next_recv; + let expected = channels + .get_sequences(&packet.dest_port, &packet.dest_channel) + .next_recv; packets.verify_ordered_sequence(&packet, expected)?; channels.increment_recv_sequence(&packet.dest_port, &packet.dest_channel); } @@ -504,7 +516,9 @@ impl IbcHandler { // For ordered channels, verify sequence if channel.ordering == ChannelOrder::Ordered { let mut channels = self.channels.write(); - let expected = channels.get_sequences(&packet.source_port, &packet.source_channel).next_ack; + let expected = channels + .get_sequences(&packet.source_port, &packet.source_channel) + .next_ack; if packet.sequence != expected { return Err(IbcError::InvalidPacketSequence { expected, @@ -515,7 +529,11 @@ impl IbcHandler { } // Delete commitment - packets.delete_packet_commitment(&packet.source_port, &packet.source_channel, packet.sequence); + packets.delete_packet_commitment( + &packet.source_port, + &packet.source_channel, + packet.sequence, + ); tracing::info!( port = %packet.source_port, @@ -545,7 +563,9 @@ impl IbcHandler { // Verify proof (simplified) if proof_unreceived.is_empty() { - return Err(IbcError::MissingProof("proof_unreceived required".to_string())); + return Err(IbcError::MissingProof( + "proof_unreceived required".to_string(), + )); } let mut packets = self.packets.write(); @@ -612,18 +632,16 @@ mod tests { let handler = IbcHandler::new(IbcConfig::default()); // Create a client - let client_state = ClientState::new_synor( - ChainId::synor_testnet(), - Height::from_height(100), - ); - let consensus_state = ConsensusState::new( - Timestamp::now(), - vec![0u8; 32], - vec![0u8; 32], - ); + let client_state = + ClientState::new_synor(ChainId::synor_testnet(), Height::from_height(100)); + let consensus_state = ConsensusState::new(Timestamp::now(), vec![0u8; 32], vec![0u8; 32]); - handler.create_client(client_state, consensus_state).unwrap(); - handler.bind_port(PortId::transfer(), "transfer".to_string()).unwrap(); + handler + .create_client(client_state, consensus_state) + .unwrap(); + handler + .bind_port(PortId::transfer(), "transfer".to_string()) + .unwrap(); handler } @@ -639,17 +657,13 @@ mod tests { fn test_client_creation() { let handler = IbcHandler::default(); - let client_state = ClientState::new_synor( - ChainId::synor_testnet(), - Height::from_height(100), - ); - let consensus_state = ConsensusState::new( - Timestamp::now(), - vec![0u8; 32], - vec![0u8; 32], - ); + let client_state = + ClientState::new_synor(ChainId::synor_testnet(), Height::from_height(100)); + let consensus_state = ConsensusState::new(Timestamp::now(), vec![0u8; 32], vec![0u8; 32]); - let client_id = handler.create_client(client_state, consensus_state).unwrap(); + let client_id = handler + .create_client(client_state, consensus_state) + .unwrap(); assert!(handler.get_client_state(&client_id).is_ok()); } @@ -660,11 +674,9 @@ mod tests { let client_id = ClientId::new("synor", 0); let counterparty_client = ClientId::new("07-tendermint", 0); - let conn_id = handler.connection_open_init( - client_id, - counterparty_client, - None, - ).unwrap(); + let conn_id = handler + .connection_open_init(client_id, counterparty_client, None) + .unwrap(); let conn = handler.get_connection(&conn_id).unwrap(); assert!(!conn.is_open()); // Still in Init state diff --git a/crates/synor-ibc/src/packet.rs b/crates/synor-ibc/src/packet.rs index a9fae6e..3a6840d 100644 --- a/crates/synor-ibc/src/packet.rs +++ b/crates/synor-ibc/src/packet.rs @@ -187,14 +187,12 @@ impl Acknowledgement { /// Decode from bytes pub fn decode(bytes: &[u8]) -> IbcResult { - serde_json::from_slice(bytes) - .map_err(|e| IbcError::InvalidAcknowledgement(e.to_string())) + serde_json::from_slice(bytes).map_err(|e| IbcError::InvalidAcknowledgement(e.to_string())) } } /// Timeout information -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -#[derive(Default)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] pub struct Timeout { /// Timeout height pub height: Height, @@ -231,7 +229,6 @@ impl Timeout { } } - /// Packet receipt (for unordered channels) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PacketReceipt { @@ -310,12 +307,7 @@ impl PacketHandler { } /// Check if packet was received - pub fn has_packet_receipt( - &self, - port: &PortId, - channel: &ChannelId, - sequence: u64, - ) -> bool { + pub fn has_packet_receipt(&self, port: &PortId, channel: &ChannelId, sequence: u64) -> bool { self.receipts .contains_key(&(port.clone(), channel.clone(), sequence)) } @@ -442,8 +434,7 @@ impl FungibleTokenPacketData { /// Decode from bytes pub fn decode(bytes: &[u8]) -> IbcResult { - serde_json::from_slice(bytes) - .map_err(|e| IbcError::DeserializationError(e.to_string())) + serde_json::from_slice(bytes).map_err(|e| IbcError::DeserializationError(e.to_string())) } /// Get the denomination trace path @@ -554,6 +545,9 @@ mod tests { // IBC token let ibc = FungibleTokenPacketData::new("transfer/channel-0/uatom", "1000", "a", "b"); assert!(!ibc.is_native()); - assert_eq!(ibc.get_denom_trace(), vec!["transfer", "channel-0", "uatom"]); + assert_eq!( + ibc.get_denom_trace(), + vec!["transfer", "channel-0", "uatom"] + ); } } diff --git a/crates/synor-ibc/src/swap.rs b/crates/synor-ibc/src/swap.rs index b39c959..a1e5063 100644 --- a/crates/synor-ibc/src/swap.rs +++ b/crates/synor-ibc/src/swap.rs @@ -280,8 +280,12 @@ impl Htlc { ) -> Self { let initiator = initiator.into(); let responder = responder.into(); - let swap_id = - SwapId::generate(&initiator, &responder, hashlock.as_bytes(), created_at.nanoseconds()); + let swap_id = SwapId::generate( + &initiator, + &responder, + hashlock.as_bytes(), + created_at.nanoseconds(), + ); Self { swap_id, @@ -426,8 +430,7 @@ impl AtomicSwap { rand::thread_rng().fill_bytes(&mut secret); let hashlock = Hashlock::from_secret(&secret); - let initiator_timelock = - Timelock::from_duration(current_time, DEFAULT_INITIATOR_TIMELOCK); + let initiator_timelock = Timelock::from_duration(current_time, DEFAULT_INITIATOR_TIMELOCK); let initiator_htlc = Htlc::new( initiator, @@ -475,8 +478,7 @@ impl AtomicSwap { responder_asset.validate()?; // Responder's timelock must be shorter than initiator's - let responder_timelock = - Timelock::from_duration(current_time, DEFAULT_RESPONDER_TIMELOCK); + let responder_timelock = Timelock::from_duration(current_time, DEFAULT_RESPONDER_TIMELOCK); // Verify responder's timelock expires before initiator's if responder_timelock.expiry >= self.initiator_htlc.timelock.expiry { @@ -1003,11 +1005,17 @@ mod tests { // Respond manager - .respond_to_swap(&swap_id, SwapAsset::ics20("uatom", 500), later_timestamp(100)) + .respond_to_swap( + &swap_id, + SwapAsset::ics20("uatom", 500), + later_timestamp(100), + ) .unwrap(); // Initiator claims - let secret = manager.initiator_claim(&swap_id, later_timestamp(200)).unwrap(); + let secret = manager + .initiator_claim(&swap_id, later_timestamp(200)) + .unwrap(); // Responder claims manager diff --git a/crates/synor-ibc/src/types.rs b/crates/synor-ibc/src/types.rs index f99833b..82f9eac 100644 --- a/crates/synor-ibc/src/types.rs +++ b/crates/synor-ibc/src/types.rs @@ -73,10 +73,20 @@ impl fmt::Display for Height { /// Timestamp in nanoseconds since Unix epoch #[derive( - Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, - Serialize, Deserialize, BorshSerialize, BorshDeserialize, + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + BorshSerialize, + BorshDeserialize, + Default, )] -#[derive(Default)] pub struct Timestamp(pub u64); impl Timestamp { @@ -115,7 +125,6 @@ impl Timestamp { } } - impl fmt::Display for Timestamp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) diff --git a/crates/synor-mining/src/lib.rs b/crates/synor-mining/src/lib.rs index b41e0c5..0e1abe8 100644 --- a/crates/synor-mining/src/lib.rs +++ b/crates/synor-mining/src/lib.rs @@ -669,7 +669,10 @@ mod tests { #[test] fn test_mining_error_invalid_template_display() { let error = MiningError::InvalidTemplate("missing coinbase".to_string()); - assert_eq!(format!("{}", error), "Invalid block template: missing coinbase"); + assert_eq!( + format!("{}", error), + "Invalid block template: missing coinbase" + ); } #[test] diff --git a/crates/synor-privacy/src/bulletproofs.rs b/crates/synor-privacy/src/bulletproofs.rs index 1628188..21531c7 100644 --- a/crates/synor-privacy/src/bulletproofs.rs +++ b/crates/synor-privacy/src/bulletproofs.rs @@ -17,17 +17,17 @@ //! protocol once crate compatibility is resolved. use alloc::vec::Vec; +use borsh::{BorshDeserialize, BorshSerialize}; use curve25519_dalek::{ ristretto::{CompressedRistretto, RistrettoPoint}, scalar::Scalar, }; use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use borsh::{BorshSerialize, BorshDeserialize}; -use sha2::{Sha512, Digest}; +use sha2::{Digest, Sha512}; use crate::{ - pedersen::{BlindingFactor, PedersenCommitment, generator_g, generator_h}, + pedersen::{generator_g, generator_h, BlindingFactor, PedersenCommitment}, Error, Result, DOMAIN_SEPARATOR, RANGE_PROOF_BITS, }; @@ -151,11 +151,18 @@ impl RangeProof { let _h = generator_h(); // Verify each bit proof - for (i, (commit_bytes, proof)) in self.bit_commitments.iter().zip(&self.bit_proofs).enumerate() { + for (i, (commit_bytes, proof)) in self + .bit_commitments + .iter() + .zip(&self.bit_proofs) + .enumerate() + { let commit = CompressedRistretto::from_slice(commit_bytes) .map_err(|_| Error::InvalidCommitment(format!("Bit commitment {} invalid", i)))? .decompress() - .ok_or_else(|| Error::InvalidCommitment(format!("Bit commitment {} not on curve", i)))?; + .ok_or_else(|| { + Error::InvalidCommitment(format!("Bit commitment {} not on curve", i)) + })?; if !verify_bit_proof(&commit, proof)? { return Err(Error::RangeProofFailed(format!("Bit proof {} failed", i))); @@ -231,10 +238,7 @@ fn prove_bit( let a1 = h * k; // A1 = k*h (for C - g) // Challenge - let c = hash_to_scalar(&[ - &commitment.compress().to_bytes(), - &a1.compress().to_bytes(), - ]); + let c = hash_to_scalar(&[&commitment.compress().to_bytes(), &a1.compress().to_bytes()]); let e1_real = c - e0_sim; let s1_real = k + e1_real * blinding; diff --git a/crates/synor-privacy/src/confidential.rs b/crates/synor-privacy/src/confidential.rs index f25d9b0..3e4043c 100644 --- a/crates/synor-privacy/src/confidential.rs +++ b/crates/synor-privacy/src/confidential.rs @@ -38,11 +38,11 @@ //! 4. Check the balance equation holds use alloc::vec::Vec; +use borsh::{BorshDeserialize, BorshSerialize}; use curve25519_dalek::scalar::Scalar; use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use borsh::{BorshSerialize, BorshDeserialize}; -use sha2::{Sha512, Digest}; +use sha2::{Digest, Sha512}; use crate::{ bulletproofs::RangeProof, @@ -89,13 +89,7 @@ impl ConfidentialInput { message: &[u8], rng: &mut R, ) -> Result { - let ring_signature = RingSignature::sign( - spending_key, - &ring, - real_index, - message, - rng, - )?; + let ring_signature = RingSignature::sign(spending_key, &ring, real_index, message, rng)?; Ok(Self { ring_signature, @@ -188,7 +182,9 @@ impl ConfidentialOutput { } /// Reference to a previous output -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub struct OutputReference { /// Transaction hash pub tx_hash: [u8; 32], @@ -199,7 +195,10 @@ pub struct OutputReference { impl OutputReference { /// Create a new output reference pub fn new(tx_hash: [u8; 32], output_index: u32) -> Self { - Self { tx_hash, output_index } + Self { + tx_hash, + output_index, + } } } @@ -276,10 +275,7 @@ impl EncryptedAmount { } /// Decrypt the amount using a stealth spending key - pub fn decrypt( - &self, - stealth_address: &StealthAddress, - ) -> Option<(u64, BlindingFactor)> { + pub fn decrypt(&self, stealth_address: &StealthAddress) -> Option<(u64, BlindingFactor)> { // Derive decryption key let mut hasher = Sha512::new(); hasher.update(DOMAIN_SEPARATOR); @@ -413,9 +409,9 @@ impl ConfidentialTransaction { let mut output_commitments = Vec::new(); for (i, output) in self.outputs.iter().enumerate() { - let commitment = output.verify().map_err(|_| { - Error::RangeProofFailed(format!("Output {}", i)) - })?; + let commitment = output + .verify() + .map_err(|_| Error::RangeProofFailed(format!("Output {}", i)))?; output_commitments.push(commitment); } @@ -424,7 +420,10 @@ impl ConfidentialTransaction { let sum_outputs = CommitmentBatch::sum(&output_commitments); // Verify the excess signature proves balance - if !self.excess_signature.verify(&sum_inputs, &sum_outputs, self.fee)? { + if !self + .excess_signature + .verify(&sum_inputs, &sum_outputs, self.fee)? + { return Err(Error::BalanceMismatch); } @@ -566,7 +565,8 @@ impl ExcessSignature { // Expected excess = sum_inputs - sum_outputs - fee*H // (The fee is a commitment to fee with blinding factor 0) let fee_commitment = PedersenCommitment::from_point(g * Scalar::from(fee)); - let _expected_excess = sum_inputs.as_point() - sum_outputs.as_point() - fee_commitment.as_point(); + let _expected_excess = + sum_inputs.as_point() - sum_outputs.as_point() - fee_commitment.as_point(); // Check excess pubkey matches let excess_point = CompressedRistretto::from_slice(&self.excess_pubkey) @@ -618,7 +618,14 @@ impl ExcessSignature { /// Builder for confidential transactions pub struct ConfidentialTransactionBuilder { - inputs: Vec<(RingPrivateKey, Vec, usize, PedersenCommitment, BlindingFactor, OutputReference)>, + inputs: Vec<( + RingPrivateKey, + Vec, + usize, + PedersenCommitment, + BlindingFactor, + OutputReference, + )>, outputs: Vec<(StealthMetaAddress, u64)>, fee: u64, } @@ -643,7 +650,14 @@ impl ConfidentialTransactionBuilder { blinding: BlindingFactor, output_ref: OutputReference, ) -> Self { - self.inputs.push((spending_key, ring, real_index, amount_commitment, blinding, output_ref)); + self.inputs.push(( + spending_key, + ring, + real_index, + amount_commitment, + blinding, + output_ref, + )); self } @@ -665,7 +679,9 @@ impl ConfidentialTransactionBuilder { let _total_input: u64 = self.outputs.iter().map(|(_, a)| *a).sum::() + self.fee; // Collect input blindings - let input_blindings: Vec = self.inputs.iter() + let input_blindings: Vec = self + .inputs + .iter() .map(|(_, _, _, _, b, _)| b.clone()) .collect(); diff --git a/crates/synor-privacy/src/lib.rs b/crates/synor-privacy/src/lib.rs index 7ebf717..3b01089 100644 --- a/crates/synor-privacy/src/lib.rs +++ b/crates/synor-privacy/src/lib.rs @@ -59,21 +59,21 @@ extern crate alloc; -pub mod pedersen; pub mod bulletproofs; -pub mod stealth; -pub mod ring; pub mod confidential; pub mod error; +pub mod pedersen; +pub mod ring; +pub mod stealth; pub use error::{Error, Result}; // Re-export main types -pub use pedersen::{PedersenCommitment, BlindingFactor}; pub use bulletproofs::RangeProof; -pub use stealth::{StealthAddress, StealthKeyPair, ViewKey, SpendKey}; -pub use ring::{RingSignature, KeyImage}; -pub use confidential::{ConfidentialTransaction, ConfidentialInput, ConfidentialOutput}; +pub use confidential::{ConfidentialInput, ConfidentialOutput, ConfidentialTransaction}; +pub use pedersen::{BlindingFactor, PedersenCommitment}; +pub use ring::{KeyImage, RingSignature}; +pub use stealth::{SpendKey, StealthAddress, StealthKeyPair, ViewKey}; /// Domain separator for Synor privacy operations pub const DOMAIN_SEPARATOR: &[u8] = b"SYNOR_PRIVACY_v1"; diff --git a/crates/synor-privacy/src/pedersen.rs b/crates/synor-privacy/src/pedersen.rs index 8789fa9..c8e473c 100644 --- a/crates/synor-privacy/src/pedersen.rs +++ b/crates/synor-privacy/src/pedersen.rs @@ -21,7 +21,8 @@ //! The homomorphic property is crucial for verifying that transaction inputs //! equal outputs without revealing the amounts. -use core::ops::{Add, Sub, Neg}; +use borsh::{BorshDeserialize, BorshSerialize}; +use core::ops::{Add, Neg, Sub}; use curve25519_dalek::{ constants::RISTRETTO_BASEPOINT_POINT, ristretto::{CompressedRistretto, RistrettoPoint}, @@ -29,8 +30,7 @@ use curve25519_dalek::{ }; use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use borsh::{BorshSerialize, BorshDeserialize}; -use sha2::{Sha512, Digest}; +use sha2::{Digest, Sha512}; use zeroize::Zeroize; use crate::{Error, Result, DOMAIN_SEPARATOR}; @@ -185,7 +185,10 @@ pub struct PedersenCommitment { impl PedersenCommitment { /// Create a commitment to a value with a random blinding factor - pub fn commit_random(value: u64, rng: &mut R) -> (Self, BlindingFactor) { + pub fn commit_random( + value: u64, + rng: &mut R, + ) -> (Self, BlindingFactor) { let blinding = BlindingFactor::random(rng); let commitment = Self::commit(value, &blinding); (commitment, blinding) @@ -300,8 +303,11 @@ impl Neg for PedersenCommitment { impl core::fmt::Debug for PedersenCommitment { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let bytes = self.to_bytes(); - write!(f, "PedersenCommitment({:02x}{:02x}{:02x}{:02x}...)", - bytes[0], bytes[1], bytes[2], bytes[3]) + write!( + f, + "PedersenCommitment({:02x}{:02x}{:02x}{:02x}...)", + bytes[0], bytes[1], bytes[2], bytes[3] + ) } } @@ -432,10 +438,7 @@ mod tests { let c_out2 = PedersenCommitment::commit(150, &b_out2); // Calculate excess blinding - let excess = CommitmentBatch::compute_excess( - &[b_in1, b_in2], - &[b_out1, b_out2], - ); + let excess = CommitmentBatch::compute_excess(&[b_in1, b_in2], &[b_out1, b_out2]); // Verify balance assert!(PedersenCommitment::verify_balance( diff --git a/crates/synor-privacy/src/ring.rs b/crates/synor-privacy/src/ring.rs index a787964..6fb5d39 100644 --- a/crates/synor-privacy/src/ring.rs +++ b/crates/synor-privacy/src/ring.rs @@ -32,6 +32,7 @@ //! - **Linkability**: Can detect if same key signed twice (via key image) use alloc::vec::Vec; +use borsh::{BorshDeserialize, BorshSerialize}; use curve25519_dalek::{ constants::RISTRETTO_BASEPOINT_POINT, ristretto::{CompressedRistretto, RistrettoPoint}, @@ -39,8 +40,7 @@ use curve25519_dalek::{ }; use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use borsh::{BorshSerialize, BorshDeserialize}; -use sha2::{Sha512, Digest}; +use sha2::{Digest, Sha512}; use zeroize::Zeroize; use crate::{Error, Result, DOMAIN_SEPARATOR}; @@ -107,7 +107,9 @@ impl RingPublicKey { /// Get the decompressed point pub fn as_point(&self) -> RistrettoPoint { - self.point.decompress().expect("RingPublicKey should be valid") + self.point + .decompress() + .expect("RingPublicKey should be valid") } /// Create from a point @@ -121,8 +123,11 @@ impl RingPublicKey { impl core::fmt::Debug for RingPublicKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let bytes = self.point.to_bytes(); - write!(f, "RingPubKey({:02x}{:02x}{:02x}{:02x}...)", - bytes[0], bytes[1], bytes[2], bytes[3]) + write!( + f, + "RingPubKey({:02x}{:02x}{:02x}{:02x}...)", + bytes[0], bytes[1], bytes[2], bytes[3] + ) } } @@ -231,8 +236,11 @@ impl KeyImage { impl core::fmt::Debug for KeyImage { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let bytes = self.point.to_bytes(); - write!(f, "KeyImage({:02x}{:02x}{:02x}{:02x}...)", - bytes[0], bytes[1], bytes[2], bytes[3]) + write!( + f, + "KeyImage({:02x}{:02x}{:02x}{:02x}...)", + bytes[0], bytes[1], bytes[2], bytes[3] + ) } } @@ -556,22 +564,16 @@ mod tests { let mut rng = OsRng; // Generate ring of 4 keys - let keys: Vec = (0..4) - .map(|_| RingPrivateKey::generate(&mut rng)) - .collect(); + let keys: Vec = + (0..4).map(|_| RingPrivateKey::generate(&mut rng)).collect(); let ring: Vec = keys.iter().map(|k| *k.public_key()).collect(); let signer_index = 2; let message = b"Test message"; - let signature = RingSignature::sign( - &keys[signer_index], - &ring, - signer_index, - message, - &mut rng, - ) - .unwrap(); + let signature = + RingSignature::sign(&keys[signer_index], &ring, signer_index, message, &mut rng) + .unwrap(); // Verify assert!(signature.verify(&ring, message).unwrap()); @@ -581,19 +583,12 @@ mod tests { fn test_ring_signature_wrong_message_fails() { let mut rng = OsRng; - let keys: Vec = (0..4) - .map(|_| RingPrivateKey::generate(&mut rng)) - .collect(); + let keys: Vec = + (0..4).map(|_| RingPrivateKey::generate(&mut rng)).collect(); let ring: Vec = keys.iter().map(|k| *k.public_key()).collect(); - let signature = RingSignature::sign( - &keys[0], - &ring, - 0, - b"Correct message", - &mut rng, - ) - .unwrap(); + let signature = + RingSignature::sign(&keys[0], &ring, 0, b"Correct message", &mut rng).unwrap(); // Wrong message should fail assert!(!signature.verify(&ring, b"Wrong message").unwrap()); @@ -603,24 +598,15 @@ mod tests { fn test_ring_signature_wrong_ring_fails() { let mut rng = OsRng; - let keys: Vec = (0..4) - .map(|_| RingPrivateKey::generate(&mut rng)) - .collect(); + let keys: Vec = + (0..4).map(|_| RingPrivateKey::generate(&mut rng)).collect(); let ring: Vec = keys.iter().map(|k| *k.public_key()).collect(); - let signature = RingSignature::sign( - &keys[0], - &ring, - 0, - b"Test", - &mut rng, - ) - .unwrap(); + let signature = RingSignature::sign(&keys[0], &ring, 0, b"Test", &mut rng).unwrap(); // Different ring should fail - let other_keys: Vec = (0..4) - .map(|_| RingPrivateKey::generate(&mut rng)) - .collect(); + let other_keys: Vec = + (0..4).map(|_| RingPrivateKey::generate(&mut rng)).collect(); let other_ring: Vec = other_keys.iter().map(|k| *k.public_key()).collect(); assert!(!signature.verify(&other_ring, b"Test").unwrap()); @@ -630,19 +616,11 @@ mod tests { fn test_key_image_in_signature() { let mut rng = OsRng; - let keys: Vec = (0..4) - .map(|_| RingPrivateKey::generate(&mut rng)) - .collect(); + let keys: Vec = + (0..4).map(|_| RingPrivateKey::generate(&mut rng)).collect(); let ring: Vec = keys.iter().map(|k| *k.public_key()).collect(); - let signature = RingSignature::sign( - &keys[1], - &ring, - 1, - b"Test", - &mut rng, - ) - .unwrap(); + let signature = RingSignature::sign(&keys[1], &ring, 1, b"Test", &mut rng).unwrap(); // Key image should match signer's key image assert_eq!( @@ -703,14 +681,8 @@ mod tests { assert_eq!(ring.len(), 6); // Signer should be able to sign - let signature = RingSignature::sign( - &signer, - &ring, - signer_index, - b"Test", - &mut rng, - ) - .unwrap(); + let signature = + RingSignature::sign(&signer, &ring, signer_index, b"Test", &mut rng).unwrap(); assert!(signature.verify(&ring, b"Test").unwrap()); } @@ -718,9 +690,8 @@ mod tests { fn test_serialization() { let mut rng = OsRng; - let keys: Vec = (0..3) - .map(|_| RingPrivateKey::generate(&mut rng)) - .collect(); + let keys: Vec = + (0..3).map(|_| RingPrivateKey::generate(&mut rng)).collect(); let ring: Vec = keys.iter().map(|k| *k.public_key()).collect(); let signature = RingSignature::sign(&keys[0], &ring, 0, b"Test", &mut rng).unwrap(); @@ -737,9 +708,8 @@ mod tests { fn test_signer_at_different_indices() { let mut rng = OsRng; - let keys: Vec = (0..5) - .map(|_| RingPrivateKey::generate(&mut rng)) - .collect(); + let keys: Vec = + (0..5).map(|_| RingPrivateKey::generate(&mut rng)).collect(); let ring: Vec = keys.iter().map(|k| *k.public_key()).collect(); // Test signing from each position diff --git a/crates/synor-privacy/src/stealth.rs b/crates/synor-privacy/src/stealth.rs index a00af16..0c5e901 100644 --- a/crates/synor-privacy/src/stealth.rs +++ b/crates/synor-privacy/src/stealth.rs @@ -31,6 +31,7 @@ //! - **One-time keys**: Each payment uses a unique address use alloc::vec::Vec; +use borsh::{BorshDeserialize, BorshSerialize}; use curve25519_dalek::{ constants::RISTRETTO_BASEPOINT_POINT, ristretto::{CompressedRistretto, RistrettoPoint}, @@ -38,8 +39,7 @@ use curve25519_dalek::{ }; use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use borsh::{BorshSerialize, BorshDeserialize}; -use sha2::{Sha512, Digest}; +use sha2::{Digest, Sha512}; use zeroize::Zeroize; use crate::{Error, Result, DOMAIN_SEPARATOR}; @@ -99,8 +99,11 @@ impl ViewKey { impl core::fmt::Debug for ViewKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let bytes = self.point.to_bytes(); - write!(f, "ViewKey({:02x}{:02x}{:02x}{:02x}...)", - bytes[0], bytes[1], bytes[2], bytes[3]) + write!( + f, + "ViewKey({:02x}{:02x}{:02x}{:02x}...)", + bytes[0], bytes[1], bytes[2], bytes[3] + ) } } @@ -150,8 +153,11 @@ impl SpendKey { impl core::fmt::Debug for SpendKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let bytes = self.point.to_bytes(); - write!(f, "SpendKey({:02x}{:02x}{:02x}{:02x}...)", - bytes[0], bytes[1], bytes[2], bytes[3]) + write!( + f, + "SpendKey({:02x}{:02x}{:02x}{:02x}...)", + bytes[0], bytes[1], bytes[2], bytes[3] + ) } } @@ -171,7 +177,9 @@ impl BorshDeserialize for SpendKey { } /// A stealth meta-address (public) - published by the recipient -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub struct StealthMetaAddress { /// View key - for detecting payments pub view_key: ViewKey, @@ -182,7 +190,10 @@ pub struct StealthMetaAddress { impl StealthMetaAddress { /// Create a new stealth meta-address pub fn new(view_key: ViewKey, spend_key: SpendKey) -> Self { - Self { view_key, spend_key } + Self { + view_key, + spend_key, + } } /// Convert to bytes (64 bytes total) @@ -197,7 +208,10 @@ impl StealthMetaAddress { pub fn from_bytes(bytes: &[u8; 64]) -> Result { let view_key = ViewKey::from_bytes(bytes[..32].try_into().unwrap())?; let spend_key = SpendKey::from_bytes(bytes[32..].try_into().unwrap())?; - Ok(Self { view_key, spend_key }) + Ok(Self { + view_key, + spend_key, + }) } } @@ -291,10 +305,7 @@ impl StealthKeyPair { /// Check if a stealth address belongs to this keypair /// Returns the spending key if it matches - pub fn check_ownership( - &self, - stealth_address: &StealthAddress, - ) -> Option { + pub fn check_ownership(&self, stealth_address: &StealthAddress) -> Option { // Compute shared secret: S = view_secret * R let shared_secret = stealth_address.ephemeral_pubkey.as_point() * self.view_secret; @@ -321,9 +332,7 @@ impl StealthKeyPair { ) -> Vec<(&'a StealthAddress, StealthSpendingKey)> { addresses .iter() - .filter_map(|addr| { - self.check_ownership(addr).map(|key| (addr, key)) - }) + .filter_map(|addr| self.check_ownership(addr).map(|key| (addr, key))) .collect() } } @@ -388,7 +397,9 @@ impl ViewOnlyWallet { } /// A one-time stealth address -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] pub struct StealthAddress { /// The one-time address (P) pub address: SpendKey, // Reusing SpendKey type as it's the same format @@ -398,10 +409,7 @@ pub struct StealthAddress { impl StealthAddress { /// Generate a stealth address for a recipient - pub fn generate( - recipient: &StealthMetaAddress, - rng: &mut R, - ) -> Self { + pub fn generate(recipient: &StealthMetaAddress, rng: &mut R) -> Self { // Generate ephemeral keypair let ephemeral_secret = random_scalar(rng); let ephemeral_pubkey = generator() * ephemeral_secret; @@ -592,7 +600,10 @@ mod tests { let spending_key = keypair.check_ownership(&stealth).unwrap(); // Spending key should correspond to stealth address - assert_eq!(spending_key.public_key().to_bytes(), stealth.address.to_bytes()); + assert_eq!( + spending_key.public_key().to_bytes(), + stealth.address.to_bytes() + ); } #[test] diff --git a/crates/synor-sharding/src/leader.rs b/crates/synor-sharding/src/leader.rs index 71d3e5c..0be28d5 100644 --- a/crates/synor-sharding/src/leader.rs +++ b/crates/synor-sharding/src/leader.rs @@ -167,7 +167,10 @@ impl LeaderElection { /// Gets validators for a shard. pub fn get_validators(&self, shard_id: ShardId) -> &[ValidatorInfo] { - self.validators.get(&shard_id).map(|v| v.as_slice()).unwrap_or(&[]) + self.validators + .get(&shard_id) + .map(|v| v.as_slice()) + .unwrap_or(&[]) } /// Gets the total stake for a shard. @@ -291,6 +294,10 @@ mod tests { } // High stake validator should be selected most of the time - assert!(high_stake_count > 80, "High stake validator selected {} times", high_stake_count); + assert!( + high_stake_count > 80, + "High stake validator selected {} times", + high_stake_count + ); } } diff --git a/crates/synor-sharding/src/lib.rs b/crates/synor-sharding/src/lib.rs index 53cddb6..d5b6efd 100644 --- a/crates/synor-sharding/src/lib.rs +++ b/crates/synor-sharding/src/lib.rs @@ -59,7 +59,7 @@ pub use leader::{LeaderElection, VrfOutput}; pub use messaging::{CrossShardMessage, MessageRouter}; pub use proof_agg::{AggregatedProof, ProofAggregator}; pub use reshard::{ReshardEvent, ReshardManager}; -pub use routing::{ShardRouter, RoutingTable}; +pub use routing::{RoutingTable, ShardRouter}; pub use state::{ShardState, ShardStateManager}; use serde::{Deserialize, Serialize}; @@ -239,7 +239,10 @@ impl ShardManager { /// Applies a resharding event. fn apply_reshard(&mut self, event: ReshardEvent) { match event { - ReshardEvent::Split { shard_id, new_shards } => { + ReshardEvent::Split { + shard_id, + new_shards, + } => { tracing::info!("Splitting shard {} into {:?}", shard_id, new_shards); self.state_manager.split_shard(shard_id, &new_shards); } @@ -257,7 +260,8 @@ impl ShardManager { to: ShardId, payload: Vec, ) -> ShardResult { - self.message_router.send(from, to, payload, self.current_slot) + self.message_router + .send(from, to, payload, self.current_slot) } /// Processes pending cross-shard messages for a shard. @@ -272,7 +276,8 @@ impl ShardManager { /// Aggregates proofs from multiple shards. pub fn aggregate_proofs(&self, shard_ids: &[ShardId]) -> ShardResult { - self.proof_aggregator.aggregate(shard_ids, &self.state_manager) + self.proof_aggregator + .aggregate(shard_ids, &self.state_manager) } /// Returns the total number of active shards. diff --git a/crates/synor-sharding/src/messaging.rs b/crates/synor-sharding/src/messaging.rs index 162294f..ba7ee1f 100644 --- a/crates/synor-sharding/src/messaging.rs +++ b/crates/synor-sharding/src/messaging.rs @@ -45,12 +45,7 @@ pub struct CrossShardMessage { impl CrossShardMessage { /// Creates a new cross-shard message. - pub fn new( - from_shard: ShardId, - to_shard: ShardId, - payload: Vec, - sent_slot: Slot, - ) -> Self { + pub fn new(from_shard: ShardId, to_shard: ShardId, payload: Vec, sent_slot: Slot) -> Self { // Generate unique ID let mut hasher = blake3::Hasher::new(); hasher.update(&from_shard.to_le_bytes()); @@ -140,7 +135,10 @@ impl MessageRouter { current_slot: Slot, ) -> ShardResult { if from >= self.num_shards || to >= self.num_shards { - return Err(ShardError::InvalidShardId(from.max(to), self.num_shards - 1)); + return Err(ShardError::InvalidShardId( + from.max(to), + self.num_shards - 1, + )); } if from == to { @@ -239,10 +237,7 @@ impl MessageRouter { /// Gets pending message count for a shard. pub fn pending_count(&self, shard_id: ShardId) -> usize { - self.inbound - .get(&shard_id) - .map(|q| q.len()) - .unwrap_or(0) + self.inbound.get(&shard_id).map(|q| q.len()).unwrap_or(0) } /// Handles message timeout. @@ -265,7 +260,8 @@ impl MessageRouter { /// Cleans up old receipts. pub fn cleanup_old_receipts(&mut self, min_slot: Slot) { - self.receipts.retain(|_, receipt| receipt.processed_slot >= min_slot); + self.receipts + .retain(|_, receipt| receipt.processed_slot >= min_slot); } /// Gets statistics about message routing. @@ -332,7 +328,14 @@ mod tests { // Confirm processing router - .confirm(msg_id, 1, 20, true, b"ok".to_vec(), Hash256::from_bytes([1u8; 32])) + .confirm( + msg_id, + 1, + 20, + true, + b"ok".to_vec(), + Hash256::from_bytes([1u8; 32]), + ) .unwrap(); let receipt = router.get_receipt(&msg_id).unwrap(); diff --git a/crates/synor-sharding/src/proof_agg.rs b/crates/synor-sharding/src/proof_agg.rs index 7ef71d0..8a3286b 100644 --- a/crates/synor-sharding/src/proof_agg.rs +++ b/crates/synor-sharding/src/proof_agg.rs @@ -152,10 +152,7 @@ impl ProofAggregator { } /// Aggregates proofs from all shards. - pub fn aggregate_all( - &self, - state_manager: &ShardStateManager, - ) -> ShardResult { + pub fn aggregate_all(&self, state_manager: &ShardStateManager) -> ShardResult { let all_ids: Vec = (0..self.num_shards).collect(); self.aggregate(&all_ids, state_manager) } diff --git a/crates/synor-sharding/src/reshard.rs b/crates/synor-sharding/src/reshard.rs index 8c45825..434eba4 100644 --- a/crates/synor-sharding/src/reshard.rs +++ b/crates/synor-sharding/src/reshard.rs @@ -136,7 +136,8 @@ impl ReshardManager { } // Check for underutilized shards (need merge) - let underutilized: Vec<_> = self.metrics + let underutilized: Vec<_> = self + .metrics .iter() .filter(|m| m.avg_tps < self.merge_threshold_tps) .collect(); @@ -174,8 +175,15 @@ impl ReshardManager { self.status = ReshardStatus::Migrating; match event { - ReshardEvent::Split { shard_id, new_shards } => { - tracing::info!("Executing split of shard {} into {:?}", shard_id, new_shards); + ReshardEvent::Split { + shard_id, + new_shards, + } => { + tracing::info!( + "Executing split of shard {} into {:?}", + shard_id, + new_shards + ); // Remove old shard metrics self.metrics.retain(|m| m.shard_id != *shard_id); @@ -194,7 +202,8 @@ impl ReshardManager { tracing::info!("Executing merge of shards {:?} into {}", shards, into); // Remove merged shards (except target) - self.metrics.retain(|m| m.shard_id == *into || !shards.contains(&m.shard_id)); + self.metrics + .retain(|m| m.shard_id == *into || !shards.contains(&m.shard_id)); } } @@ -243,10 +252,12 @@ impl ReshardManager { } let avg = self.total_tps() / self.metrics.len() as f64; - let variance: f64 = self.metrics + let variance: f64 = self + .metrics .iter() .map(|m| (m.avg_tps - avg).powi(2)) - .sum::() / self.metrics.len() as f64; + .sum::() + / self.metrics.len() as f64; variance.sqrt() } @@ -286,7 +297,10 @@ mod tests { manager.update_metrics(0, metrics); let event = manager.check_reshard_needed(); - assert!(matches!(event, Some(ReshardEvent::Split { shard_id: 0, .. }))); + assert!(matches!( + event, + Some(ReshardEvent::Split { shard_id: 0, .. }) + )); } #[test] diff --git a/crates/synor-sharding/src/routing.rs b/crates/synor-sharding/src/routing.rs index ffeae42..4616cb7 100644 --- a/crates/synor-sharding/src/routing.rs +++ b/crates/synor-sharding/src/routing.rs @@ -198,7 +198,11 @@ impl ShardRouter { pub fn load_stats(&self) -> LoadStats { let loads: Vec = self.shard_load.values().copied().collect(); let total: u64 = loads.iter().sum(); - let avg = if loads.is_empty() { 0.0 } else { total as f64 / loads.len() as f64 }; + let avg = if loads.is_empty() { + 0.0 + } else { + total as f64 / loads.len() as f64 + }; let min = loads.iter().min().copied().unwrap_or(0); let max = loads.iter().max().copied().unwrap_or(0); diff --git a/crates/synor-sharding/src/state.rs b/crates/synor-sharding/src/state.rs index fd27c96..c7e25fb 100644 --- a/crates/synor-sharding/src/state.rs +++ b/crates/synor-sharding/src/state.rs @@ -82,12 +82,7 @@ impl ShardState { } /// Transfers balance between accounts (within same shard). - pub fn transfer( - &mut self, - from: &Hash256, - to: &Hash256, - amount: u128, - ) -> ShardResult<()> { + pub fn transfer(&mut self, from: &Hash256, to: &Hash256, amount: u128) -> ShardResult<()> { let from_balance = self.get_balance(from); if from_balance < amount { return Err(ShardError::Internal("Insufficient balance".into())); diff --git a/crates/synor-storage/src/bin/storage-node.rs b/crates/synor-storage/src/bin/storage-node.rs index 540e86a..1d56532 100644 --- a/crates/synor-storage/src/bin/storage-node.rs +++ b/crates/synor-storage/src/bin/storage-node.rs @@ -6,7 +6,7 @@ //! synor-storage-node --config /path/to/config.toml //! synor-storage-node --data-dir ./storage-data -use synor_storage::{NodeConfig, StorageNode, GatewayConfig, Gateway}; +use synor_storage::{Gateway, GatewayConfig, NodeConfig, StorageNode}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -17,13 +17,15 @@ async fn main() -> Result<(), Box> { // Parse command line arguments let args: Vec = std::env::args().collect(); - let config_path = args.iter() + let config_path = args + .iter() .position(|a| a == "--config") .map(|i| args.get(i + 1)) .flatten() .cloned(); - let data_dir = args.iter() + let data_dir = args + .iter() .position(|a| a == "--data-dir") .map(|i| args.get(i + 1)) .flatten() @@ -65,7 +67,8 @@ async fn main() -> Result<(), Box> { if gateway_enabled { let gateway_config = GatewayConfig { - listen_addr: std::env::var("GATEWAY_ADDR").unwrap_or_else(|_| "0.0.0.0:8080".to_string()), + listen_addr: std::env::var("GATEWAY_ADDR") + .unwrap_or_else(|_| "0.0.0.0:8080".to_string()), ..GatewayConfig::default() }; diff --git a/crates/synor-storage/src/car.rs b/crates/synor-storage/src/car.rs index 55eda03..4fd1f9c 100644 --- a/crates/synor-storage/src/car.rs +++ b/crates/synor-storage/src/car.rs @@ -113,8 +113,8 @@ impl CarHeader { .map_err(|e| Error::InvalidFormat(e.to_string()))?; offset += cid_len as usize; - let cid = ContentId::from_string(cid_str) - .map_err(|e| Error::InvalidCid(e.to_string()))?; + let cid = + ContentId::from_string(cid_str).map_err(|e| Error::InvalidCid(e.to_string()))?; roots.push(cid); } @@ -176,8 +176,8 @@ impl CarBlock { .map_err(|e| Error::InvalidFormat(e.to_string()))?; offset += cid_len as usize; - let mut cid = ContentId::from_string(cid_str) - .map_err(|e| Error::InvalidCid(e.to_string()))?; + let mut cid = + ContentId::from_string(cid_str).map_err(|e| Error::InvalidCid(e.to_string()))?; // Data let (data_len, read) = decode_varint(&data[offset..])?; @@ -189,7 +189,13 @@ impl CarBlock { // Set the size field since it's not encoded in the CID string cid.size = block_data.len() as u64; - Ok((Self { cid, data: block_data }, offset)) + Ok(( + Self { + cid, + data: block_data, + }, + offset, + )) } } @@ -325,7 +331,9 @@ impl CarFile { /// Get a block by CID pub fn get_block(&self, cid: &ContentId) -> Option<&CarBlock> { let cid_str = cid.to_string_repr(); - self.blocks.iter().find(|b| b.cid.to_string_repr() == cid_str) + self.blocks + .iter() + .find(|b| b.cid.to_string_repr() == cid_str) } /// Total size in bytes @@ -356,7 +364,9 @@ impl TrustlessResponse { /// Create a trustless response from content pub fn from_content(data: &[u8]) -> Self { let car_file = CarFile::from_content(data); - let root_cid = car_file.roots().first() + let root_cid = car_file + .roots() + .first() .map(|c| c.to_string_repr()) .unwrap_or_default(); diff --git a/crates/synor-storage/src/chunker.rs b/crates/synor-storage/src/chunker.rs index 4c6206b..2f9822e 100644 --- a/crates/synor-storage/src/chunker.rs +++ b/crates/synor-storage/src/chunker.rs @@ -88,11 +88,7 @@ impl Chunker { let mut index = 0u32; for chunk_data in data.chunks(self.config.chunk_size) { - chunks.push(Chunk::new( - index, - chunk_data.to_vec(), - offset, - )); + chunks.push(Chunk::new(index, chunk_data.to_vec(), offset)); offset += chunk_data.len() as u64; index += 1; } @@ -127,7 +123,7 @@ impl Chunker { // Combine data let total_size: usize = sorted.iter().map(|c| c.data.len()).sum(); let mut result = Vec::with_capacity(total_size); - + for chunk in sorted { result.extend_from_slice(&chunk.data); } @@ -140,7 +136,7 @@ impl Chunker { let size = file_size as usize; let full_chunks = size / self.config.chunk_size; let has_remainder = size % self.config.chunk_size != 0; - + (full_chunks + if has_remainder { 1 } else { 0 }) as u32 } } @@ -193,7 +189,11 @@ impl ChunkedFile { cid: original_cid, size: chunks.iter().map(|c| c.data.len() as u64).sum(), chunk_count: chunks.len() as u32, - chunk_size: if chunks.is_empty() { 0 } else { chunks[0].data.len() }, + chunk_size: if chunks.is_empty() { + 0 + } else { + chunks[0].data.len() + }, chunk_cids: chunks.iter().map(|c| c.cid.clone()).collect(), } } @@ -207,7 +207,7 @@ mod tests { fn test_chunk_small_file() { let chunker = Chunker::new(); let data = b"Small file that fits in one chunk"; - + let chunks = chunker.chunk(data); assert_eq!(chunks.len(), 1); assert_eq!(chunks[0].data, data); @@ -218,12 +218,12 @@ mod tests { fn test_chunk_large_file() { let config = ChunkerConfig { chunk_size: 10 }; let chunker = Chunker::with_config(config); - + let data = b"This is a longer file that will be split into chunks"; let chunks = chunker.chunk(data); - + assert!(chunks.len() > 1); - + // Verify all chunks for chunk in &chunks { assert!(chunk.verify()); @@ -234,10 +234,10 @@ mod tests { fn test_reassemble() { let config = ChunkerConfig { chunk_size: 10 }; let chunker = Chunker::with_config(config); - + let original = b"This is a test file for chunking and reassembly"; let chunks = chunker.chunk(original); - + let reassembled = chunker.reassemble(&chunks).unwrap(); assert_eq!(reassembled, original); } @@ -246,13 +246,13 @@ mod tests { fn test_reassemble_missing_chunk() { let config = ChunkerConfig { chunk_size: 10 }; let chunker = Chunker::with_config(config); - + let data = b"Test data for missing chunk test case here"; let mut chunks = chunker.chunk(data); - + // Remove middle chunk chunks.remove(1); - + let result = chunker.reassemble(&chunks); assert!(matches!(result, Err(ReassembleError::MissingChunk(_)))); } @@ -261,7 +261,7 @@ mod tests { fn test_chunk_count() { let config = ChunkerConfig { chunk_size: 100 }; let chunker = Chunker::with_config(config); - + assert_eq!(chunker.chunk_count(0), 0); assert_eq!(chunker.chunk_count(50), 1); assert_eq!(chunker.chunk_count(100), 1); @@ -273,9 +273,9 @@ mod tests { fn test_empty_file() { let chunker = Chunker::new(); let chunks = chunker.chunk(&[]); - + assert!(chunks.is_empty()); - + let reassembled = chunker.reassemble(&chunks).unwrap(); assert!(reassembled.is_empty()); } diff --git a/crates/synor-storage/src/cid.rs b/crates/synor-storage/src/cid.rs index f0efc6b..de43b13 100644 --- a/crates/synor-storage/src/cid.rs +++ b/crates/synor-storage/src/cid.rs @@ -20,7 +20,6 @@ pub enum HashType { Blake3 = 0x1E, } - /// Content Identifier - uniquely identifies content by hash #[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ContentId { @@ -45,14 +44,14 @@ impl ContentId { /// Create a new CID from content bytes using SHA256 pub fn from_content_sha256(data: &[u8]) -> Self { - use sha2::{Sha256, Digest}; + use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); hasher.update(data); let result = hasher.finalize(); - + let mut digest = [0u8; 32]; digest.copy_from_slice(&result); - + Self { hash_type: HashType::Sha256, digest, @@ -65,14 +64,14 @@ impl ContentId { if data.len() as u64 != self.size { return false; } - + match self.hash_type { HashType::Blake3 => { let hash = blake3::hash(data); hash.as_bytes() == &self.digest } HashType::Sha256 => { - use sha2::{Sha256, Digest}; + use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); hasher.update(data); let result = hasher.finalize(); @@ -99,31 +98,31 @@ impl ContentId { if !s.starts_with("synor1") { return Err(CidParseError::InvalidPrefix); } - + let encoded = &s[6..]; // Skip "synor1" let bytes = bs58::decode(encoded) .into_vec() .map_err(|_| CidParseError::InvalidBase58)?; - + if bytes.len() < 34 { return Err(CidParseError::InvalidLength); } - + let hash_type = match bytes[0] { 0x12 => HashType::Sha256, 0x1B => HashType::Keccak256, 0x1E => HashType::Blake3, _ => return Err(CidParseError::UnknownHashType), }; - + let digest_len = bytes[1] as usize; if digest_len != 32 || bytes.len() < 2 + digest_len { return Err(CidParseError::InvalidLength); } - + let mut digest = [0u8; 32]; digest.copy_from_slice(&bytes[2..34]); - + Ok(Self { hash_type, digest, @@ -192,7 +191,7 @@ mod tests { fn test_cid_from_content() { let data = b"Hello, Synor Storage!"; let cid = ContentId::from_content(data); - + assert_eq!(cid.hash_type, HashType::Blake3); assert_eq!(cid.size, data.len() as u64); assert!(cid.verify(data)); @@ -202,7 +201,7 @@ mod tests { fn test_cid_verification_fails_wrong_data() { let data = b"Hello, Synor Storage!"; let cid = ContentId::from_content(data); - + assert!(!cid.verify(b"Wrong data")); } @@ -210,10 +209,10 @@ mod tests { fn test_cid_string_roundtrip() { let data = b"Test content for CID"; let cid = ContentId::from_content(data); - + let s = cid.to_string_repr(); assert!(s.starts_with("synor1")); - + let parsed = ContentId::from_string(&s).unwrap(); assert_eq!(cid.hash_type, parsed.hash_type); assert_eq!(cid.digest, parsed.digest); @@ -223,7 +222,7 @@ mod tests { fn test_cid_display() { let data = b"Display test"; let cid = ContentId::from_content(data); - + let display = format!("{}", cid); assert!(display.starts_with("synor1")); } @@ -232,7 +231,7 @@ mod tests { fn test_cid_sha256() { let data = b"SHA256 test"; let cid = ContentId::from_content_sha256(data); - + assert_eq!(cid.hash_type, HashType::Sha256); assert!(cid.verify(data)); } diff --git a/crates/synor-storage/src/deal.rs b/crates/synor-storage/src/deal.rs index dd852bb..0c2a833 100644 --- a/crates/synor-storage/src/deal.rs +++ b/crates/synor-storage/src/deal.rs @@ -83,7 +83,7 @@ impl StorageDeal { if total_possible_proofs == 0 { return 0; } - + let success_rate = self.successful_proofs as u64 * 100 / total_possible_proofs; self.total_cost() * success_rate / 100 } @@ -171,7 +171,7 @@ impl StorageMarket { current_block: u64, ) -> StorageDeal { let deal_id = self.generate_deal_id(&request.cid, &client, current_block); - + let offer = self.offers.iter().find(|o| o.provider == provider); let price = offer.map(|o| o.price_per_byte_epoch).unwrap_or(0); @@ -184,7 +184,7 @@ impl StorageMarket { duration: request.duration, price_per_byte_epoch: price, provider_collateral: 0, // Set during acceptance - client_deposit: 0, // Set during creation + client_deposit: 0, // Set during creation start_block: current_block, end_block: current_block + request.duration, status: DealStatus::Pending, @@ -282,7 +282,7 @@ mod tests { #[test] fn test_market_find_providers() { let mut market = StorageMarket::new(); - + market.add_offer(StorageOffer { provider: [1u8; 32], available_capacity: 100_000_000_000, // 100 GB diff --git a/crates/synor-storage/src/erasure.rs b/crates/synor-storage/src/erasure.rs index d559c9f..1ec6fee 100644 --- a/crates/synor-storage/src/erasure.rs +++ b/crates/synor-storage/src/erasure.rs @@ -3,10 +3,10 @@ //! Uses Reed-Solomon coding to add redundancy to chunks. //! Allows recovery of data even if some shards are lost. -use reed_solomon_erasure::galois_8::ReedSolomon; -use serde::{Deserialize, Serialize}; use crate::cid::ContentId; use crate::error::{Error, Result}; +use reed_solomon_erasure::galois_8::ReedSolomon; +use serde::{Deserialize, Serialize}; /// Default number of data shards pub const DEFAULT_DATA_SHARDS: usize = 10; @@ -108,26 +108,23 @@ impl ErasureCoder { pub fn with_config(config: ErasureConfig) -> Result { let rs = ReedSolomon::new(config.data_shards, config.parity_shards) .map_err(|e| Error::ErasureCoding(format!("Failed to create RS coder: {}", e)))?; - + Ok(Self { config, rs }) } /// Encode data into shards with parity pub fn encode(&self, data: &[u8], chunk_cid: ContentId) -> Result { let original_size = data.len(); - + // Pad data to be divisible by data_shards let shard_size = data.len().div_ceil(self.config.data_shards); let padded_size = shard_size * self.config.data_shards; - + let mut padded_data = data.to_vec(); padded_data.resize(padded_size, 0); // Split into data shards - let mut shards: Vec> = padded_data - .chunks(shard_size) - .map(|c| c.to_vec()) - .collect(); + let mut shards: Vec> = padded_data.chunks(shard_size).map(|c| c.to_vec()).collect(); // Add parity shards (initially empty) for _ in 0..self.config.parity_shards { @@ -135,16 +132,15 @@ impl ErasureCoder { } // Encode parity - self.rs.encode(&mut shards) + self.rs + .encode(&mut shards) .map_err(|e| Error::ErasureCoding(format!("Encoding failed: {}", e)))?; // Create shard structs let shard_structs: Vec = shards .into_iter() .enumerate() - .map(|(i, data)| { - Shard::new(i, i < self.config.data_shards, data) - }) + .map(|(i, data)| Shard::new(i, i < self.config.data_shards, data)) .collect(); Ok(EncodedChunk { @@ -161,7 +157,7 @@ impl ErasureCoder { /// Some shards can be None (missing) as long as enough remain pub fn decode(&self, encoded: &EncodedChunk) -> Result> { let total = encoded.data_shards + encoded.parity_shards; - + // Prepare shards (Some for present, None for missing) let mut shards: Vec>> = vec![None; total]; let mut present_count = 0; @@ -182,7 +178,8 @@ impl ErasureCoder { } // Reconstruct missing shards - self.rs.reconstruct(&mut shards) + self.rs + .reconstruct(&mut shards) .map_err(|e| Error::ErasureCoding(format!("Reconstruction failed: {}", e)))?; // Combine data shards @@ -217,10 +214,10 @@ mod tests { let coder = ErasureCoder::new().unwrap(); let data = b"Hello, erasure coding!"; let cid = ContentId::from_content(data); - + let encoded = coder.encode(data, cid).unwrap(); assert_eq!(encoded.shards.len(), 14); // 10 data + 4 parity - + let decoded = coder.decode(&encoded).unwrap(); assert_eq!(decoded, data); } @@ -230,15 +227,15 @@ mod tests { let coder = ErasureCoder::new().unwrap(); let data = b"Test data for recovery with some missing shards"; let cid = ContentId::from_content(data); - + let mut encoded = coder.encode(data, cid).unwrap(); - + // Remove 4 shards (max we can lose with 4 parity) encoded.shards.remove(0); encoded.shards.remove(2); encoded.shards.remove(4); encoded.shards.remove(6); - + let decoded = coder.decode(&encoded).unwrap(); assert_eq!(decoded, data); } @@ -248,14 +245,14 @@ mod tests { let coder = ErasureCoder::new().unwrap(); let data = b"Test data"; let cid = ContentId::from_content(data); - + let mut encoded = coder.encode(data, cid).unwrap(); - + // Remove 5 shards (more than 4 parity can handle) for _ in 0..5 { encoded.shards.remove(0); } - + let result = coder.decode(&encoded); assert!(result.is_err()); } @@ -273,13 +270,13 @@ mod tests { parity_shards: 1, }; let coder = ErasureCoder::with_config(config).unwrap(); - + let data = b"Small test"; let cid = ContentId::from_content(data); - + let encoded = coder.encode(data, cid).unwrap(); assert_eq!(encoded.shards.len(), 3); - + let decoded = coder.decode(&encoded).unwrap(); assert_eq!(decoded, data); } diff --git a/crates/synor-storage/src/gateway/cache.rs b/crates/synor-storage/src/gateway/cache.rs index 33e019c..2efca7d 100644 --- a/crates/synor-storage/src/gateway/cache.rs +++ b/crates/synor-storage/src/gateway/cache.rs @@ -3,8 +3,8 @@ //! Caches resolved content to reduce load on storage nodes //! and improve response times for popular content. -use crate::cid::ContentId; use super::GatewayResponse; +use crate::cid::ContentId; use std::collections::HashMap; /// LRU cache entry diff --git a/crates/synor-storage/src/gateway/handler.rs b/crates/synor-storage/src/gateway/handler.rs index 29f55a0..7a137db 100644 --- a/crates/synor-storage/src/gateway/handler.rs +++ b/crates/synor-storage/src/gateway/handler.rs @@ -180,7 +180,12 @@ pub struct GatewayHandler { impl GatewayHandler { /// Create a new handler - pub fn new(rate_limit: u32, max_content_size: u64, enable_upload: bool, cors_origins: Vec) -> Self { + pub fn new( + rate_limit: u32, + max_content_size: u64, + enable_upload: bool, + cors_origins: Vec, + ) -> Self { Self { rate_limiter: RateLimiter::new(rate_limit, 60), max_content_size, @@ -200,8 +205,7 @@ impl GatewayHandler { (path, None) }; - let cid = ContentId::from_string(cid_part) - .map_err(|e| Error::InvalidCid(e.to_string()))?; + let cid = ContentId::from_string(cid_part).map_err(|e| Error::InvalidCid(e.to_string()))?; Ok((cid, subpath)) } @@ -223,7 +227,10 @@ impl GatewayHandler { headers.insert("Access-Control-Allow-Origin".to_string(), "*".to_string()); } else if let Some(origin) = origin { if self.cors_origins.iter().any(|o| o == origin) { - headers.insert("Access-Control-Allow-Origin".to_string(), origin.to_string()); + headers.insert( + "Access-Control-Allow-Origin".to_string(), + origin.to_string(), + ); } } @@ -279,7 +286,9 @@ impl GatewayHandler { }; // Check format parameter for CAR response - let _wants_car = request.query.get("format") + let _wants_car = request + .query + .get("format") .map(|f| f == "car") .unwrap_or(false); @@ -287,7 +296,7 @@ impl GatewayHandler { // For now, return a placeholder HttpResponse::ok( format!("Content for CID: {}", cid.to_string_repr()).into_bytes(), - "text/plain" + "text/plain", ) } @@ -300,7 +309,9 @@ impl GatewayHandler { // Return empty response with headers let mut response = HttpResponse::ok(vec![], "application/octet-stream"); - response.headers.insert("X-Content-CID".to_string(), cid.to_string_repr()); + response + .headers + .insert("X-Content-CID".to_string(), cid.to_string_repr()); response } @@ -349,7 +360,9 @@ impl GatewayHandler { headers: self.cors_headers(origin), body: vec![], }; - response.headers.insert("Content-Length".to_string(), "0".to_string()); + response + .headers + .insert("Content-Length".to_string(), "0".to_string()); response } } diff --git a/crates/synor-storage/src/gateway/mod.rs b/crates/synor-storage/src/gateway/mod.rs index 893a0bd..b0995c0 100644 --- a/crates/synor-storage/src/gateway/mod.rs +++ b/crates/synor-storage/src/gateway/mod.rs @@ -32,13 +32,13 @@ //! - `Cache-Control: public, max-age=31536000, immutable` //! - `ETag: "synor1abc...xyz"` (CID as ETag) +mod cache; mod handler; mod resolver; -mod cache; +pub use cache::GatewayCache; pub use handler::GatewayHandler; pub use resolver::ContentResolver; -pub use cache::GatewayCache; use crate::cid::ContentId; use crate::error::{Error, Result}; @@ -114,8 +114,8 @@ impl Default for CdnConfig { fn default() -> Self { Self { enabled: true, - immutable_max_age: 31536000, // 1 year - mutable_max_age: 300, // 5 minutes + immutable_max_age: 31536000, // 1 year + mutable_max_age: 300, // 5 minutes stale_while_revalidate: 86400, // 1 day provider: CdnProvider::Generic, } @@ -199,10 +199,7 @@ pub fn cdn_cache_headers(config: &CdnConfig, is_immutable: bool) -> HashMap HashMap { @@ -419,11 +421,17 @@ impl GatewayResponse { let car_data = car.encode(); let mut headers = HashMap::new(); - headers.insert("Content-Type".to_string(), "application/vnd.ipld.car".to_string()); + headers.insert( + "Content-Type".to_string(), + "application/vnd.ipld.car".to_string(), + ); headers.insert("Content-Length".to_string(), car_data.len().to_string()); headers.insert("X-Ipfs-Roots".to_string(), cid.to_string_repr()); headers.insert("X-Content-Type-Options".to_string(), "nosniff".to_string()); - headers.insert("Cache-Control".to_string(), "public, max-age=31536000, immutable".to_string()); + headers.insert( + "Cache-Control".to_string(), + "public, max-age=31536000, immutable".to_string(), + ); Self { cid, diff --git a/crates/synor-storage/src/gateway/resolver.rs b/crates/synor-storage/src/gateway/resolver.rs index bd90834..74b2ae0 100644 --- a/crates/synor-storage/src/gateway/resolver.rs +++ b/crates/synor-storage/src/gateway/resolver.rs @@ -44,8 +44,7 @@ pub struct ContentResolver { } /// Node health tracking -#[derive(Debug, Clone)] -#[derive(Default)] +#[derive(Debug, Clone, Default)] struct NodeHealth { /// Successful requests successes: u64, @@ -57,7 +56,6 @@ struct NodeHealth { last_check: u64, } - impl ContentResolver { /// Create a new resolver with node list pub fn new(nodes: Vec) -> Self { @@ -127,7 +125,9 @@ impl ContentResolver { if chunk_index < metadata.chunk_count - 1 && chunk_data.len() != metadata.chunk_size { return Err(Error::ReassemblyFailed(format!( "Chunk {} size {} doesn't match expected {}", - chunk_index, chunk_data.len(), metadata.chunk_size + chunk_index, + chunk_data.len(), + metadata.chunk_size ))); } @@ -181,7 +181,8 @@ impl ContentResolver { // Update health stats let latency_ms = start.elapsed().as_millis() as u32; - self.update_health(provider, result.is_ok(), latency_ms).await; + self.update_health(provider, result.is_ok(), latency_ms) + .await; result } @@ -194,7 +195,11 @@ impl ContentResolver { } /// Fetch content metadata - async fn fetch_metadata(&self, cid: &ContentId, providers: &[String]) -> Result { + async fn fetch_metadata( + &self, + cid: &ContentId, + providers: &[String], + ) -> Result { for provider in providers { match self.query_metadata(provider, cid).await { Ok(metadata) => return Ok(metadata), @@ -228,7 +233,10 @@ impl ContentResolver { ) -> Result> { // Try to fetch from multiple providers for provider in providers { - match self.fetch_chunk_from_provider(provider, cid, chunk_index).await { + match self + .fetch_chunk_from_provider(provider, cid, chunk_index) + .await + { Ok(data) => return Ok(data), Err(_) => continue, } @@ -256,7 +264,8 @@ impl ContentResolver { // Update health stats let latency_ms = start.elapsed().as_millis() as u32; - self.update_health(provider, result.is_ok(), latency_ms).await; + self.update_health(provider, result.is_ok(), latency_ms) + .await; result } diff --git a/crates/synor-storage/src/lib.rs b/crates/synor-storage/src/lib.rs index 68994a0..904de15 100644 --- a/crates/synor-storage/src/lib.rs +++ b/crates/synor-storage/src/lib.rs @@ -25,12 +25,12 @@ //! let retrieved = client.retrieve(&cid).await?; //! ``` -pub mod cid; pub mod chunker; -pub mod erasure; -pub mod proof; +pub mod cid; pub mod deal; +pub mod erasure; pub mod error; +pub mod proof; // Database layer for blockchain state pub mod cf; @@ -49,23 +49,23 @@ pub mod node; // Gateway module - HTTP access to stored content pub mod gateway; -pub use cid::ContentId; pub use chunker::{Chunk, Chunker}; +pub use cid::ContentId; pub use error::{Error, Result}; -pub use node::{NodeConfig, StorageNode, NodeState, NodeStats}; -pub use gateway::{Gateway, GatewayConfig, GatewayStats, CdnConfig, CdnProvider, SubdomainRoute}; +pub use gateway::{CdnConfig, CdnProvider, Gateway, GatewayConfig, GatewayStats, SubdomainRoute}; +pub use node::{NodeConfig, NodeState, NodeStats, StorageNode}; // CAR exports -pub use car::{CarFile, CarBlock, CarBuilder, CarHeader, TrustlessResponse}; +pub use car::{CarBlock, CarBuilder, CarFile, CarHeader, TrustlessResponse}; // Pinning exports pub use pinning::{ - PinManager, PinManagerConfig, PinRecord, PinRequest, PinSummary, PinStatus, - RedundancyLevel, Region, StorageNode as PinStorageNode, + PinManager, PinManagerConfig, PinRecord, PinRequest, PinStatus, PinSummary, RedundancyLevel, + Region, StorageNode as PinStorageNode, }; // Database exports for blockchain state storage -pub use db::{Database, DatabaseConfig, DbError, keys}; +pub use db::{keys, Database, DatabaseConfig, DbError}; pub use stores::{ BlockBody, BlockStore, ChainState, ContractStateStore, ContractStore, GhostdagStore, HeaderStore, MetadataStore, RelationsStore, StoredContract, StoredGhostdagData, diff --git a/crates/synor-storage/src/node/mod.rs b/crates/synor-storage/src/node/mod.rs index 4a1dced..16cf6ee 100644 --- a/crates/synor-storage/src/node/mod.rs +++ b/crates/synor-storage/src/node/mod.rs @@ -6,18 +6,18 @@ //! - Participate in P2P network for data distribution //! - Earn rewards for successful storage -mod store; mod network; mod prover; +mod store; -pub use store::ChunkStore; pub use network::StorageNetwork; pub use prover::ProofSubmitter; +pub use store::ChunkStore; use crate::deal::{StorageDeal, StorageOffer}; use crate::erasure::Shard; -use crate::proof::{Challenge, StorageProof}; use crate::error::{Error, Result}; +use crate::proof::{Challenge, StorageProof}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::RwLock; @@ -198,7 +198,9 @@ impl StorageNode { // Update available capacity in offer let mut offer = self.offer.write().await; - offer.available_capacity = offer.available_capacity.saturating_sub(shard.data.len() as u64); + offer.available_capacity = offer + .available_capacity + .saturating_sub(shard.data.len() as u64); Ok(()) } @@ -238,11 +240,18 @@ impl StorageNode { .ok_or_else(|| Error::NotFound("CID not stored".to_string()))?; // Get the challenged shard/chunk - let shard = self.chunk_store.get_shard(&deal.deal_id, challenge.chunk_index as u8).await?; + let shard = self + .chunk_store + .get_shard(&deal.deal_id, challenge.chunk_index as u8) + .await?; // Build Merkle proof for the requested byte range - let proof_data = shard.data - .get(challenge.byte_offset as usize..(challenge.byte_offset + challenge.byte_length) as usize) + let proof_data = shard + .data + .get( + challenge.byte_offset as usize + ..(challenge.byte_offset + challenge.byte_length) as usize, + ) .ok_or_else(|| Error::ProofVerificationFailed("Byte range out of bounds".to_string()))? .to_vec(); diff --git a/crates/synor-storage/src/node/network.rs b/crates/synor-storage/src/node/network.rs index 2e0e6e7..17374b7 100644 --- a/crates/synor-storage/src/node/network.rs +++ b/crates/synor-storage/src/node/network.rs @@ -29,10 +29,7 @@ pub struct PeerInfo { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum NetworkMessage { /// Request to retrieve a shard - GetShard { - deal_id: [u8; 32], - shard_index: u8, - }, + GetShard { deal_id: [u8; 32], shard_index: u8 }, /// Response with shard data ShardResponse { deal_id: [u8; 32], @@ -47,9 +44,7 @@ pub enum NetworkMessage { regions: Vec, }, /// Request to find providers for a CID - FindProviders { - cid: ContentId, - }, + FindProviders { cid: ContentId }, /// Response with provider list ProvidersFound { cid: ContentId, @@ -183,13 +178,16 @@ impl StorageNetwork { _deal_id: [u8; 32], _shard_index: u8, ) -> Result> { - let _peer = self.peers.get(peer_id).ok_or_else(|| { - Error::Network(format!("Peer not found: {:?}", hex::encode(peer_id))) - })?; + let _peer = self + .peers + .get(peer_id) + .ok_or_else(|| Error::Network(format!("Peer not found: {:?}", hex::encode(peer_id))))?; // TODO: Send GetShard message via libp2p // For now, return error as network not fully implemented - Err(Error::Network("Network request not implemented".to_string())) + Err(Error::Network( + "Network request not implemented".to_string(), + )) } /// Broadcast a deal announcement diff --git a/crates/synor-storage/src/node/prover.rs b/crates/synor-storage/src/node/prover.rs index 2d50fa0..b6b62b0 100644 --- a/crates/synor-storage/src/node/prover.rs +++ b/crates/synor-storage/src/node/prover.rs @@ -3,8 +3,8 @@ //! Monitors L1 for challenges and submits proofs. //! Failed proofs result in slashing of provider stake. -use crate::proof::{Challenge, StorageProof}; use crate::error::{Error, Result}; +use crate::proof::{Challenge, StorageProof}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -153,9 +153,10 @@ impl ProofSubmitter { pub fn set_proof(&mut self, challenge: &Challenge, proof: StorageProof) -> Result<()> { let challenge_id = self.challenge_id(challenge); - let tracked = self.challenges.get_mut(&challenge_id).ok_or_else(|| { - Error::Internal("Challenge not found".to_string()) - })?; + let tracked = self + .challenges + .get_mut(&challenge_id) + .ok_or_else(|| Error::Internal("Challenge not found".to_string()))?; tracked.proof = Some(proof); tracked.status = ChallengeStatus::ProofReady; @@ -167,9 +168,10 @@ impl ProofSubmitter { pub async fn submit_proof(&mut self, challenge: &Challenge) -> Result<[u8; 32]> { let challenge_id = self.challenge_id(challenge); - let tracked = self.challenges.get_mut(&challenge_id).ok_or_else(|| { - Error::Internal("Challenge not found".to_string()) - })?; + let tracked = self + .challenges + .get_mut(&challenge_id) + .ok_or_else(|| Error::Internal("Challenge not found".to_string()))?; if tracked.proof.is_none() { return Err(Error::Internal("Proof not ready".to_string())); @@ -219,11 +221,12 @@ impl ProofSubmitter { for (id, tracked) in &mut self.challenges { if (tracked.status == ChallengeStatus::Pending || tracked.status == ChallengeStatus::ProofReady) - && current_block >= tracked.deadline_block { - tracked.status = ChallengeStatus::Expired; - self.stats.challenges_expired += 1; - expired.push(*id); - } + && current_block >= tracked.deadline_block + { + tracked.status = ChallengeStatus::Expired; + self.stats.challenges_expired += 1; + expired.push(*id); + } } expired @@ -264,13 +267,11 @@ impl ProofSubmitter { /// Clean up old completed/failed challenges pub fn cleanup(&mut self, max_age_blocks: u64, current_block: u64) { - self.challenges.retain(|_, tracked| { - match tracked.status { - ChallengeStatus::Verified | ChallengeStatus::Failed | ChallengeStatus::Expired => { - current_block - tracked.received_block < max_age_blocks - } - _ => true, + self.challenges.retain(|_, tracked| match tracked.status { + ChallengeStatus::Verified | ChallengeStatus::Failed | ChallengeStatus::Expired => { + current_block - tracked.received_block < max_age_blocks } + _ => true, }); } } diff --git a/crates/synor-storage/src/node/store.rs b/crates/synor-storage/src/node/store.rs index 26fbcbc..f8cf17b 100644 --- a/crates/synor-storage/src/node/store.rs +++ b/crates/synor-storage/src/node/store.rs @@ -75,17 +75,17 @@ impl ChunkStore { fs::rename(&temp_path, &shard_path).await?; // Update used capacity - self.used_bytes.fetch_add( - encoded.len() as u64, - std::sync::atomic::Ordering::Relaxed, - ); + self.used_bytes + .fetch_add(encoded.len() as u64, std::sync::atomic::Ordering::Relaxed); Ok(()) } /// Retrieve a shard pub async fn get_shard(&self, deal_id: &[u8; 32], shard_index: u8) -> Result { - let shard_path = self.deal_dir(deal_id).join(format!("shard_{:03}.bin", shard_index)); + let shard_path = self + .deal_dir(deal_id) + .join(format!("shard_{:03}.bin", shard_index)); if !shard_path.exists() { return Err(Error::NotFound(format!( @@ -114,7 +114,8 @@ impl ChunkStore { fs::remove_dir_all(&deal_dir).await?; // Update used capacity - self.used_bytes.fetch_sub(size, std::sync::atomic::Ordering::Relaxed); + self.used_bytes + .fetch_sub(size, std::sync::atomic::Ordering::Relaxed); } Ok(()) @@ -137,7 +138,10 @@ impl ChunkStore { if name_str.starts_with("shard_") && name_str.ends_with(".bin") { // Parse index from "shard_XXX.bin" - if let Some(idx_str) = name_str.strip_prefix("shard_").and_then(|s| s.strip_suffix(".bin")) { + if let Some(idx_str) = name_str + .strip_prefix("shard_") + .and_then(|s| s.strip_suffix(".bin")) + { if let Ok(idx) = idx_str.parse::() { indices.push(idx); } diff --git a/crates/synor-storage/src/pinning.rs b/crates/synor-storage/src/pinning.rs index 2fe1682..4e84b5b 100644 --- a/crates/synor-storage/src/pinning.rs +++ b/crates/synor-storage/src/pinning.rs @@ -78,8 +78,7 @@ impl Region { } /// Redundancy level for pinning -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[derive(Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum RedundancyLevel { /// Single copy (no redundancy) None, @@ -119,7 +118,6 @@ impl RedundancyLevel { } } - /// Storage node information #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StorageNode { @@ -343,8 +341,8 @@ impl Default for PinManagerConfig { fn default() -> Self { Self { default_redundancy: RedundancyLevel::Standard, - health_check_interval: 300, // 5 minutes - max_verification_age: 86400, // 24 hours + health_check_interval: 300, // 5 minutes + max_verification_age: 86400, // 24 hours auto_repin: true, max_concurrent_pins: 100, } @@ -433,7 +431,12 @@ impl PinManager { } /// Pin content with specified redundancy - pub async fn pin(&self, cid: &ContentId, size: u64, redundancy: RedundancyLevel) -> Result { + pub async fn pin( + &self, + cid: &ContentId, + size: u64, + redundancy: RedundancyLevel, + ) -> Result { let required_copies = redundancy.min_copies(); let required_regions = redundancy.min_regions(); @@ -457,7 +460,10 @@ impl PinManager { stats.total_pins += 1; stats.active_pins += 1; stats.total_bytes += size; - *stats.pins_by_region.entry(node.region.code().to_string()).or_insert(0) += 1; + *stats + .pins_by_region + .entry(node.region.code().to_string()) + .or_insert(0) += 1; } } @@ -467,9 +473,11 @@ impl PinManager { all_pins.insert(cid.to_string_repr(), pins.clone()); } - let healthy_pins = pins.iter().filter(|p| p.status == PinStatus::Pinned).count(); - let redundancy_met = healthy_pins >= required_copies - && regions.len() >= required_regions; + let healthy_pins = pins + .iter() + .filter(|p| p.status == PinStatus::Pinned) + .count(); + let redundancy_met = healthy_pins >= required_copies && regions.len() >= required_regions; Ok(PinSummary { cid: cid.clone(), @@ -677,9 +685,21 @@ mod tests { let manager = PinManager::default_manager(); // Register nodes - manager.register_node(StorageNode::new("na-1", "https://na1.example.com", Region::NorthAmerica)); - manager.register_node(StorageNode::new("eu-1", "https://eu1.example.com", Region::Europe)); - manager.register_node(StorageNode::new("ap-1", "https://ap1.example.com", Region::AsiaPacific)); + manager.register_node(StorageNode::new( + "na-1", + "https://na1.example.com", + Region::NorthAmerica, + )); + manager.register_node(StorageNode::new( + "eu-1", + "https://eu1.example.com", + Region::Europe, + )); + manager.register_node(StorageNode::new( + "ap-1", + "https://ap1.example.com", + Region::AsiaPacific, + )); let healthy = manager.healthy_nodes(); assert_eq!(healthy.len(), 3); diff --git a/crates/synor-storage/src/proof.rs b/crates/synor-storage/src/proof.rs index ee703fa..1acf0a7 100644 --- a/crates/synor-storage/src/proof.rs +++ b/crates/synor-storage/src/proof.rs @@ -37,7 +37,7 @@ impl Challenge { // Use block hash to deterministically select chunk and range let chunk_seed = u64::from_le_bytes(block_hash[0..8].try_into().unwrap()); let chunk_index = (chunk_seed % total_chunks as u64) as u32; - + let offset_seed = u64::from_le_bytes(block_hash[8..16].try_into().unwrap()); let byte_offset = offset_seed % (chunk_size as u64 / 2); let byte_length = 1024.min(chunk_size as u64 - byte_offset); // Max 1KB proof @@ -145,7 +145,7 @@ impl MerkleTree { while level_size > 1 { let next_level_size = level_size.div_ceil(2); - + for i in 0..next_level_size { let left_idx = level_start + i * 2; let right_idx = left_idx + 1; @@ -187,11 +187,7 @@ impl MerkleTree { let mut level_size = self.leaf_count; while level_size > 1 { - let sibling_idx = if idx % 2 == 0 { - idx + 1 - } else { - idx - 1 - }; + let sibling_idx = if idx % 2 == 0 { idx + 1 } else { idx - 1 }; if sibling_idx < level_size { proof.push(self.nodes[level_start + sibling_idx]); @@ -216,24 +212,22 @@ mod tests { fn test_merkle_tree() { let data = b"AAAABBBBCCCCDDDD"; let tree = build_merkle_tree(data, 4); - + assert!(!tree.nodes.is_empty()); assert_eq!(tree.leaf_count, 4); } #[test] fn test_merkle_proof() { - let leaves: Vec<[u8; 32]> = (0..4) - .map(|i| *blake3::hash(&[i]).as_bytes()) - .collect(); - + let leaves: Vec<[u8; 32]> = (0..4).map(|i| *blake3::hash(&[i]).as_bytes()).collect(); + let tree = MerkleTree::from_leaves(leaves.clone()); let root = tree.root(); // Verify proof for each leaf for (i, leaf) in leaves.iter().enumerate() { let proof = tree.proof(i); - + // Verify by walking up let mut current = *leaf; for (j, sibling) in proof.iter().enumerate() { @@ -247,7 +241,7 @@ mod tests { } current = *blake3::hash(&combined).as_bytes(); } - + assert_eq!(current, root); } } @@ -256,9 +250,9 @@ mod tests { fn test_challenge_generation() { let block_hash = [1u8; 32]; let cid = ContentId::from_content(b"test"); - + let challenge = Challenge::generate(block_hash, 1, cid, 100, 1024); - + assert!(challenge.chunk_index < 100); assert!(challenge.byte_length <= 1024); } diff --git a/crates/synor-storage/src/stores.rs b/crates/synor-storage/src/stores.rs index db4f0bb..e3b24d7 100644 --- a/crates/synor-storage/src/stores.rs +++ b/crates/synor-storage/src/stores.rs @@ -2,7 +2,7 @@ //! //! Each store provides typed access to a specific category of blockchain data. -use crate::{cf, db::Database, db::keys, DbError}; +use crate::{cf, db::keys, db::Database, DbError}; use borsh::{BorshDeserialize, BorshSerialize}; use std::sync::Arc; use synor_types::{BlockHeader, BlockId, Hash256, Transaction, TransactionId}; diff --git a/crates/synor-verifier/src/ast.rs b/crates/synor-verifier/src/ast.rs index 722fe44..d1bb251 100644 --- a/crates/synor-verifier/src/ast.rs +++ b/crates/synor-verifier/src/ast.rs @@ -54,10 +54,7 @@ pub struct Function { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Statement { /// Variable assignment. - Assign { - target: String, - value: Expression, - }, + Assign { target: String, value: Expression }, /// If-then-else. If { condition: Expression, @@ -218,7 +215,10 @@ impl BinaryOperator { pub fn is_logical(&self) -> bool { matches!( self, - BinaryOperator::And | BinaryOperator::Or | BinaryOperator::Implies | BinaryOperator::Iff + BinaryOperator::And + | BinaryOperator::Or + | BinaryOperator::Implies + | BinaryOperator::Iff ) } } diff --git a/crates/synor-verifier/src/checker.rs b/crates/synor-verifier/src/checker.rs index ecf395d..b047ec9 100644 --- a/crates/synor-verifier/src/checker.rs +++ b/crates/synor-verifier/src/checker.rs @@ -114,39 +114,21 @@ impl VulnerabilityType { VulnerabilityType::Reentrancy => { "External call followed by state change allows reentrancy attack" } - VulnerabilityType::IntegerOverflow => { - "Arithmetic operation may overflow or underflow" - } - VulnerabilityType::UncheckedCall => { - "External call return value not checked" - } - VulnerabilityType::AccessControl => { - "Missing or insufficient access control" - } - VulnerabilityType::FrontRunning => { - "Transaction ordering can be exploited" - } - VulnerabilityType::DoS => { - "Contract can be rendered unusable" - } + VulnerabilityType::IntegerOverflow => "Arithmetic operation may overflow or underflow", + VulnerabilityType::UncheckedCall => "External call return value not checked", + VulnerabilityType::AccessControl => "Missing or insufficient access control", + VulnerabilityType::FrontRunning => "Transaction ordering can be exploited", + VulnerabilityType::DoS => "Contract can be rendered unusable", VulnerabilityType::TimestampDependence => { "Reliance on block.timestamp which can be manipulated" } - VulnerabilityType::WeakRandomness => { - "Predictable random number generation" - } + VulnerabilityType::WeakRandomness => "Predictable random number generation", VulnerabilityType::UninitializedStorage => { "Storage variable used before initialization" } - VulnerabilityType::DelegatecallInjection => { - "Delegatecall to user-controlled address" - } - VulnerabilityType::SelfDestruct => { - "Contract can be destroyed by attacker" - } - VulnerabilityType::GasLimit => { - "Loop or operation may exceed gas limit" - } + VulnerabilityType::DelegatecallInjection => "Delegatecall to user-controlled address", + VulnerabilityType::SelfDestruct => "Contract can be destroyed by attacker", + VulnerabilityType::GasLimit => "Loop or operation may exceed gas limit", } } } @@ -297,9 +279,10 @@ impl PropertyChecker { ann: &Annotation, ) -> VerifierResult { // Get function signature - let _func = ctx.functions.get(&ann.function).ok_or_else(|| { - VerifierError::UnknownFunction(ann.function.clone()) - })?; + let _func = ctx + .functions + .get(&ann.function) + .ok_or_else(|| VerifierError::UnknownFunction(ann.function.clone()))?; let state = self.executor.create_initial_state(ctx)?; @@ -404,11 +387,7 @@ mod tests { #[test] fn test_check_result() { - let result = CheckResult::new( - "test_prop", - true, - ProofResult::Proven { time_ms: 100 }, - ); + let result = CheckResult::new("test_prop", true, ProofResult::Proven { time_ms: 100 }); assert!(result.is_verified()); assert_eq!(result.name, "test_prop"); } diff --git a/crates/synor-verifier/src/lib.rs b/crates/synor-verifier/src/lib.rs index 70eae18..4f671b7 100644 --- a/crates/synor-verifier/src/lib.rs +++ b/crates/synor-verifier/src/lib.rs @@ -260,11 +260,18 @@ impl VerificationReport { fn compute_status(&mut self) { let all_verified = self.results.iter().all(|(_, r)| r.is_verified()); - let no_vulns = self.vulnerabilities.iter().all(|v| v.severity < Severity::High); + let no_vulns = self + .vulnerabilities + .iter() + .all(|v| v.severity < Severity::High); self.status = if all_verified && no_vulns { VerificationStatus::Verified - } else if self.vulnerabilities.iter().any(|v| v.severity == Severity::Critical) { + } else if self + .vulnerabilities + .iter() + .any(|v| v.severity == Severity::Critical) + { VerificationStatus::Failed } else { VerificationStatus::PartiallyVerified diff --git a/crates/synor-verifier/src/parser.rs b/crates/synor-verifier/src/parser.rs index c5dfa54..ee3e6ff 100644 --- a/crates/synor-verifier/src/parser.rs +++ b/crates/synor-verifier/src/parser.rs @@ -468,7 +468,9 @@ impl SpecParser { /// Checks if a line declares a state variable. fn is_state_var_line(line: &str) -> bool { // Simple heuristic: type followed by visibility/name - let keywords = ["uint", "int", "bool", "address", "bytes", "string", "mapping"]; + let keywords = [ + "uint", "int", "bool", "address", "bytes", "string", "mapping", + ]; keywords.iter().any(|k| line.starts_with(k)) } @@ -578,7 +580,10 @@ mod tests { #[test] fn test_parse_type() { - assert_eq!(SpecParser::parse_type("uint256").unwrap(), VarType::Uint(256)); + assert_eq!( + SpecParser::parse_type("uint256").unwrap(), + VarType::Uint(256) + ); assert_eq!(SpecParser::parse_type("int128").unwrap(), VarType::Int(128)); assert_eq!(SpecParser::parse_type("bool").unwrap(), VarType::Bool); assert_eq!(SpecParser::parse_type("address").unwrap(), VarType::Address); diff --git a/crates/synor-verifier/src/prover.rs b/crates/synor-verifier/src/prover.rs index 7d831aa..a63b5d1 100644 --- a/crates/synor-verifier/src/prover.rs +++ b/crates/synor-verifier/src/prover.rs @@ -152,7 +152,9 @@ impl Prover { // Check if negation is unsatisfiable (property always holds) let negated = self.smt.negate(&property)?; - let result = self.smt.check_sat_with_constraints(&constraints, &negated)?; + let result = self + .smt + .check_sat_with_constraints(&constraints, &negated)?; let elapsed = start.elapsed().as_millis() as u64; @@ -205,7 +207,9 @@ impl Prover { // If pre AND NOT post is unsat, then pre => post let combined = self.smt.and(&pre, &neg_post)?; - let result = self.smt.check_sat_with_constraints(&constraints, &combined)?; + let result = self + .smt + .check_sat_with_constraints(&constraints, &combined)?; let elapsed = start.elapsed().as_millis() as u64; diff --git a/crates/synor-verifier/src/smt.rs b/crates/synor-verifier/src/smt.rs index 9bc1d25..294a989 100644 --- a/crates/synor-verifier/src/smt.rs +++ b/crates/synor-verifier/src/smt.rs @@ -58,7 +58,8 @@ impl SmtContext { /// Add a variable declaration pub fn declare(&mut self, name: &str, sort: &str) { - self.declarations.push(format!("(declare-fun {} () {})", name, sort)); + self.declarations + .push(format!("(declare-fun {} () {})", name, sort)); } /// Add an assertion @@ -200,7 +201,11 @@ impl SmtContext { Expression::Result => Ok("__result".to_string()), - Expression::Sum { var, range: _, body } => { + Expression::Sum { + var, + range: _, + body, + } => { // Sum is modeled as uninterpreted function let body_smt = self.expr_to_smt(body)?; Ok(format!("(sum_{} {})", self.sanitize_name(var), body_smt)) @@ -364,7 +369,11 @@ impl SmtContext { /// Creates conjunction of expressions. pub fn and(&self, a: &SmtExpr, b: &SmtExpr) -> VerifierResult { - Ok(SmtExpr::new(&format!("(and {} {})", a.as_str(), b.as_str()))) + Ok(SmtExpr::new(&format!( + "(and {} {})", + a.as_str(), + b.as_str() + ))) } /// Creates disjunction of expressions. diff --git a/crates/synor-verifier/src/symbolic.rs b/crates/synor-verifier/src/symbolic.rs index ca871b3..309d4c4 100644 --- a/crates/synor-verifier/src/symbolic.rs +++ b/crates/synor-verifier/src/symbolic.rs @@ -112,21 +112,11 @@ impl SymbolicValue { (Literal::Uint(a), BinaryOperator::Div, Literal::Uint(b)) if *b != 0 => { Some(Literal::Uint(a / b)) } - (Literal::Uint(a), BinaryOperator::Eq, Literal::Uint(b)) => { - Some(Literal::Bool(a == b)) - } - (Literal::Uint(a), BinaryOperator::Lt, Literal::Uint(b)) => { - Some(Literal::Bool(a < b)) - } - (Literal::Uint(a), BinaryOperator::Le, Literal::Uint(b)) => { - Some(Literal::Bool(a <= b)) - } - (Literal::Uint(a), BinaryOperator::Gt, Literal::Uint(b)) => { - Some(Literal::Bool(a > b)) - } - (Literal::Uint(a), BinaryOperator::Ge, Literal::Uint(b)) => { - Some(Literal::Bool(a >= b)) - } + (Literal::Uint(a), BinaryOperator::Eq, Literal::Uint(b)) => Some(Literal::Bool(a == b)), + (Literal::Uint(a), BinaryOperator::Lt, Literal::Uint(b)) => Some(Literal::Bool(a < b)), + (Literal::Uint(a), BinaryOperator::Le, Literal::Uint(b)) => Some(Literal::Bool(a <= b)), + (Literal::Uint(a), BinaryOperator::Gt, Literal::Uint(b)) => Some(Literal::Bool(a > b)), + (Literal::Uint(a), BinaryOperator::Ge, Literal::Uint(b)) => Some(Literal::Bool(a >= b)), (Literal::Bool(a), BinaryOperator::And, Literal::Bool(b)) => { Some(Literal::Bool(*a && *b)) } @@ -244,10 +234,7 @@ impl SymbolicExecutor { } /// Creates an initial symbolic state from contract context. - pub fn create_initial_state( - &self, - ctx: &VerificationContext, - ) -> VerifierResult { + pub fn create_initial_state(&self, ctx: &VerificationContext) -> VerifierResult { let mut state = SymbolicState::new(); // Create symbolic values for state variables @@ -257,10 +244,22 @@ impl SymbolicExecutor { } // Add msg.sender, msg.value, etc. - state.set("msg.sender", SymbolicValue::symbol("msg.sender", VarType::Address)); - state.set("msg.value", SymbolicValue::symbol("msg.value", VarType::Uint(256))); - state.set("block.timestamp", SymbolicValue::symbol("block.timestamp", VarType::Uint(256))); - state.set("block.number", SymbolicValue::symbol("block.number", VarType::Uint(256))); + state.set( + "msg.sender", + SymbolicValue::symbol("msg.sender", VarType::Address), + ); + state.set( + "msg.value", + SymbolicValue::symbol("msg.value", VarType::Uint(256)), + ); + state.set( + "block.timestamp", + SymbolicValue::symbol("block.timestamp", VarType::Uint(256)), + ); + state.set( + "block.number", + SymbolicValue::symbol("block.number", VarType::Uint(256)), + ); Ok(state) } diff --git a/crates/synor-vm/src/compression.rs b/crates/synor-vm/src/compression.rs index ff62f2f..7fef056 100644 --- a/crates/synor-vm/src/compression.rs +++ b/crates/synor-vm/src/compression.rs @@ -453,9 +453,7 @@ impl CompressedContractStore { /// Retrieves a contract. pub fn get(&self, id: &ContractId) -> Result, VmError> { let contracts = self.contracts.read(); - let compressed = contracts - .get(id) - .ok_or(VmError::ContractNotFound(*id))?; + let compressed = contracts.get(id).ok_or(VmError::ContractNotFound(*id))?; self.compressor.decompress(compressed) } diff --git a/crates/synor-vm/src/compute.rs b/crates/synor-vm/src/compute.rs index b1cc966..528cea1 100644 --- a/crates/synor-vm/src/compute.rs +++ b/crates/synor-vm/src/compute.rs @@ -11,7 +11,6 @@ use synor_compute::{ ComputeCluster, ComputeJob, JobId, NodeId, }; - /// Configuration for compute-accelerated VM execution. #[derive(Clone, Debug)] pub struct ComputeConfig { @@ -50,7 +49,11 @@ pub enum OffloadableOp { /// Storage proof verification. VerifyStorageProof { size: usize }, /// Custom compute (e.g., ZK proof generation). - Custom { name: String, flops: f64, memory: u64 }, + Custom { + name: String, + flops: f64, + memory: u64, + }, } impl OffloadableOp { @@ -59,9 +62,7 @@ impl OffloadableOp { match self { OffloadableOp::Memory { size } => (*size as u64) / 100, OffloadableOp::Hash { size, .. } => (*size as u64) / 50, - OffloadableOp::MerkleProof { depth, leaves } => { - (*depth as u64) * (*leaves as u64) * 10 - } + OffloadableOp::MerkleProof { depth, leaves } => (*depth as u64) * (*leaves as u64) * 10, OffloadableOp::VerifySignatures { count } => (*count as u64) * 5000, OffloadableOp::VerifyStorageProof { size } => (*size as u64) * 100, OffloadableOp::Custom { flops, .. } => (*flops as u64) / 1_000_000, @@ -75,8 +76,7 @@ impl OffloadableOp { } /// Result of compute offload. -#[derive(Clone, Debug)] -#[derive(Default)] +#[derive(Clone, Debug, Default)] pub struct ComputeResult { /// Whether the operation was offloaded. pub offloaded: bool, @@ -88,7 +88,6 @@ pub struct ComputeResult { pub execution_time_us: u64, } - /// Compute-aware execution context. #[cfg(feature = "compute")] pub struct ComputeContext { @@ -179,7 +178,7 @@ impl ComputeContext { ComputeResult { offloaded: true, processor_type: Some("cpu".to_string()), // Will be determined by scheduler - gas_savings: op.estimated_gas() / 2, // 50% gas savings for offloaded ops + gas_savings: op.estimated_gas() / 2, // 50% gas savings for offloaded ops execution_time_us: 0, } } diff --git a/crates/synor-vm/src/lib.rs b/crates/synor-vm/src/lib.rs index 42ceebd..8721535 100644 --- a/crates/synor-vm/src/lib.rs +++ b/crates/synor-vm/src/lib.rs @@ -48,28 +48,26 @@ pub mod speculation; pub mod storage; pub mod tiered; +pub use compression::{ + BytecodeCompressor, CompressedBytecode, CompressedContractStore, CompressionConfig, + CompressionLevel, CompressionStats, DeltaPatch, +}; pub use context::{CallContext, ExecutionContext}; pub use engine::{ContractModule, VmEngine, VmInstance}; pub use gas::{Gas, GasConfig, GasMeter}; pub use gas_estimator::{costs, GasEstimate, GasEstimator}; pub use host::HostFunctions; pub use scheduler::{ - AccessSet, ExecutionBatch, LockManager, ParallelExecutor, ParallelScheduler, - ScheduledResult, ScheduledTransaction, SchedulerConfig, SchedulerStats, TransactionBuilder, -}; -pub use storage::{ContractStorage, MemoryStorage, StorageKey, StorageValue}; -pub use tiered::{ - CompilationTier, CompilerStatistics, TieredCompiler, TieredConfig, TieredModule, -}; -pub use compression::{ - BytecodeCompressor, CompressedBytecode, CompressedContractStore, CompressionConfig, - CompressionLevel, CompressionStats, DeltaPatch, + AccessSet, ExecutionBatch, LockManager, ParallelExecutor, ParallelScheduler, ScheduledResult, + ScheduledTransaction, SchedulerConfig, SchedulerStats, TransactionBuilder, }; pub use speculation::{ CachedState, ContractCache, GlobalCacheStats, HotStateCache, PendingExecution, SpeculativeConfig, SpeculativeContext, SpeculativeExecutor, SpeculativeResult, SpeculativeStats, StateSnapshot, StateVersion, }; +pub use storage::{ContractStorage, MemoryStorage, StorageKey, StorageValue}; +pub use tiered::{CompilationTier, CompilerStatistics, TieredCompiler, TieredConfig, TieredModule}; use synor_types::{Address, Hash256}; diff --git a/crates/synor-vm/src/scheduler.rs b/crates/synor-vm/src/scheduler.rs index f5157c8..99ca0e8 100644 --- a/crates/synor-vm/src/scheduler.rs +++ b/crates/synor-vm/src/scheduler.rs @@ -223,7 +223,10 @@ impl ParallelScheduler { /// Uses a greedy algorithm: /// 1. Try to add each transaction to the first batch it doesn't conflict with /// 2. If no batch works, create a new batch - pub fn schedule(&mut self, transactions: Vec>) -> Vec> { + pub fn schedule( + &mut self, + transactions: Vec>, + ) -> Vec> { let mut batches: Vec> = Vec::new(); let tx_count = transactions.len(); diff --git a/crates/synor-vm/src/speculation.rs b/crates/synor-vm/src/speculation.rs index ca84fe1..f46c34f 100644 --- a/crates/synor-vm/src/speculation.rs +++ b/crates/synor-vm/src/speculation.rs @@ -288,12 +288,7 @@ impl HotStateCache { } /// Inserts a value into cache. - pub fn insert( - &self, - contract_id: ContractId, - key: StorageKey, - value: Option, - ) { + pub fn insert(&self, contract_id: ContractId, key: StorageKey, value: Option) { let version = self.advance_version(); let mut caches = self.caches.write(); @@ -304,9 +299,9 @@ impl HotStateCache { self.evict_coldest_contract(&mut caches); } - let cache = caches - .entry(contract_id) - .or_insert_with(|| ContractCache::new(contract_id, self.config.cache_size_per_contract)); + let cache = caches.entry(contract_id).or_insert_with(|| { + ContractCache::new(contract_id, self.config.cache_size_per_contract) + }); cache.insert(key, value, version); } @@ -320,9 +315,9 @@ impl HotStateCache { let version = self.advance_version(); let mut caches = self.caches.write(); - let cache = caches - .entry(contract_id) - .or_insert_with(|| ContractCache::new(contract_id, self.config.cache_size_per_contract)); + let cache = caches.entry(contract_id).or_insert_with(|| { + ContractCache::new(contract_id, self.config.cache_size_per_contract) + }); for (key, value) in changes { cache.update(key, value.clone(), version); @@ -582,7 +577,9 @@ impl SpeculativeExecutor { let idx = pending .iter() .position(|p| &p.tx_id == tx_id) - .ok_or_else(|| VmError::ExecutionError("Speculative execution not found".to_string()))?; + .ok_or_else(|| { + VmError::ExecutionError("Speculative execution not found".to_string()) + })?; let exec = pending.remove(idx).unwrap(); @@ -607,7 +604,9 @@ impl SpeculativeExecutor { let idx = pending .iter() .position(|p| &p.tx_id == tx_id) - .ok_or_else(|| VmError::ExecutionError("Speculative execution not found".to_string()))?; + .ok_or_else(|| { + VmError::ExecutionError("Speculative execution not found".to_string()) + })?; let exec = pending.remove(idx).unwrap(); @@ -617,7 +616,8 @@ impl SpeculativeExecutor { } // Invalidate newer cache entries. - self.cache.invalidate_older_than(exec.snapshot.version.next()); + self.cache + .invalidate_older_than(exec.snapshot.version.next()); // Update stats. { diff --git a/crates/synor-vm/src/tiered.rs b/crates/synor-vm/src/tiered.rs index bc3a66b..3d0487c 100644 --- a/crates/synor-vm/src/tiered.rs +++ b/crates/synor-vm/src/tiered.rs @@ -49,8 +49,8 @@ impl CompilationTier { pub fn threshold(&self) -> u64 { match self { CompilationTier::Cold => 0, - CompilationTier::Warm => 10, // After 10 executions - CompilationTier::Hot => 100, // After 100 executions + CompilationTier::Warm => 10, // After 10 executions + CompilationTier::Hot => 100, // After 100 executions } } } @@ -240,7 +240,11 @@ impl TieredCompiler { } /// Gets or compiles a module at the appropriate tier. - pub fn get_or_compile(&self, id: &ContractId, bytecode: &[u8]) -> Result, VmError> { + pub fn get_or_compile( + &self, + id: &ContractId, + bytecode: &[u8], + ) -> Result, VmError> { // Check hot cache first (most likely to be used) if let Some(module) = self.hot_cache.read().get(id) { self.total_cache_hits.fetch_add(1, Ordering::Relaxed); @@ -275,12 +279,15 @@ impl TieredCompiler { self.cold_cache.lock().put(*id, module.clone()); // Initialize stats - self.stats.write().insert(*id, ContractStats { - execution_count: 1, - tier: CompilationTier::Cold, - last_executed: Instant::now(), - ..Default::default() - }); + self.stats.write().insert( + *id, + ContractStats { + execution_count: 1, + tier: CompilationTier::Cold, + last_executed: Instant::now(), + ..Default::default() + }, + ); Ok(module) } @@ -302,8 +309,8 @@ impl TieredCompiler { let engine = self.engines.get(tier); let start = Instant::now(); - let module = Module::new(engine, bytecode) - .map_err(|e| VmError::InvalidBytecode(e.to_string()))?; + let module = + Module::new(engine, bytecode).map_err(|e| VmError::InvalidBytecode(e.to_string()))?; let compile_time = start.elapsed(); self.total_compilations.fetch_add(1, Ordering::Relaxed); @@ -400,10 +407,13 @@ impl TieredCompiler { let module = self.compile_at_tier(&id, &bytecode, CompilationTier::Warm)?; self.warm_cache.lock().put(id, module); self.bytecode_store.write().insert(id, bytecode); - self.stats.write().insert(id, ContractStats { - tier: CompilationTier::Warm, - ..Default::default() - }); + self.stats.write().insert( + id, + ContractStats { + tier: CompilationTier::Warm, + ..Default::default() + }, + ); count += 1; } Ok(count) @@ -433,14 +443,12 @@ impl TieredCompiler { pub fn statistics(&self) -> CompilerStatistics { let stats = self.stats.read(); - let (cold_count, warm_count, hot_count) = stats.values().fold( - (0, 0, 0), - |(c, w, h), s| match s.tier { + let (cold_count, warm_count, hot_count) = + stats.values().fold((0, 0, 0), |(c, w, h), s| match s.tier { CompilationTier::Cold => (c + 1, w, h), CompilationTier::Warm => (c, w + 1, h), CompilationTier::Hot => (c, w, h + 1), - }, - ); + }); CompilerStatistics { cold_contracts: cold_count, diff --git a/crates/synor-zk/src/bin/zk-sequencer.rs b/crates/synor-zk/src/bin/zk-sequencer.rs index 6090ab8..5e0923b 100644 --- a/crates/synor-zk/src/bin/zk-sequencer.rs +++ b/crates/synor-zk/src/bin/zk-sequencer.rs @@ -6,8 +6,8 @@ use std::env; use std::net::SocketAddr; use std::sync::Arc; -use synor_zk::rollup::{RollupConfig, RollupManager}; use synor_zk::proof::{ProofSystem, ProofSystemBackend}; +use synor_zk::rollup::{RollupConfig, RollupManager}; use synor_zk::state::StateTree; /// Sequencer configuration from environment/config file @@ -48,7 +48,7 @@ async fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::from_default_env() - .add_directive("synor_zk=info".parse()?) + .add_directive("synor_zk=info".parse()?), ) .init(); diff --git a/crates/synor-zk/src/circuit.rs b/crates/synor-zk/src/circuit.rs index 9eea198..d914b7b 100644 --- a/crates/synor-zk/src/circuit.rs +++ b/crates/synor-zk/src/circuit.rs @@ -10,9 +10,9 @@ //! - **WithdrawCircuit**: Validates withdrawals from L2 to L1 //! - **BatchCircuit**: Aggregates multiple transactions into single proof +use ark_bn254::Fr as ScalarField; use ark_ff::PrimeField; use ark_relations::r1cs::SynthesisError; -use ark_bn254::Fr as ScalarField; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -211,7 +211,8 @@ impl Circuit for TransferCircuit { // Private witnesses let sender_idx_var = cs.alloc_witness("sender_idx", ScalarField::from(self.sender_idx))?; - let recipient_idx_var = cs.alloc_witness("recipient_idx", ScalarField::from(self.recipient_idx))?; + let recipient_idx_var = + cs.alloc_witness("recipient_idx", ScalarField::from(self.recipient_idx))?; let amount_var = cs.alloc_witness("amount", ScalarField::from(self.amount))?; // Constraint 1: Amount > 0 @@ -302,7 +303,8 @@ impl Circuit for BatchCircuit { fn synthesize(&self, cs: &mut CS) -> Result<(), CircuitError> { // Public inputs: initial_root, final_root, batch_hash - let initial_root_var = cs.alloc_input("initial_root", field_from_hash(&self.initial_root.0))?; + let initial_root_var = + cs.alloc_input("initial_root", field_from_hash(&self.initial_root.0))?; let final_root_var = cs.alloc_input("final_root", field_from_hash(&self.final_root.0))?; // For each transfer, verify: @@ -675,8 +677,8 @@ mod tests { let new_root = StateRoot([1u8; 32]); let signature = vec![0xab, 0xcd, 0xef]; - let circuit = TransferCircuit::new(old_root, new_root, 0, 1, 100) - .with_signature(signature.clone()); + let circuit = + TransferCircuit::new(old_root, new_root, 0, 1, 100).with_signature(signature.clone()); assert_eq!(circuit.signature, signature); } @@ -696,40 +698,22 @@ mod tests { #[test] fn test_transfer_circuit_name() { - let circuit = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ); + let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100); assert_eq!(circuit.name(), "TransferCircuit"); } #[test] fn test_transfer_circuit_config_default() { - let circuit = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ); + let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100); let config = circuit.config(); assert_eq!(config.max_batch_size, crate::constants::MAX_BATCH_SIZE); } #[test] fn test_transfer_circuit_builder_chain() { - let circuit = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ) - .with_proofs(vec![[1u8; 32]], vec![[2u8; 32]]) - .with_signature(vec![1, 2, 3]); + let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100) + .with_proofs(vec![[1u8; 32]], vec![[2u8; 32]]) + .with_signature(vec![1, 2, 3]); assert_eq!(circuit.sender_proof.len(), 1); assert_eq!(circuit.recipient_proof.len(), 1); @@ -738,13 +722,8 @@ mod tests { #[test] fn test_transfer_circuit_clone() { - let circuit = TransferCircuit::new( - StateRoot([0xaa; 32]), - StateRoot([0xbb; 32]), - 5, - 10, - 500, - ); + let circuit = + TransferCircuit::new(StateRoot([0xaa; 32]), StateRoot([0xbb; 32]), 5, 10, 500); let cloned = circuit.clone(); assert_eq!(circuit.old_root, cloned.old_root); @@ -756,25 +735,14 @@ mod tests { #[test] fn test_transfer_circuit_large_amount() { - let circuit = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - u64::MAX, - ); + let circuit = + TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, u64::MAX); assert_eq!(circuit.amount, u64::MAX); } #[test] fn test_transfer_circuit_zero_amount() { - let circuit = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - 0, - ); + let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 0); assert_eq!(circuit.amount, 0); } @@ -798,13 +766,7 @@ mod tests { let mut batch = BatchCircuit::new(StateRoot([0u8; 32]), StateRoot([2u8; 32])); assert_eq!(batch.num_transfers(), 0); - let transfer = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ); + let transfer = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100); batch.add_transfer(transfer); assert_eq!(batch.num_transfers(), 1); } @@ -1149,13 +1111,7 @@ mod tests { #[test] fn test_transfer_circuit_trait_clone() { - let circuit = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ); + let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100); fn assert_clone(_: &T) {} assert_clone(&circuit); @@ -1196,14 +1152,8 @@ mod tests { #[test] fn test_empty_merkle_proofs() { - let circuit = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ) - .with_proofs(vec![], vec![]); + let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100) + .with_proofs(vec![], vec![]); assert!(circuit.sender_proof.is_empty()); assert!(circuit.recipient_proof.is_empty()); @@ -1211,14 +1161,8 @@ mod tests { #[test] fn test_empty_signature() { - let circuit = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ) - .with_signature(vec![]); + let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100) + .with_signature(vec![]); assert!(circuit.signature.is_empty()); } @@ -1226,14 +1170,8 @@ mod tests { #[test] fn test_large_merkle_proof() { let large_proof: Vec<[u8; 32]> = (0..32).map(|i| [i as u8; 32]).collect(); - let circuit = TransferCircuit::new( - StateRoot([0u8; 32]), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ) - .with_proofs(large_proof.clone(), large_proof.clone()); + let circuit = TransferCircuit::new(StateRoot([0u8; 32]), StateRoot([1u8; 32]), 0, 1, 100) + .with_proofs(large_proof.clone(), large_proof.clone()); assert_eq!(circuit.sender_proof.len(), 32); assert_eq!(circuit.recipient_proof.len(), 32); diff --git a/crates/synor-zk/src/lib.rs b/crates/synor-zk/src/lib.rs index 2f96d78..cd3069e 100644 --- a/crates/synor-zk/src/lib.rs +++ b/crates/synor-zk/src/lib.rs @@ -76,8 +76,8 @@ pub mod state; pub use circuit::{Circuit, CircuitConfig, ConstraintSystem}; pub use proof::{Proof, ProofSystem, VerificationKey}; -pub use rollup::{BatchTransaction, RollupBatch, RollupManager, RollupConfig}; -pub use state::{StateTree, StateRoot, Account, AccountState}; +pub use rollup::{BatchTransaction, RollupBatch, RollupConfig, RollupManager}; +pub use state::{Account, AccountState, StateRoot, StateTree}; /// Re-export common types pub use synor_types::Hash256; diff --git a/crates/synor-zk/src/proof.rs b/crates/synor-zk/src/proof.rs index 0ff8fa8..aea1b08 100644 --- a/crates/synor-zk/src/proof.rs +++ b/crates/synor-zk/src/proof.rs @@ -26,8 +26,7 @@ use thiserror::Error; use crate::circuit::{Circuit, CircuitError}; /// Proof system backend selection. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[derive(Default)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum ProofSystemBackend { /// Groth16 - smallest proofs (~200 bytes), requires per-circuit trusted setup #[default] @@ -38,7 +37,6 @@ pub enum ProofSystemBackend { Stark, } - /// Verification key for proof validation. #[derive(Clone)] pub struct VerificationKey { @@ -253,9 +251,12 @@ impl Proof { let data = bytes[5..5 + data_len].to_vec(); let input_offset = 5 + data_len; - let input_count = - u32::from_le_bytes([bytes[input_offset], bytes[input_offset + 1], bytes[input_offset + 2], bytes[input_offset + 3]]) - as usize; + let input_count = u32::from_le_bytes([ + bytes[input_offset], + bytes[input_offset + 1], + bytes[input_offset + 2], + bytes[input_offset + 3], + ]) as usize; let mut public_inputs = Vec::with_capacity(input_count); let mut offset = input_offset + 4; @@ -312,7 +313,10 @@ impl ProofSystem { /// /// This is the "trusted setup" phase for Groth16. /// For production, use a multi-party computation ceremony. - pub fn setup(&self, _circuit: &C) -> Result<(ProvingKey, VerificationKey), ProofError> { + pub fn setup( + &self, + _circuit: &C, + ) -> Result<(ProvingKey, VerificationKey), ProofError> { match self.backend { ProofSystemBackend::Groth16 => { // In a real implementation, we would: @@ -334,12 +338,12 @@ impl ProofSystem { }, )) } - ProofSystemBackend::Plonk => { - Err(ProofError::UnsupportedBackend("PLONK not yet implemented".into())) - } - ProofSystemBackend::Stark => { - Err(ProofError::UnsupportedBackend("STARK not yet implemented".into())) - } + ProofSystemBackend::Plonk => Err(ProofError::UnsupportedBackend( + "PLONK not yet implemented".into(), + )), + ProofSystemBackend::Stark => Err(ProofError::UnsupportedBackend( + "STARK not yet implemented".into(), + )), } } @@ -360,7 +364,10 @@ impl ProofSystem { public_inputs: Vec::new(), }) } - _ => Err(ProofError::UnsupportedBackend(format!("{:?}", self.backend))), + _ => Err(ProofError::UnsupportedBackend(format!( + "{:?}", + self.backend + ))), } } @@ -379,7 +386,10 @@ impl ProofSystem { // Placeholder: always return true for testing Ok(true) } - _ => Err(ProofError::UnsupportedBackend(format!("{:?}", self.backend))), + _ => Err(ProofError::UnsupportedBackend(format!( + "{:?}", + self.backend + ))), } } @@ -410,8 +420,8 @@ impl ProofSystem { /// Returns the expected proof size for a backend. pub fn proof_size(backend: ProofSystemBackend) -> usize { match backend { - ProofSystemBackend::Groth16 => 192, // ~200 bytes - ProofSystemBackend::Plonk => 512, // ~500 bytes + ProofSystemBackend::Groth16 => 192, // ~200 bytes + ProofSystemBackend::Plonk => 512, // ~500 bytes ProofSystemBackend::Stark => 50 * 1024, // ~50KB } } @@ -556,13 +566,7 @@ mod tests { #[test] fn test_proof_system_prove_groth16() { let ps = ProofSystem::groth16(); - let circuit = TransferCircuit::new( - StateRoot::zero(), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ); + let circuit = TransferCircuit::new(StateRoot::zero(), StateRoot([1u8; 32]), 0, 1, 100); let (pk, _) = ps.setup(&circuit).unwrap(); let proof = ps.prove(&circuit, &pk); @@ -573,13 +577,7 @@ mod tests { #[test] fn test_proof_system_verify_groth16() { let ps = ProofSystem::groth16(); - let circuit = TransferCircuit::new( - StateRoot::zero(), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ); + let circuit = TransferCircuit::new(StateRoot::zero(), StateRoot([1u8; 32]), 0, 1, 100); let (pk, vk) = ps.setup(&circuit).unwrap(); let proof = ps.prove(&circuit, &pk).unwrap(); @@ -591,13 +589,7 @@ mod tests { #[test] fn test_proof_system_prove_unsupported_backend() { let ps = ProofSystem::new(ProofSystemBackend::Plonk); - let circuit = TransferCircuit::new( - StateRoot::zero(), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ); + let circuit = TransferCircuit::new(StateRoot::zero(), StateRoot([1u8; 32]), 0, 1, 100); let pk = ProvingKey { backend: ProofSystemBackend::Plonk, data: vec![0u8; 100], @@ -1112,13 +1104,7 @@ mod tests { fn test_full_proof_workflow() { let ps = ProofSystem::groth16(); - let circuit = TransferCircuit::new( - StateRoot::zero(), - StateRoot([1u8; 32]), - 0, - 1, - 100, - ); + let circuit = TransferCircuit::new(StateRoot::zero(), StateRoot([1u8; 32]), 0, 1, 100); let (pk, vk) = ps.setup(&circuit).unwrap(); let proof = ps.prove(&circuit, &pk).unwrap(); diff --git a/crates/synor-zk/src/rollup/mod.rs b/crates/synor-zk/src/rollup/mod.rs index 619c06a..421cae9 100644 --- a/crates/synor-zk/src/rollup/mod.rs +++ b/crates/synor-zk/src/rollup/mod.rs @@ -157,20 +157,36 @@ impl BatchTransaction { pub fn hash(&self) -> [u8; 32] { let mut hasher = blake3::Hasher::new(); match self { - Self::Transfer { from, to, amount, nonce, .. } => { + Self::Transfer { + from, + to, + amount, + nonce, + .. + } => { hasher.update(b"transfer"); hasher.update(&from.to_le_bytes()); hasher.update(&to.to_le_bytes()); hasher.update(&amount.to_le_bytes()); hasher.update(&nonce.to_le_bytes()); } - Self::Deposit { to, amount, l1_tx_hash } => { + Self::Deposit { + to, + amount, + l1_tx_hash, + } => { hasher.update(b"deposit"); hasher.update(&to.to_le_bytes()); hasher.update(&amount.to_le_bytes()); hasher.update(l1_tx_hash); } - Self::Withdraw { from, l1_recipient, amount, nonce, .. } => { + Self::Withdraw { + from, + l1_recipient, + amount, + nonce, + .. + } => { hasher.update(b"withdraw"); hasher.update(&from.to_le_bytes()); hasher.update(l1_recipient); @@ -412,8 +428,7 @@ impl RollupManager { let batch_start = self.batch_start_time.read(); if let Some(start) = *batch_start { let pending = self.pending_txs.read().len(); - if pending >= self.config.min_batch_size - && start.elapsed() >= self.config.batch_timeout + if pending >= self.config.min_batch_size && start.elapsed() >= self.config.batch_timeout { return true; } @@ -464,7 +479,9 @@ impl RollupManager { // Apply transactions to state tree for tx in &batch.transactions { match tx { - BatchTransaction::Transfer { from, to, amount, .. } => { + BatchTransaction::Transfer { + from, to, amount, .. + } => { self.state_tree.apply_transfer(*from, *to, *amount)?; } BatchTransaction::Deposit { to, amount, .. } => { @@ -499,13 +516,20 @@ impl RollupManager { /// Verifies a batch proof. pub fn verify_batch(&self, batch: &RollupBatch) -> Result { let proof = batch.proof.as_ref().ok_or(RollupError::NoProof)?; - let vk = self.verification_key.as_ref().ok_or(RollupError::NoVerificationKey)?; + let vk = self + .verification_key + .as_ref() + .ok_or(RollupError::NoVerificationKey)?; Ok(self.proof_system.verify(proof, vk)?) } /// Registers a new account. - pub fn register_account(&self, index: u64, pubkey_hash: [u8; 32]) -> Result { + pub fn register_account( + &self, + index: u64, + pubkey_hash: [u8; 32], + ) -> Result { let state = AccountState::new(pubkey_hash); Ok(self.state_tree.set_account(index, state)?) } @@ -639,7 +663,13 @@ mod tests { let tx = BatchTransaction::transfer(0, 1, 100); match tx { - BatchTransaction::Transfer { from, to, amount, nonce, signature } => { + BatchTransaction::Transfer { + from, + to, + amount, + nonce, + signature, + } => { assert_eq!(from, 0); assert_eq!(to, 1); assert_eq!(amount, 100); @@ -656,7 +686,11 @@ mod tests { let tx = BatchTransaction::deposit(5, 1000, l1_tx_hash); match tx { - BatchTransaction::Deposit { to, amount, l1_tx_hash: hash } => { + BatchTransaction::Deposit { + to, + amount, + l1_tx_hash: hash, + } => { assert_eq!(to, 5); assert_eq!(amount, 1000); assert_eq!(hash, l1_tx_hash); @@ -671,7 +705,13 @@ mod tests { let tx = BatchTransaction::withdraw(10, l1_recipient, 500); match tx { - BatchTransaction::Withdraw { from, l1_recipient: recipient, amount, nonce, signature } => { + BatchTransaction::Withdraw { + from, + l1_recipient: recipient, + amount, + nonce, + signature, + } => { assert_eq!(from, 10); assert_eq!(recipient, l1_recipient); assert_eq!(amount, 500); @@ -782,7 +822,9 @@ mod tests { .with_signature(vec![1, 2, 3]); match tx { - BatchTransaction::Transfer { nonce, signature, .. } => { + BatchTransaction::Transfer { + nonce, signature, .. + } => { assert_eq!(nonce, 5); assert_eq!(signature, vec![1, 2, 3]); } @@ -825,8 +867,12 @@ mod tests { #[test] fn test_rollup_batch_with_transactions() { let mut batch = RollupBatch::new(1, StateRoot([1u8; 32])); - batch.transactions.push(BatchTransaction::transfer(0, 1, 100)); - batch.transactions.push(BatchTransaction::transfer(1, 2, 50)); + batch + .transactions + .push(BatchTransaction::transfer(0, 1, 100)); + batch + .transactions + .push(BatchTransaction::transfer(1, 2, 50)); assert_eq!(batch.tx_count(), 2); } @@ -834,7 +880,9 @@ mod tests { #[test] fn test_rollup_batch_compute_hash() { let mut batch = RollupBatch::new(0, StateRoot::zero()); - batch.transactions.push(BatchTransaction::transfer(0, 1, 100)); + batch + .transactions + .push(BatchTransaction::transfer(0, 1, 100)); batch.post_state_root = StateRoot([1u8; 32]); batch.compute_hash(); @@ -848,7 +896,9 @@ mod tests { let hash1 = batch1.batch_hash; let mut batch2 = RollupBatch::new(0, StateRoot::zero()); - batch2.transactions.push(BatchTransaction::transfer(0, 1, 100)); + batch2 + .transactions + .push(BatchTransaction::transfer(0, 1, 100)); batch2.compute_hash(); let hash2 = batch2.batch_hash; @@ -861,7 +911,9 @@ mod tests { for i in 0..10 { assert_eq!(batch.tx_count(), i); - batch.transactions.push(BatchTransaction::transfer(0, 1, 100)); + batch + .transactions + .push(BatchTransaction::transfer(0, 1, 100)); } assert_eq!(batch.tx_count(), 10); @@ -885,7 +937,9 @@ mod tests { #[test] fn test_rollup_batch_clone() { let mut batch = RollupBatch::new(5, StateRoot([0xaa; 32])); - batch.transactions.push(BatchTransaction::transfer(0, 1, 100)); + batch + .transactions + .push(BatchTransaction::transfer(0, 1, 100)); batch.post_state_root = StateRoot([0xbb; 32]); batch.compute_hash(); diff --git a/crates/synor-zk/src/state.rs b/crates/synor-zk/src/state.rs index 3be3026..59ae5a9 100644 --- a/crates/synor-zk/src/state.rs +++ b/crates/synor-zk/src/state.rs @@ -309,11 +309,15 @@ impl StateTree { let tree: MerkleTree = MerkleTree::from_leaves(&padded_leaves); let proof = tree.proof(&[proof_index]); - Ok(proof.proof_hashes().iter().map(|h| { - let mut arr = [0u8; 32]; - arr.copy_from_slice(h); - arr - }).collect()) + Ok(proof + .proof_hashes() + .iter() + .map(|h| { + let mut arr = [0u8; 32]; + arr.copy_from_slice(h); + arr + }) + .collect()) } /// Verifies a Merkle proof. @@ -856,7 +860,10 @@ mod tests { .unwrap(); let result = tree.apply_transfer(0, 1, 200); - assert!(matches!(result, Err(StateError::InsufficientBalance { .. }))); + assert!(matches!( + result, + Err(StateError::InsufficientBalance { .. }) + )); } #[test] @@ -990,7 +997,10 @@ mod tests { .unwrap(); let result = tree.apply_withdrawal(0, 200); - assert!(matches!(result, Err(StateError::InsufficientBalance { .. }))); + assert!(matches!( + result, + Err(StateError::InsufficientBalance { .. }) + )); } #[test] @@ -1067,8 +1077,11 @@ mod tests { let tree = StateTree::with_default_depth(); for i in 0..8 { - tree.set_account(i, AccountState::with_balance([i as u8; 32], (i as u128) * 100)) - .unwrap(); + tree.set_account( + i, + AccountState::with_balance([i as u8; 32], (i as u128) * 100), + ) + .unwrap(); } for i in 0..8 { diff --git a/src/lib.rs b/src/lib.rs index 819f1a3..f17fe93 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,11 +6,11 @@ //! Note: Some crates are excluded from root package due to external //! dependencies (rocksdb, etc.) that require additional build config. -pub use synor_types; -pub use synor_mining; pub use synor_bridge; -pub use synor_crypto; pub use synor_consensus; +pub use synor_crypto; pub use synor_dag; +pub use synor_mining; pub use synor_rpc; +pub use synor_types; pub use synor_vm; diff --git a/tests/cross_crate_integration.rs b/tests/cross_crate_integration.rs index ad37c78..aaf8a6d 100644 --- a/tests/cross_crate_integration.rs +++ b/tests/cross_crate_integration.rs @@ -14,7 +14,7 @@ #![allow(unused_mut)] #![allow(unused_imports)] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; // ============================================================================= // MODULE 1: Types + Mining Integration @@ -22,37 +22,40 @@ use serde::{Serialize, Deserialize}; #[cfg(test)] mod types_mining_integration { - use synor_mining::{KHeavyHash, Target, MiningStats}; - use synor_types::{Hash256, Amount, Timestamp, BlueScore}; + use synor_mining::{KHeavyHash, MiningStats, Target}; + use synor_types::{Amount, BlueScore, Hash256, Timestamp}; /// Test that Hash256 from synor-types works correctly with Target comparison #[test] fn test_hash256_target_comparison() { // Create a target with 3 leading zero bytes let target = Target::from_bytes([ - 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, ]); // Hash with more leading zeros should meet target let easy_hash = Hash256::from_bytes([ - 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, 0x01, + 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, 0x01, ]); - assert!(target.is_met_by(&easy_hash), "Hash with more zeros should meet target"); + assert!( + target.is_met_by(&easy_hash), + "Hash with more zeros should meet target" + ); // Hash with fewer leading zeros should NOT meet target let hard_hash = Hash256::from_bytes([ - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, ]); - assert!(!target.is_met_by(&hard_hash), "Hash with fewer zeros should not meet target"); + assert!( + !target.is_met_by(&hard_hash), + "Hash with fewer zeros should not meet target" + ); } /// Test kHeavyHash produces valid Hash256 types @@ -91,10 +94,16 @@ mod types_mining_integration { // Verify the chain: header -> pre_hash -> final hash -> target check assert!(target.is_met_by(&pow.hash), "Found hash should meet target"); - assert!(pow.meets_target(&target), "PowHash.meets_target should agree"); + assert!( + pow.meets_target(&target), + "PowHash.meets_target should agree" + ); // Verify using the verify method - assert!(hasher.verify(header, pow.nonce, &target), "Verification should pass"); + assert!( + hasher.verify(header, pow.nonce, &target), + "Verification should pass" + ); } /// Test mining reward amount calculations @@ -104,10 +113,16 @@ mod types_mining_integration { let fee_total = Amount::from_sompi(1_000_000); // 0.01 SYNOR let total_reward = base_reward.checked_add(fee_total); - assert!(total_reward.is_some(), "Should add rewards without overflow"); + assert!( + total_reward.is_some(), + "Should add rewards without overflow" + ); let total = total_reward.unwrap(); - assert!(total.as_sompi() > base_reward.as_sompi(), "Total should exceed base reward"); + assert!( + total.as_sompi() > base_reward.as_sompi(), + "Total should exceed base reward" + ); assert_eq!(total.as_sompi(), 50 * 100_000_000 + 1_000_000); } @@ -125,7 +140,10 @@ mod types_mining_integration { // Timestamp should be within reasonable range (1 hour) let hour_ms = 3600 * 1000; - assert!(block_time.abs_diff(current_time) < hour_ms, "Timestamp should be recent"); + assert!( + block_time.abs_diff(current_time) < hour_ms, + "Timestamp should be recent" + ); } /// Test blue score progression during mining @@ -149,14 +167,16 @@ mod types_mining_integration { // Create harder target (more leading zeros required) let hard_target = Target::from_bytes([ - 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x01, 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, ]); let hard_difficulty = hard_target.to_difficulty(); - assert!(hard_difficulty > easy_difficulty, "Harder target should have higher difficulty"); + assert!( + hard_difficulty > easy_difficulty, + "Harder target should have higher difficulty" + ); } /// Test mining stats integration with amounts @@ -170,7 +190,10 @@ mod types_mining_integration { stats.record_block(2100); // 1.1 seconds later assert_eq!(stats.blocks_found, 2); - assert!((stats.hashrate - 1_000_000.0).abs() < 1.0, "Hashrate should be ~1 MH/s"); + assert!( + (stats.hashrate - 1_000_000.0).abs() < 1.0, + "Hashrate should be ~1 MH/s" + ); // Calculate mining income (hypothetical) let reward_per_block = Amount::from_synor(50); @@ -191,11 +214,17 @@ mod types_mining_integration { // Verify determinism let merkle_root2 = Hash256::merkle_root(&[tx1_hash, tx2_hash, tx3_hash]); - assert_eq!(merkle_root, merkle_root2, "Same transactions should produce same root"); + assert_eq!( + merkle_root, merkle_root2, + "Same transactions should produce same root" + ); // Different order should produce different root let merkle_root_reordered = Hash256::merkle_root(&[tx2_hash, tx1_hash, tx3_hash]); - assert_ne!(merkle_root, merkle_root_reordered, "Order should affect merkle root"); + assert_ne!( + merkle_root, merkle_root_reordered, + "Order should affect merkle root" + ); } } @@ -206,7 +235,7 @@ mod types_mining_integration { #[cfg(test)] mod types_rpc_integration { use super::*; - use synor_types::{Hash256, Amount, Address, Network, BlueScore, Timestamp}; + use synor_types::{Address, Amount, BlueScore, Hash256, Network, Timestamp}; /// Test Amount serialization for RPC responses #[test] @@ -240,7 +269,10 @@ mod types_rpc_integration { // JSON should contain hex string let json = serde_json::to_string(&hash).unwrap(); - assert!(json.contains(&hash.to_hex()), "JSON should contain hex representation"); + assert!( + json.contains(&hash.to_hex()), + "JSON should contain hex representation" + ); // Should roundtrip correctly let parsed: Hash256 = serde_json::from_str(&json).unwrap(); @@ -300,7 +332,10 @@ mod types_rpc_integration { }; let json = serde_json::to_string(&output).unwrap(); - assert!(json.contains("tsynor1"), "Testnet address should have tsynor prefix"); + assert!( + json.contains("tsynor1"), + "Testnet address should have tsynor prefix" + ); let parsed: MockTxOutput = serde_json::from_str(&json).unwrap(); assert_eq!(parsed.value.as_synor(), 50); @@ -340,7 +375,12 @@ mod types_rpc_integration { for (network, expected_prefix) in networks.iter().zip(prefixes.iter()) { let addr = Address::from_ed25519_pubkey(*network, &pubkey); let json = serde_json::to_string(&addr).unwrap(); - assert!(json.contains(expected_prefix), "Network {:?} should have prefix {}", network, expected_prefix); + assert!( + json.contains(expected_prefix), + "Network {:?} should have prefix {}", + network, + expected_prefix + ); } } @@ -367,8 +407,8 @@ mod types_rpc_integration { #[cfg(test)] mod bridge_types_integration { - use synor_bridge::{ChainType, AssetId, BridgeAddress}; - use synor_types::{Network, Address}; + use synor_bridge::{AssetId, BridgeAddress, ChainType}; + use synor_types::{Address, Network}; /// Test BridgeAddress creation from Synor address bytes #[test] @@ -379,7 +419,10 @@ mod bridge_types_integration { // Create BridgeAddress from the payload bytes let bridge_addr = BridgeAddress::from_synor(*synor_addr.payload()); - assert!(bridge_addr.as_synor().is_some(), "Should convert back to 32-byte address"); + assert!( + bridge_addr.as_synor().is_some(), + "Should convert back to 32-byte address" + ); assert_eq!(bridge_addr.chain, ChainType::Synor); } @@ -403,7 +446,11 @@ mod bridge_types_integration { let wrapped = AssetId::wrapped(ð_asset); assert!(wrapped.is_wrapped(), "Should be marked as wrapped"); - assert_eq!(wrapped.chain, ChainType::Synor, "Wrapped assets live on Synor"); + assert_eq!( + wrapped.chain, + ChainType::Synor, + "Wrapped assets live on Synor" + ); assert_eq!(wrapped.symbol, "sETH", "Wrapped ETH should be sETH"); assert_eq!(wrapped.decimals, 18, "Should preserve decimals"); } @@ -430,12 +477,18 @@ mod bridge_types_integration { // Valid Ethereum address (20 bytes) let eth_addr = BridgeAddress::from_eth([0x42; 20]); assert!(eth_addr.as_eth().is_some()); - assert!(eth_addr.as_synor().is_none(), "ETH address should not be valid Synor"); + assert!( + eth_addr.as_synor().is_none(), + "ETH address should not be valid Synor" + ); // Valid Synor address (32 bytes) let synor_addr = BridgeAddress::from_synor([0x42; 32]); assert!(synor_addr.as_synor().is_some()); - assert!(synor_addr.as_eth().is_none(), "Synor address should not be valid ETH"); + assert!( + synor_addr.as_eth().is_none(), + "Synor address should not be valid ETH" + ); } /// Test native asset creation across chains @@ -455,11 +508,7 @@ mod bridge_types_integration { /// Test ERC-20 token asset creation #[test] fn test_erc20_asset_creation() { - let usdc = AssetId::erc20( - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "USDC", - 6 - ); + let usdc = AssetId::erc20("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "USDC", 6); assert_eq!(usdc.chain, ChainType::Ethereum); assert_eq!(usdc.symbol, "USDC"); @@ -496,12 +545,9 @@ mod bridge_types_integration { #[cfg(test)] mod mining_consensus_integration { - use synor_mining::{KHeavyHash, Target, MiningWork, WorkResult}; - use synor_consensus::{ - RewardCalculator, - COINBASE_MATURITY, TARGET_BLOCK_TIME_MS, - }; - use synor_types::{Hash256, Address, Network, Timestamp}; + use synor_consensus::{RewardCalculator, COINBASE_MATURITY, TARGET_BLOCK_TIME_MS}; + use synor_mining::{KHeavyHash, MiningWork, Target, WorkResult}; + use synor_types::{Address, Hash256, Network, Timestamp}; /// Test PoW produces valid consensus input #[test] @@ -532,14 +578,16 @@ mod mining_consensus_integration { // Higher difficulty targets should have more leading zeros let harder = Target::from_bytes([ - 0x00, 0x00, 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, + 0x00, 0x00, 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, ]); let harder_difficulty = harder.to_difficulty(); - assert!(harder_difficulty > difficulty, "More zeros = higher difficulty"); + assert!( + harder_difficulty > difficulty, + "More zeros = higher difficulty" + ); } /// Test block reward calculation @@ -553,8 +601,10 @@ mod mining_consensus_integration { // Rewards should decrease over time (due to chromatic halving) let reward_later = calculator.calculate_subsidy(10_000_000); - assert!(reward_later.as_sompi() <= reward_0.as_sompi(), - "Rewards should decrease over time"); + assert!( + reward_later.as_sompi() <= reward_0.as_sompi(), + "Rewards should decrease over time" + ); } /// Test coinbase maturity with mining @@ -620,7 +670,10 @@ mod mining_consensus_integration { /// Test block time target constant #[test] fn test_block_time_target() { - assert_eq!(TARGET_BLOCK_TIME_MS, 100, "Target should be 100ms for 10 BPS"); + assert_eq!( + TARGET_BLOCK_TIME_MS, 100, + "Target should be 100ms for 10 BPS" + ); let blocks_per_second = 1000 / TARGET_BLOCK_TIME_MS; assert_eq!(blocks_per_second, 10, "Should target 10 blocks per second"); @@ -648,7 +701,7 @@ mod mining_consensus_integration { #[cfg(test)] mod crypto_types_integration { - use synor_crypto::{Mnemonic, HybridKeypair}; + use synor_crypto::{HybridKeypair, Mnemonic}; use synor_types::{Hash256, Network}; /// Test signature verification for network messages @@ -663,8 +716,10 @@ mod crypto_types_integration { // Network node verifies signature let public_key = keypair.public_key(); - assert!(public_key.verify(message, &signature).is_ok(), - "Network message signature should verify"); + assert!( + public_key.verify(message, &signature).is_ok(), + "Network message signature should verify" + ); } /// Test address derivation for peer identity @@ -696,8 +751,14 @@ mod crypto_types_integration { let sig_bytes = signature.to_bytes(); // Hybrid signature is ~3.4KB (Ed25519: 64 bytes + Dilithium3: ~3300 bytes) - assert!(sig_bytes.len() > 3000, "Hybrid signature should be over 3KB"); - assert!(sig_bytes.len() < 5000, "Hybrid signature should be under 5KB"); + assert!( + sig_bytes.len() > 3000, + "Hybrid signature should be over 3KB" + ); + assert!( + sig_bytes.len() < 5000, + "Hybrid signature should be under 5KB" + ); } /// Test message hash signing @@ -711,7 +772,10 @@ mod crypto_types_integration { let hash = Hash256::blake3(data); let signature = keypair.sign(hash.as_bytes()); - assert!(keypair.public_key().verify(hash.as_bytes(), &signature).is_ok()); + assert!(keypair + .public_key() + .verify(hash.as_bytes(), &signature) + .is_ok()); } /// Test keypair generation from mnemonic @@ -758,7 +822,11 @@ mod crypto_types_integration { // Different passphrase = different keypair let addr1 = keypair_no_pass.address(Network::Mainnet); let addr2 = keypair_with_pass.address(Network::Mainnet); - assert_ne!(addr1.payload(), addr2.payload(), "Passphrase should change derived keys"); + assert_ne!( + addr1.payload(), + addr2.payload(), + "Passphrase should change derived keys" + ); } /// Test tampered message detection @@ -827,10 +895,7 @@ mod types_hashing_integration { let parent2 = Hash256::blake3(b"parent block 2"); // Combine hashes to create a DAG node identifier - let combined = Hash256::combine(&[ - parent1.as_bytes(), - parent2.as_bytes(), - ]); + let combined = Hash256::combine(&[parent1.as_bytes(), parent2.as_bytes()]); assert!(!combined.is_zero()); assert_ne!(combined, parent1); @@ -900,12 +965,11 @@ mod types_hashing_integration { #[cfg(test)] mod vm_contracts_integration { + use synor_types::{Address, Hash256, Network}; use synor_vm::{ - ContractId, ExecutionResult, ContractLog, StorageChange, - DeployParams, CallParams, VmError, GasMeter, - MAX_CONTRACT_SIZE, MAX_CALL_DEPTH, + CallParams, ContractId, ContractLog, DeployParams, ExecutionResult, GasMeter, + StorageChange, VmError, MAX_CALL_DEPTH, MAX_CONTRACT_SIZE, }; - use synor_types::{Hash256, Address, Network}; /// Test contract ID creation from hash #[test] @@ -962,13 +1026,11 @@ mod vm_contracts_integration { let result = ExecutionResult { return_data: vec![1, 2, 3], gas_used: 50_000, - logs: vec![ - ContractLog { - contract: contract_id, - topics: vec![Hash256::blake3(b"Transfer")], - data: vec![10, 20, 30], - } - ], + logs: vec![ContractLog { + contract: contract_id, + topics: vec![Hash256::blake3(b"Transfer")], + data: vec![10, 20, 30], + }], storage_changes: vec![], internal_calls: vec![], }; @@ -1020,7 +1082,10 @@ mod vm_contracts_integration { let errors = vec![ VmError::ContractNotFound(contract_id), VmError::InvalidBytecode("not WASM".to_string()), - VmError::OutOfGas { used: 100, limit: 50 }, + VmError::OutOfGas { + used: 100, + limit: 50, + }, VmError::Timeout, VmError::StackOverflow, ]; @@ -1075,13 +1140,9 @@ mod vm_contracts_integration { #[cfg(test)] mod consensus_dag_integration { - use synor_dag::{ - GHOSTDAG_K, MAX_BLOCK_PARENTS, BlockRateConfig, - }; - use synor_consensus::{ - COINBASE_MATURITY, BLOCKS_PER_SECOND, MAX_BLOCK_MASS, - }; - use synor_types::{Hash256, BlueScore}; + use synor_consensus::{BLOCKS_PER_SECOND, COINBASE_MATURITY, MAX_BLOCK_MASS}; + use synor_dag::{BlockRateConfig, GHOSTDAG_K, MAX_BLOCK_PARENTS}; + use synor_types::{BlueScore, Hash256}; /// Test GHOSTDAG K parameter relationship with block rate #[test] @@ -1131,9 +1192,12 @@ mod consensus_dag_integration { let time_secs = merge_depth as f64 / config.bps(); // Should be approximately 6 minutes (360 seconds) - assert!(time_secs > 350.0 && time_secs < 370.0, + assert!( + time_secs > 350.0 && time_secs < 370.0, "Merge depth for {:?} should be ~6 minutes, got {:.0} seconds", - config, time_secs); + config, + time_secs + ); } } @@ -1151,9 +1215,12 @@ mod consensus_dag_integration { let time_hours = finality_depth as f64 / config.bps() / 3600.0; // Should be approximately 2.4 hours - assert!(time_hours > 2.3 && time_hours < 2.5, + assert!( + time_hours > 2.3 && time_hours < 2.5, "Finality depth for {:?} should be ~2.4 hours, got {:.2} hours", - config, time_hours); + config, + time_hours + ); } } @@ -1167,7 +1234,10 @@ mod consensus_dag_integration { let pruning = config.pruning_depth(); // Pruning should be > finality > merge - assert!(pruning > finality, "Pruning depth should exceed finality depth"); + assert!( + pruning > finality, + "Pruning depth should exceed finality depth" + ); assert!(finality > merge, "Finality depth should exceed merge depth"); } @@ -1175,8 +1245,10 @@ mod consensus_dag_integration { #[test] fn test_dag_block_constants() { // K parameter must be less than max parents - assert!(GHOSTDAG_K as usize <= MAX_BLOCK_PARENTS, - "K should not exceed max parents"); + assert!( + GHOSTDAG_K as usize <= MAX_BLOCK_PARENTS, + "K should not exceed max parents" + ); // Reasonable bounds assert!(GHOSTDAG_K >= 3, "K should be at least 3"); @@ -1191,8 +1263,14 @@ mod consensus_dag_integration { assert_eq!(expected_bps, BLOCKS_PER_SECOND); // Coinbase maturity should be reasonable - assert!(COINBASE_MATURITY >= 10, "Coinbase should require some confirmations"); - assert!(COINBASE_MATURITY <= 1000, "Coinbase maturity shouldn't be excessive"); + assert!( + COINBASE_MATURITY >= 10, + "Coinbase should require some confirmations" + ); + assert!( + COINBASE_MATURITY <= 1000, + "Coinbase maturity shouldn't be excessive" + ); } /// Test block time configurations @@ -1231,7 +1309,10 @@ mod consensus_dag_integration { // Block should fit many transactions let typical_tx_mass = 5000u64; let min_txs_per_block = MAX_BLOCK_MASS / typical_tx_mass; - assert!(min_txs_per_block >= 50, "Block should fit at least 50 typical transactions"); + assert!( + min_txs_per_block >= 50, + "Block should fit at least 50 typical transactions" + ); } } @@ -1241,9 +1322,9 @@ mod consensus_dag_integration { #[cfg(test)] mod e2e_scenarios { - use synor_types::{Hash256, Amount, Address, Network, BlueScore, Timestamp}; + use synor_crypto::{HybridKeypair, Mnemonic}; use synor_mining::{KHeavyHash, Target}; - use synor_crypto::{Mnemonic, HybridKeypair}; + use synor_types::{Address, Amount, BlueScore, Hash256, Network, Timestamp}; /// Scenario: Block production flow #[test] @@ -1255,10 +1336,7 @@ mod e2e_scenarios { // 2. Construct block header let parent_hash = Hash256::blake3(b"parent block"); - let merkle_root = Hash256::merkle_root(&[ - Hash256::blake3(b"tx1"), - Hash256::blake3(b"tx2"), - ]); + let merkle_root = Hash256::merkle_root(&[Hash256::blake3(b"tx1"), Hash256::blake3(b"tx2")]); let timestamp = Timestamp::now(); // 3. Mine block @@ -1266,7 +1344,8 @@ mod e2e_scenarios { parent_hash.as_bytes().as_slice(), merkle_root.as_bytes().as_slice(), ×tamp.as_millis().to_le_bytes(), - ].concat(); + ] + .concat(); let hasher = KHeavyHash::new(); let target = Target::max(); @@ -1306,7 +1385,10 @@ mod e2e_scenarios { let signature = sender_keypair.sign(tx_hash.as_bytes()); // 4. Verify signature - assert!(sender_keypair.public_key().verify(tx_hash.as_bytes(), &signature).is_ok()); + assert!(sender_keypair + .public_key() + .verify(tx_hash.as_bytes(), &signature) + .is_ok()); } /// Scenario: Blue score progression during sync @@ -1349,14 +1431,16 @@ mod e2e_scenarios { // Simulate difficulty increase (harder target) let harder_target = Target::from_bytes([ - 0x00, 0x00, 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, + 0x00, 0x00, 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, ]); let harder_diff = harder_target.to_difficulty(); - assert!(harder_diff > easy_diff, "Difficulty should increase with harder target"); + assert!( + harder_diff > easy_diff, + "Difficulty should increase with harder target" + ); } } diff --git a/tests/phase13_integration.rs b/tests/phase13_integration.rs index 91e03da..2b77e0b 100644 --- a/tests/phase13_integration.rs +++ b/tests/phase13_integration.rs @@ -67,10 +67,12 @@ mod dagknight_tests { #[cfg(test)] mod quantum_crypto_tests { - use synor_crypto::falcon::{FalconKeypair, FalconVariant}; - use synor_crypto::sphincs::{SphincsKeypair, SphincsVariant}; - use synor_crypto::negotiation::{AlgorithmNegotiator, AlgorithmCapabilities, PqAlgorithm, AlgorithmFamily}; use std::collections::HashMap; + use synor_crypto::falcon::{FalconKeypair, FalconVariant}; + use synor_crypto::negotiation::{ + AlgorithmCapabilities, AlgorithmFamily, AlgorithmNegotiator, PqAlgorithm, + }; + use synor_crypto::sphincs::{SphincsKeypair, SphincsVariant}; /// Test SPHINCS+ signature generation and verification #[test] @@ -151,7 +153,9 @@ mod quantum_crypto_tests { }; let negotiator = AlgorithmNegotiator::new(node_a_caps); - let result = negotiator.negotiate(&node_b_caps).expect("Negotiation should succeed"); + let result = negotiator + .negotiate(&node_b_caps) + .expect("Negotiation should succeed"); // Should agree on Dilithium3 (highest security that both support) assert_eq!(