From d5c9fcc38fadfab4e2d0ffeb6d2aa17465bd6bf9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 03:24:56 +0000 Subject: [PATCH 1/5] Initial plan From e7e3d85cbf83cd834bccc501978271e729f799f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 03:43:39 +0000 Subject: [PATCH 2/5] Merge rc1 into feature/economic-system-and-rewards: resolve conflicts in favor of production code Co-authored-by: Steake <530040+Steake@users.noreply.github.com> --- crates/bitcell-node/src/blockchain.rs | 188 ++++--------------- crates/bitcell-node/src/dht.rs | 12 +- crates/bitcell-node/src/network.rs | 67 ++++--- crates/bitcell-node/src/rpc.rs | 218 ++++------------------- crates/bitcell-state/src/lib.rs | 47 +---- crates/bitcell-zkp/src/battle_circuit.rs | 66 ++----- crates/bitcell-zkp/src/state_circuit.rs | 79 +------- 7 files changed, 133 insertions(+), 544 deletions(-) diff --git a/crates/bitcell-node/src/blockchain.rs b/crates/bitcell-node/src/blockchain.rs index 69fea42..4d1dd55 100644 --- a/crates/bitcell-node/src/blockchain.rs +++ b/crates/bitcell-node/src/blockchain.rs @@ -1,15 +1,9 @@ ///! Blockchain manager for block production and validation -///! -///! Provides functionality for: -///! - Block production with VRF-based proposer selection -///! - Block validation including signature, VRF, and transaction verification -///! - Transaction indexing for efficient lookups -///! - State management with Merkle tree root computation use crate::{Result, MetricsRegistry}; use bitcell_consensus::{Block, BlockHeader, Transaction, BattleProof}; use bitcell_crypto::{Hash256, PublicKey, SecretKey}; -use bitcell_economics::{COIN, INITIAL_BLOCK_REWARD, HALVING_INTERVAL, MAX_HALVINGS}; +use bitcell_economics::{COIN, INITIAL_BLOCK_REWARD, HALVING_INTERVAL}; use bitcell_state::StateManager; use std::sync::{Arc, RwLock}; use std::collections::HashMap; @@ -17,17 +11,7 @@ use std::collections::HashMap; /// Genesis block height pub const GENESIS_HEIGHT: u64 = 0; -/// Transaction location in blockchain (block height and index within block) -#[derive(Clone, Debug)] -pub struct TxLocation { - pub block_height: u64, - pub tx_index: usize, -} - /// Blockchain manager -/// -/// Maintains the blockchain state including blocks, transactions, and state root. -/// Provides O(1) transaction lookup via hash index. #[derive(Clone)] pub struct Blockchain { /// Current chain height @@ -39,9 +23,6 @@ pub struct Blockchain { /// Block storage (height -> block) blocks: Arc>>, - /// Transaction hash index for O(1) lookups (tx_hash -> location) - tx_index: Arc>>, - /// State manager state: Arc>, @@ -65,7 +46,6 @@ impl Blockchain { height: Arc::new(RwLock::new(GENESIS_HEIGHT)), latest_hash: Arc::new(RwLock::new(genesis_hash)), blocks: Arc::new(RwLock::new(blocks)), - tx_index: Arc::new(RwLock::new(HashMap::new())), state: Arc::new(RwLock::new(StateManager::new())), metrics, secret_key, @@ -101,64 +81,29 @@ impl Blockchain { } /// Get current chain height - /// - /// Returns the current blockchain height. If the lock is poisoned (indicating - /// a prior panic while holding the lock), logs an error and recovers the guard. pub fn height(&self) -> u64 { *self.height.read().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in height() - prior panic detected: {}", e); + eprintln!("Lock poisoned in height(): {}", e); e.into_inner() }) } /// Get latest block hash - /// - /// Returns the hash of the latest block. If the lock is poisoned (indicating - /// a prior panic while holding the lock), logs an error and recovers the guard. pub fn latest_hash(&self) -> Hash256 { *self.latest_hash.read().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in latest_hash() - prior panic detected: {}", e); + eprintln!("Lock poisoned in latest_hash(): {}", e); e.into_inner() }) } /// Get block by height - /// - /// Returns the block at the specified height, or None if not found. - /// If the lock is poisoned, logs an error and recovers the guard. pub fn get_block(&self, height: u64) -> Option { self.blocks.read().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in get_block() - prior panic detected: {}", e); + eprintln!("Lock poisoned in get_block(): {}", e); e.into_inner() }).get(&height).cloned() } - /// Get transaction by hash using the O(1) hash index - /// - /// Returns the transaction and its location (block height, index) if found. - /// This is significantly more efficient than linear scan for large blockchains. - pub fn get_transaction_by_hash(&self, tx_hash: &Hash256) -> Option<(Transaction, TxLocation)> { - // First, look up the location in the index - let location = { - let index = self.tx_index.read().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in get_transaction_by_hash() - prior panic detected: {}", e); - e.into_inner() - }); - index.get(tx_hash).cloned() - }; - - // Then retrieve the actual transaction from the block - if let Some(loc) = location { - if let Some(block) = self.get_block(loc.block_height) { - if loc.tx_index < block.transactions.len() { - return Some((block.transactions[loc.tx_index].clone(), loc)); - } - } - } - - None - } - /// Get state manager (read-only access) pub fn state(&self) -> Arc> { Arc::clone(&self.state) @@ -167,8 +112,8 @@ impl Blockchain { /// Calculate block reward based on height (halves every HALVING_INTERVAL blocks) pub fn calculate_block_reward(height: u64) -> u64 { let halvings = height / HALVING_INTERVAL; - if halvings >= MAX_HALVINGS { - // After MAX_HALVINGS halvings, reward is effectively 0 + if halvings >= 64 { + // After 64 halvings, reward is effectively 0 return 0; } INITIAL_BLOCK_REWARD >> halvings @@ -190,45 +135,24 @@ impl Blockchain { // Get current state root let state_root = { - let state = self.state.read().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in produce_block() while reading state - prior panic detected: {}", e); - e.into_inner() - }); + let state = self.state.read().unwrap(); state.state_root }; - // Generate VRF output and proof using proper VRF chaining - // For genesis block (height 1), use previous hash as input - // For all other blocks, use the previous block's VRF output for chaining - // - // NOTE: We generate VRF proof while holding the blocks lock to prevent race conditions - // where the blockchain state could change between reading the VRF input and using it. - let (vrf_output, vrf_proof_bytes) = if new_height == 1 { - // First block after genesis uses genesis hash as VRF input - let vrf_input = prev_hash.as_bytes().to_vec(); - let (vrf_output, vrf_proof) = self.secret_key.vrf_prove(&vrf_input); - (vrf_output, bincode::serialize(&vrf_proof).unwrap_or_default()) + // Generate VRF output and proof + // Input is previous block's VRF output (or hash if genesis) + let vrf_input = if new_height == 1 { + prev_hash.as_bytes().to_vec() } else { - // Use previous block's VRF output for proper VRF chaining - // This ensures verifiable randomness chain where each output - // deterministically derives from the previous output - let blocks = self.blocks.read().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in produce_block() - prior panic detected: {}", e); - e.into_inner() - }); - - let vrf_input = if let Some(prev_block) = blocks.get(¤t_height) { - prev_block.header.vrf_output.to_vec() - } else { - // Fallback if previous block not found (shouldn't happen in normal operation) - tracing::warn!("Previous block {} not found for VRF chaining, using hash fallback", current_height); - prev_hash.as_bytes().to_vec() - }; - - // Generate VRF proof while still holding the read lock to prevent race conditions - let (vrf_output, vrf_proof) = self.secret_key.vrf_prove(&vrf_input); - (vrf_output, bincode::serialize(&vrf_proof).unwrap_or_default()) + // In a real implementation, we'd get the previous block's VRF output + // For now, we mix the prev_hash with the height to ensure uniqueness + let mut input = prev_hash.as_bytes().to_vec(); + input.extend_from_slice(&new_height.to_le_bytes()); + input }; + + let (vrf_output, vrf_proof) = self.secret_key.vrf_prove(&vrf_input); + let vrf_proof_bytes = bincode::serialize(&vrf_proof).unwrap_or_default(); // Create block header let header = BlockHeader { @@ -283,27 +207,16 @@ impl Blockchain { return Err(crate::Error::Node("Invalid block signature".to_string())); } - // Verify VRF proof using proper VRF chaining + // Verify VRF let vrf_proof: bitcell_crypto::VrfProof = bincode::deserialize(&block.header.vrf_proof) .map_err(|_| crate::Error::Node("Invalid VRF proof format".to_string()))?; - // Reconstruct VRF input using the same chaining logic as produce_block let vrf_input = if block.header.height == 1 { - // First block after genesis uses genesis hash as VRF input block.header.prev_hash.as_bytes().to_vec() } else { - // Use previous block's VRF output for proper VRF chaining - let blocks = self.blocks.read().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in validate_block() - prior panic detected: {}", e); - e.into_inner() - }); - if let Some(prev_block) = blocks.get(&(block.header.height - 1)) { - prev_block.header.vrf_output.to_vec() - } else { - return Err(crate::Error::Node( - format!("Previous block {} not found for VRF verification", block.header.height - 1) - )); - } + let mut input = block.header.prev_hash.as_bytes().to_vec(); + input.extend_from_slice(&block.header.height.to_le_bytes()); + input }; let vrf_output = vrf_proof.verify(&block.header.proposer, &vrf_input) @@ -337,23 +250,13 @@ impl Blockchain { // Apply transactions to state { - let mut state = self.state.write().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in add_block() while writing state - prior panic detected: {}", e); - e.into_inner() - }); + let mut state = self.state.write().unwrap(); // Apply block reward to proposer let reward = Self::calculate_block_reward(block_height); if reward > 0 { - match state.credit_account(*block.header.proposer.as_bytes(), reward) { - Ok(_) => { - tracing::info!("Block reward credited: {} units to proposer", reward); - } - Err(e) => { - tracing::error!("Failed to credit block reward: {:?}", e); - return Err(crate::Error::Node("Failed to credit block reward".to_string())); - } - } + state.credit_account(*block.header.proposer.as_bytes(), reward); + println!("Block reward credited: {} units to proposer", reward); } for tx in &block.transactions { @@ -366,10 +269,10 @@ impl Blockchain { ) { Ok(new_state_root) => { // State updated successfully - tracing::debug!("Transaction applied, new state root: {:?}", new_state_root); + println!("Transaction applied, new state root: {:?}", new_state_root); } Err(e) => { - tracing::warn!("Failed to apply transaction: {:?}", e); + println!("Failed to apply transaction: {:?}", e); // In production, this should rollback the entire block // For now, we just skip the transaction } @@ -377,43 +280,19 @@ impl Blockchain { } } - // Index transactions for O(1) lookup - { - let mut tx_index = self.tx_index.write().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in add_block() while indexing transactions - prior panic detected: {}", e); - e.into_inner() - }); - for (idx, tx) in block.transactions.iter().enumerate() { - tx_index.insert(tx.hash(), TxLocation { - block_height, - tx_index: idx, - }); - } - tracing::debug!("Indexed {} transactions in block {}", block.transactions.len(), block_height); - } - // Store block { - let mut blocks = self.blocks.write().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in add_block() while storing block - prior panic detected: {}", e); - e.into_inner() - }); + let mut blocks = self.blocks.write().unwrap(); blocks.insert(block_height, block); } // Update chain tip { - let mut height = self.height.write().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in add_block() while updating height - prior panic detected: {}", e); - e.into_inner() - }); + let mut height = self.height.write().unwrap(); *height = block_height; } { - let mut latest_hash = self.latest_hash.write().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in add_block() while updating latest hash - prior panic detected: {}", e); - e.into_inner() - }); + let mut latest_hash = self.latest_hash.write().unwrap(); *latest_hash = block_hash; } @@ -446,10 +325,7 @@ impl Blockchain { } // Check nonce and balance - let state = self.state.read().unwrap_or_else(|e| { - tracing::error!("Lock poisoned in validate_transaction() - prior panic detected: {}", e); - e.into_inner() - }); + let state = self.state.read().unwrap(); if let Some(account) = state.get_account(tx.from.as_bytes()) { if tx.nonce != account.nonce { return Err(crate::Error::Node(format!( diff --git a/crates/bitcell-node/src/dht.rs b/crates/bitcell-node/src/dht.rs index 3eb8a23..55326f3 100644 --- a/crates/bitcell-node/src/dht.rs +++ b/crates/bitcell-node/src/dht.rs @@ -50,7 +50,7 @@ impl DhtManager { // 1. Create libp2p keypair let keypair = Self::bitcell_to_libp2p_keypair(secret_key)?; let local_peer_id = PeerId::from(keypair.public()); - tracing::info!("Local Peer ID: {}", local_peer_id); + println!("Local Peer ID: {}", local_peer_id); // 2. Create transport let mut swarm = SwarmBuilder::with_existing_identity(keypair.clone()) @@ -136,18 +136,18 @@ impl DhtManager { })) => { if message.topic == block_topic.hash() { if let Ok(block) = bincode::deserialize::(&message.data) { - tracing::info!("Received block via Gossipsub from {}", peer_id); + println!("Received block via Gossipsub from {}", peer_id); let _ = block_tx.send(block).await; } } else if message.topic == tx_topic.hash() { if let Ok(tx) = bincode::deserialize::(&message.data) { - tracing::info!("Received tx via Gossipsub from {}", peer_id); + println!("Received tx via Gossipsub from {}", peer_id); let _ = tx_tx.send(tx).await; } } } SwarmEvent::NewListenAddr { address, .. } => { - tracing::info!("DHT listening on {:?}", address); + println!("DHT listening on {:?}", address); } _ => {} }, @@ -157,12 +157,12 @@ impl DhtManager { } Some(DhtCommand::BroadcastBlock(data)) => { if let Err(e) = swarm.behaviour_mut().gossipsub.publish(block_topic.clone(), data) { - tracing::error!("Failed to publish block via Gossipsub: {:?}", e); + eprintln!("Failed to publish block: {:?}", e); } } Some(DhtCommand::BroadcastTransaction(data)) => { if let Err(e) = swarm.behaviour_mut().gossipsub.publish(tx_topic.clone(), data) { - tracing::error!("Failed to publish transaction via Gossipsub: {:?}", e); + eprintln!("Failed to publish tx: {:?}", e); } } None => break, diff --git a/crates/bitcell-node/src/network.rs b/crates/bitcell-node/src/network.rs index 113dc1b..f5955f5 100644 --- a/crates/bitcell-node/src/network.rs +++ b/crates/bitcell-node/src/network.rs @@ -99,14 +99,13 @@ impl NetworkManager { let dht_manager = crate::dht::DhtManager::new(secret_key, bootstrap, block_tx, tx_tx)?; let mut dht = self.dht.write(); *dht = Some(dht_manager); - tracing::info!("DHT enabled"); + println!("DHT enabled"); Ok(()) } + + /// Start the network listener - /// - /// Binds to the specified port and starts accepting connections. - /// Also initiates DHT discovery if bootstrap nodes are provided. pub async fn start(&self, port: u16, bootstrap_nodes: Vec) -> Result<()> { let addr = format!("0.0.0.0:{}", port); @@ -120,7 +119,7 @@ impl NetworkManager { let listener = TcpListener::bind(&addr).await .map_err(|e| format!("Failed to bind to {}: {}", addr, e))?; - tracing::info!("Network listening on {}", addr); + println!("Network listening on {}", addr); // Spawn listener task let network = self.clone(); @@ -143,12 +142,12 @@ impl NetworkManager { }; if let Some(mut dht) = dht_manager { - tracing::info!("Starting DHT discovery..."); + println!("Starting DHT discovery..."); // 1. Connect to explicit bootstrap nodes from config // This is necessary because DhtManager might reject addresses without Peer IDs if !bootstrap_nodes_clone.is_empty() { - tracing::info!("Connecting to {} bootstrap nodes...", bootstrap_nodes_clone.len()); + println!("Connecting to {} bootstrap nodes...", bootstrap_nodes_clone.len()); for addr_str in bootstrap_nodes_clone { // Extract IP and port from multiaddr string /ip4/x.x.x.x/tcp/yyyy // Also handle /p2p/Qm... suffix if present @@ -167,7 +166,7 @@ impl NetworkManager { }; let connect_addr = format!("{}:{}", ip, port); - tracing::info!("Connecting to bootstrap node: {}", connect_addr); + println!("Connecting to bootstrap node: {}", connect_addr); let _ = network_clone.connect_to_peer(&connect_addr).await; } } @@ -175,7 +174,7 @@ impl NetworkManager { } if let Ok(peers) = dht.start_discovery().await { - tracing::info!("DHT discovery found {} peers", peers.len()); + println!("DHT discovery found {} peers", peers.len()); for peer in peers { for addr in peer.addresses { // Convert multiaddr to string address if possible @@ -198,7 +197,7 @@ impl NetworkManager { }; let connect_addr = format!("{}:{}", ip, port); - tracing::info!("DHT discovered peer: {}", connect_addr); + println!("DHT discovered peer: {}", connect_addr); let _ = network_clone.connect_to_peer(&connect_addr).await; } } @@ -228,16 +227,16 @@ impl NetworkManager { loop { match listener.accept().await { Ok((socket, addr)) => { - tracing::info!("Accepted connection from {}", addr); + println!("Accepted connection from {}", addr); let network = self.clone(); tokio::spawn(async move { if let Err(e) = network.handle_connection(socket).await { - tracing::error!("Connection error: {}", e); + eprintln!("Connection error: {}", e); } }); } Err(e) => { - tracing::error!("Failed to accept connection: {}", e); + eprintln!("Failed to accept connection: {}", e); } } } @@ -245,22 +244,22 @@ impl NetworkManager { /// Handle a peer connection async fn handle_connection(&self, mut socket: TcpStream) -> Result<()> { - tracing::info!("Accepted connection"); + println!("Accepted connection"); // Send handshake self.send_message(&mut socket, &NetworkMessage::Handshake { peer_id: self.local_peer }).await?; - tracing::info!("Sent handshake to incoming peer"); + println!("Sent handshake to incoming peer"); // Read handshake response let msg = self.receive_message(&mut socket).await?; - tracing::info!("Received handshake response"); + println!("Received handshake response"); let peer_id = match msg { NetworkMessage::Handshake { peer_id } => peer_id, _ => return Err("Expected handshake".into()), }; - tracing::info!("Handshake complete with peer: {:?}", peer_id); + println!("Handshake complete with peer: {:?}", peer_id); // Split socket for concurrent read/write let (reader, writer) = tokio::io::split(socket); @@ -303,11 +302,11 @@ impl NetworkManager { } NetworkMessage::Block(block) => { - tracing::info!("Received block {} from peer", block.header.height); + println!("Received block {} from peer", block.header.height); self.handle_incoming_block(block).await?; } NetworkMessage::Transaction(tx) => { - tracing::info!("Received transaction from peer"); + println!("Received transaction from peer"); self.handle_incoming_transaction(tx).await?; } NetworkMessage::GetPeers => { @@ -328,7 +327,7 @@ impl NetworkManager { } } Err(e) => { - tracing::info!("Peer {:?} disconnected: {}", peer_id, e); + println!("Peer {:?} disconnected: {}", peer_id, e); break; } } @@ -449,27 +448,27 @@ impl NetworkManager { } // Only print if we're actually attempting a new connection - tracing::info!("Connecting to peer at {}", address); + println!("Connecting to peer at {}", address); match TcpStream::connect(address).await { Ok(mut socket) => { - tracing::info!("Connected to {}, sending handshake", address); + println!("Connected to {}, sending handshake", address); // Send handshake self.send_message(&mut socket, &NetworkMessage::Handshake { peer_id: self.local_peer, }).await?; - tracing::info!("Sent handshake to {}", address); + println!("Sent handshake to {}", address); // Receive handshake let msg = self.receive_message(&mut socket).await?; - tracing::info!("Received handshake response from {}", address); + println!("Received handshake response from {}", address); let peer_id = match msg { NetworkMessage::Handshake { peer_id } => peer_id, _ => return Err("Expected handshake".into()), }; - tracing::info!("Connected to peer: {:?}", peer_id); + println!("Connected to peer: {:?}", peer_id); // Split socket let (reader, writer) = tokio::io::split(socket); @@ -551,7 +550,7 @@ impl NetworkManager { /// Connect to a peer by PublicKey (legacy compatibility) pub fn connect_peer(&self, peer_id: PublicKey) -> Result<()> { // This is now handled by connect_to_peer with actual addresses - tracing::info!("Legacy connect_peer called for: {:?}", peer_id); + println!("Legacy connect_peer called for: {:?}", peer_id); Ok(()) } @@ -560,7 +559,7 @@ impl NetworkManager { let mut peers = self.peers.write(); peers.remove(peer_id); self.metrics.set_peer_count(peers.len()); - tracing::info!("Disconnected from peer: {:?}", peer_id); + println!("Disconnected from peer: {:?}", peer_id); Ok(()) } @@ -569,7 +568,7 @@ impl NetworkManager { // Broadcast via TCP let peer_ids: Vec = { let peers = self.peers.read(); - tracing::info!("Broadcasting block {} to {} peers", block.header.height, peers.len()); + println!("Broadcasting block {} to {} peers", block.header.height, peers.len()); peers.keys().copied().collect() }; @@ -591,7 +590,7 @@ impl NetworkManager { if let Some(dht) = dht_opt { if let Err(e) = dht.broadcast_block(block).await { - tracing::error!("Failed to broadcast block via DHT: {}", e); + eprintln!("Failed to broadcast block via DHT: {}", e); } } @@ -603,7 +602,7 @@ impl NetworkManager { // Broadcast via TCP let peer_ids: Vec = { let peers = self.peers.read(); - tracing::info!("Broadcasting transaction to {} peers", peers.len()); + println!("Broadcasting transaction to {} peers", peers.len()); peers.keys().copied().collect() }; @@ -625,7 +624,7 @@ impl NetworkManager { if let Some(dht) = dht_opt { if let Err(e) = dht.broadcast_transaction(tx).await { - tracing::error!("Failed to broadcast transaction via DHT: {}", e); + eprintln!("Failed to broadcast transaction via DHT: {}", e); } } @@ -700,16 +699,16 @@ pub async fn discover_peers( network: Arc, bootstrap_addresses: Vec, ) -> Result<()> { - tracing::info!("Starting peer discovery with {} bootstrap addresses...", bootstrap_addresses.len()); + println!("Starting peer discovery with {} bootstrap addresses...", bootstrap_addresses.len()); for addr in bootstrap_addresses { network.add_bootstrap_peer(addr.clone()); if let Err(e) = network.connect_to_peer(&addr).await { - tracing::error!("Failed to connect to bootstrap peer {}: {}", addr, e); + eprintln!("Failed to connect to bootstrap peer {}: {}", addr, e); } } - tracing::info!("Peer discovery complete: {} peers connected", network.peer_count()); + println!("Peer discovery complete: {} peers connected", network.peer_count()); Ok(()) } diff --git a/crates/bitcell-node/src/rpc.rs b/crates/bitcell-node/src/rpc.rs index 82cf85e..24bb995 100644 --- a/crates/bitcell-node/src/rpc.rs +++ b/crates/bitcell-node/src/rpc.rs @@ -11,9 +11,6 @@ use serde_json::{Value, json}; use crate::{Blockchain, NetworkManager, TransactionPool, NodeConfig}; use crate::tournament::TournamentManager; -/// Empty bloom filter (256 bytes of zeros) for blocks without logs -static EMPTY_BLOOM_FILTER: [u8; 256] = [0u8; 256]; - /// RPC Server State #[derive(Clone)] pub struct RpcState { @@ -23,7 +20,6 @@ pub struct RpcState { pub tournament_manager: Option>, pub config: NodeConfig, pub node_type: String, // "validator", "miner", "full" - pub node_id: String, // Unique node identifier (public key hex) } /// Start the RPC server @@ -35,7 +31,7 @@ pub async fn run_server(state: RpcState, port: u16) -> Result<(), Box eth_get_transaction_by_hash(&state, req.params).await, "eth_getBalance" => eth_get_balance(&state, req.params).await, "eth_sendRawTransaction" => eth_send_raw_transaction(&state, req.params).await, - "eth_getTransactionCount" => eth_get_transaction_count(&state, req.params).await, - "eth_gasPrice" => eth_gas_price(&state).await, // BitCell Namespace "bitcell_getNodeInfo" => bitcell_get_node_info(&state).await, @@ -107,7 +101,6 @@ async fn handle_json_rpc( "bitcell_getBattleReplay" => bitcell_get_battle_replay(&state, req.params).await, "bitcell_getReputation" => bitcell_get_reputation(&state, req.params).await, "bitcell_getMinerStats" => bitcell_get_miner_stats(&state, req.params).await, - "bitcell_getPendingBlockInfo" => eth_pending_block_number(&state).await, // Default _ => Err(JsonRpcError { @@ -135,27 +128,11 @@ async fn handle_json_rpc( // --- JSON-RPC Methods --- -/// Get current block number -/// -/// Returns the highest confirmed block number. -/// If pending transactions exist, a "pending" query will return height + 1. async fn eth_block_number(state: &RpcState) -> Result { let height = state.blockchain.height(); Ok(json!(format!("0x{:x}", height))) } -/// Get pending block number (height + 1 if pending transactions exist) -async fn eth_pending_block_number(state: &RpcState) -> Result { - let height = state.blockchain.height(); - let pending_count = state.tx_pool.pending_count(); - let pending_height = if pending_count > 0 { height + 1 } else { height }; - Ok(json!({ - "confirmed": format!("0x{:x}", height), - "pending": format!("0x{:x}", pending_height), - "pendingTransactions": pending_count - })) -} - async fn eth_get_block_by_number(state: &RpcState, params: Option) -> Result { let params = params.ok_or(JsonRpcError { code: -32602, @@ -229,16 +206,13 @@ async fn eth_get_block_by_number(state: &RpcState, params: Option) -> Res json!(tx_hashes) }; - // Calculate actual block size - let block_size = bincode::serialized_size(&block).unwrap_or(0); - Ok(json!({ "number": format!("0x{:x}", block.header.height), "hash": format!("0x{}", hex::encode(block.hash().as_bytes())), "parentHash": format!("0x{}", hex::encode(block.header.prev_hash.as_bytes())), - "nonce": format!("0x{:016x}", block.header.work), + "nonce": "0x0000000000000000", // TODO: Use work/nonce "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", // Empty uncle hash - "logsBloom": format!("0x{}", hex::encode(&EMPTY_BLOOM_FILTER)), + "logsBloom": "0x00", // TODO: Bloom filter "transactionsRoot": format!("0x{}", hex::encode(block.header.tx_root.as_bytes())), "stateRoot": format!("0x{}", hex::encode(block.header.state_root.as_bytes())), "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", // Empty receipts root @@ -246,14 +220,12 @@ async fn eth_get_block_by_number(state: &RpcState, params: Option) -> Res "difficulty": "0x1", "totalDifficulty": format!("0x{:x}", block.header.height), // Simplified "extraData": "0x", - "size": format!("0x{:x}", block_size), + "size": format!("0x{:x}", 1000), // TODO: Real size "gasLimit": "0x1fffffffffffff", "gasUsed": "0x0", "timestamp": format!("0x{:x}", block.header.timestamp), "transactions": transactions, - "uncles": [], - "vrfOutput": format!("0x{}", hex::encode(block.header.vrf_output)), - "battleProofsCount": block.battle_proofs.len() + "uncles": [] })) } else { Ok(Value::Null) @@ -306,23 +278,30 @@ async fn eth_get_transaction_by_hash(state: &RpcState, params: Option) -> hash.copy_from_slice(&tx_hash_bytes); let target_hash = bitcell_crypto::Hash256::from(hash); - // Use efficient O(1) lookup via transaction hash index - if let Some((tx, location)) = state.blockchain.get_transaction_by_hash(&target_hash) { - // Get the block to include block hash in response - if let Some(block) = state.blockchain.get_block(location.block_height) { - return Ok(json!({ - "hash": format!("0x{}", hex::encode(tx.hash().as_bytes())), - "nonce": format!("0x{:x}", tx.nonce), - "blockHash": format!("0x{}", hex::encode(block.hash().as_bytes())), - "blockNumber": format!("0x{:x}", location.block_height), - "transactionIndex": format!("0x{:x}", location.tx_index), - "from": format!("0x{}", hex::encode(tx.from.as_bytes())), - "to": format!("0x{}", hex::encode(tx.to.as_bytes())), - "value": format!("0x{:x}", tx.amount), - "gas": format!("0x{:x}", tx.gas_limit), - "gasPrice": format!("0x{:x}", tx.gas_price), - "input": format!("0x{}", hex::encode(&tx.data)), - })); + // Search in blockchain (inefficient linear scan for now, need index later) + let height = state.blockchain.height(); + // Scan last 100 blocks for efficiency in this demo + let start_height = if height > 100 { height - 100 } else { 0 }; + + for h in (start_height..=height).rev() { + if let Some(block) = state.blockchain.get_block(h) { + for (i, tx) in block.transactions.iter().enumerate() { + if tx.hash() == target_hash { + return Ok(json!({ + "hash": format!("0x{}", hex::encode(tx.hash().as_bytes())), + "nonce": format!("0x{:x}", tx.nonce), + "blockHash": format!("0x{}", hex::encode(block.hash().as_bytes())), + "blockNumber": format!("0x{:x}", block.header.height), + "transactionIndex": format!("0x{:x}", i), + "from": format!("0x{}", hex::encode(tx.from.as_bytes())), + "to": format!("0x{}", hex::encode(tx.to.as_bytes())), + "value": format!("0x{:x}", tx.amount), + "gas": format!("0x{:x}", tx.gas_limit), + "gasPrice": format!("0x{:x}", tx.gas_price), + "input": format!("0x{}", hex::encode(&tx.data)), + })); + } + } } } @@ -392,85 +371,6 @@ async fn eth_get_balance(state: &RpcState, params: Option) -> Result) -> Result { - let params = params.ok_or(JsonRpcError { - code: -32602, - message: "Invalid params".to_string(), - data: None, - })?; - - let args = params.as_array().ok_or(JsonRpcError { - code: -32602, - message: "Params must be an array".to_string(), - data: None, - })?; - - if args.is_empty() { - return Err(JsonRpcError { - code: -32602, - message: "Missing address".to_string(), - data: None, - }); - } - - let address_str = args[0].as_str().ok_or(JsonRpcError { - code: -32602, - message: "Address must be a string".to_string(), - data: None, - })?; - - // Parse address (hex string to PublicKey) - let address_hex = address_str.strip_prefix("0x").unwrap_or(address_str); - let address_bytes = hex::decode(address_hex).map_err(|_| JsonRpcError { - code: -32602, - message: "Invalid address format".to_string(), - data: None, - })?; - - if address_bytes.len() != 33 { - return Err(JsonRpcError { - code: -32602, - message: "Address must be 33 bytes (compressed public key)".to_string(), - data: None, - }); - } - - let mut address = [0u8; 33]; - address.copy_from_slice(&address_bytes); - - // Fetch nonce from blockchain state - let nonce = { - let state_lock = state.blockchain.state(); - let state = state_lock.read().map_err(|_| JsonRpcError { - code: -32603, - message: "Failed to acquire state lock".to_string(), - data: None, - })?; - state.get_account(&address) - .map(|account| account.nonce) - .unwrap_or(0) - }; - - // Return nonce as hex string - Ok(json!(format!("0x{:x}", nonce))) -} - -/// Default gas price in wei (1 Gwei) -const DEFAULT_GAS_PRICE: u64 = 1_000_000_000; - -/// Get current gas price -/// -/// Returns the current gas price. In production, this should be -/// dynamically calculated based on network congestion and mempool state. -async fn eth_gas_price(_state: &RpcState) -> Result { - // TODO: Calculate dynamic gas price based on: - // - Transaction pool congestion - // - Recent block gas usage - // - Priority fee market - Ok(json!(format!("0x{:x}", DEFAULT_GAS_PRICE))) -} - async fn eth_send_raw_transaction(state: &RpcState, params: Option) -> Result { let params = params.ok_or(JsonRpcError { code: -32602, @@ -549,56 +449,11 @@ async fn eth_send_raw_transaction(state: &RpcState, params: Option) -> Re }); } } else { - // Account doesn't exist - allow transactions with nonce 0 - // This supports sending to/from new accounts that haven't been - // credited yet (e.g., funding transactions from coinbase rewards) - // - // DoS Mitigation Notes: - // 1. The transaction still needs a valid signature, preventing random spam - // 2. The transaction pool has capacity limits that reject excess transactions - // 3. Gas fees will be burned even if the transaction fails, discouraging abuse - // 4. Future improvement: Add per-address rate limiting in the mempool - if tx.nonce != 0 { - return Err(JsonRpcError { - code: -32602, - message: format!("Account not found and nonce is not zero (got nonce {}). New accounts must start with nonce 0.", tx.nonce), - data: None, - }); - } - - // Validate gas parameters to prevent spam and overflow attacks - // Gas price and limit must be non-zero and within reasonable bounds - const MAX_GAS_PRICE: u64 = 10_000_000_000_000; // 10,000 Gwei max - const MAX_GAS_LIMIT: u64 = 30_000_000; // 30M gas max (similar to Ethereum block limit) - - if tx.gas_price == 0 || tx.gas_limit == 0 { - return Err(JsonRpcError { - code: -32602, - message: "Transactions from new accounts require non-zero gas price and limit to prevent DoS attacks".to_string(), - data: None, - }); - } - - if tx.gas_price > MAX_GAS_PRICE { - return Err(JsonRpcError { - code: -32602, - message: format!("Gas price {} exceeds maximum allowed {}", tx.gas_price, MAX_GAS_PRICE), - data: None, - }); - } - - if tx.gas_limit > MAX_GAS_LIMIT { - return Err(JsonRpcError { - code: -32602, - message: format!("Gas limit {} exceeds maximum allowed {}", tx.gas_limit, MAX_GAS_LIMIT), - data: None, - }); - } - - tracing::debug!( - from = %hex::encode(tx.from.as_bytes()), - "Allowing transaction from new account with nonce 0" - ); + return Err(JsonRpcError { + code: -32602, + message: "Account not found".to_string(), + data: None, + }); } } @@ -615,18 +470,15 @@ async fn eth_send_raw_transaction(state: &RpcState, params: Option) -> Re Ok(json!(format!("0x{}", hex::encode(tx_hash.as_bytes())))) } -/// Get node information including ID, version, and capabilities async fn bitcell_get_node_info(state: &RpcState) -> Result { Ok(json!({ - "node_id": state.node_id, + "node_id": "TODO_NODE_ID", // TODO: Expose node ID from NetworkManager "version": "0.1.0", "protocol_version": "1", "network_id": "bitcell-testnet", "api_version": "0.1-alpha", "capabilities": ["bitcell/1"], "node_type": state.node_type, - "chain_height": state.blockchain.height(), - "peer_count": state.network.peer_count(), })) } diff --git a/crates/bitcell-state/src/lib.rs b/crates/bitcell-state/src/lib.rs index a577ae4..b247569 100644 --- a/crates/bitcell-state/src/lib.rs +++ b/crates/bitcell-state/src/lib.rs @@ -5,7 +5,6 @@ //! - Bond management //! - State Merkle tree //! - Nullifier set -//! - Persistent storage with RocksDB pub mod account; pub mod bonds; @@ -13,11 +12,11 @@ pub mod storage; pub use account::{Account, AccountState}; pub use bonds::{BondState, BondStatus}; -pub use storage::{StorageManager, PruningStats}; use bitcell_crypto::Hash256; use std::collections::HashMap; use std::sync::Arc; +use storage::StorageManager; pub type Result = std::result::Result; @@ -32,9 +31,6 @@ pub enum Error { #[error("Invalid bond")] InvalidBond, - #[error("Balance overflow")] - BalanceOverflow, - #[error("Storage error: {0}")] StorageError(String), } @@ -110,22 +106,12 @@ impl StateManager { } /// Create or update account - /// - /// Updates the in-memory cache and persists to storage if available. - /// Storage errors are logged but do not prevent the operation from succeeding - /// in memory (eventual consistency model). pub fn update_account(&mut self, pubkey: [u8; 33], account: Account) { self.accounts.insert(pubkey, account.clone()); // Persist to storage if available if let Some(storage) = &self.storage { - if let Err(e) = storage.store_account(&pubkey, &account) { - tracing::error!( - pubkey = %hex::encode(&pubkey), - error = %e, - "Failed to persist account to storage. State may be inconsistent on restart." - ); - } + let _ = storage.store_account(&pubkey, &account); } self.recompute_root(); @@ -154,22 +140,12 @@ impl StateManager { } /// Update bond state - /// - /// Updates the in-memory cache and persists to storage if available. - /// Storage errors are logged but do not prevent the operation from succeeding - /// in memory (eventual consistency model). pub fn update_bond(&mut self, pubkey: [u8; 33], bond: BondState) { self.bonds.insert(pubkey, bond.clone()); // Persist to storage if available if let Some(storage) = &self.storage { - if let Err(e) = storage.store_bond(&pubkey, &bond) { - tracing::error!( - pubkey = %hex::encode(&pubkey), - error = %e, - "Failed to persist bond to storage. State may be inconsistent on restart." - ); - } + let _ = storage.store_bond(&pubkey, &bond); } self.recompute_root(); @@ -241,27 +217,16 @@ impl StateManager { } /// Credit an account (minting/coinbase) - /// Returns the new state root on success, or an error if overflow would occur. - /// Note: This method should only be called by blockchain core during block processing. - pub fn credit_account(&mut self, pubkey: [u8; 33], amount: u64) -> Result { + pub fn credit_account(&mut self, pubkey: [u8; 33], amount: u64) -> Hash256 { let mut account = self.accounts.get(&pubkey) .cloned() .unwrap_or(Account { balance: 0, nonce: 0 }); - account.balance = account.balance.checked_add(amount) - .ok_or(Error::BalanceOverflow)?; - - tracing::debug!( - pubkey = %hex::encode(&pubkey), - amount = amount, - new_balance = account.balance, - "Credited account" - ); - + account.balance += amount; self.accounts.insert(pubkey, account); self.recompute_root(); - Ok(self.state_root) + self.state_root } } diff --git a/crates/bitcell-zkp/src/battle_circuit.rs b/crates/bitcell-zkp/src/battle_circuit.rs index 2488a25..0098109 100644 --- a/crates/bitcell-zkp/src/battle_circuit.rs +++ b/crates/bitcell-zkp/src/battle_circuit.rs @@ -1,23 +1,16 @@ -//! Battle verification circuit +//! Battle verification circuit stub //! -//! Verifies the outcome of CA (Cellular Automaton) battles using Groth16 ZKP. -//! The circuit ensures that: -//! 1. The winner ID is valid (0, 1, or 2) -//! 2. The commitments match the public inputs -//! -//! Full battle verification requires extensive constraint programming to -//! verify the CA simulation steps, which is a complex undertaking. +//! Demonstrates structure for verifying CA battles with Groth16. +//! Full implementation requires extensive constraint programming. +use bitcell_crypto::Hash256; +use serde::{Deserialize, Serialize}; + +use ark_ff::Field; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_bn254::Fr; /// Battle circuit configuration -/// -/// Proves that a battle between two players resulted in the claimed winner. -/// Winner ID meanings: -/// - 0: Draw (no winner) -/// - 1: Player A wins -/// - 2: Player B wins #[derive(Clone)] pub struct BattleCircuit { // Public inputs @@ -94,10 +87,7 @@ use ark_snark::SNARK; use ark_std::rand::thread_rng; impl BattleCircuit { - /// Setup the circuit and generate proving/verifying keys - /// - /// Returns an error if the circuit setup fails (e.g., due to constraint system issues). - pub fn setup() -> crate::Result<(ProvingKey, VerifyingKey)> { + pub fn setup() -> (ProvingKey, VerifyingKey) { let rng = &mut thread_rng(); Groth16::::circuit_specific_setup( Self { @@ -109,10 +99,9 @@ impl BattleCircuit { }, rng, ) - .map_err(|e| crate::Error::ProofGeneration(format!("Circuit setup failed: {}", e))) + .unwrap() } - /// Generate a proof for this circuit instance pub fn prove( &self, pk: &ProvingKey, @@ -123,7 +112,6 @@ impl BattleCircuit { Ok(crate::Groth16Proof::new(proof)) } - /// Verify a proof against public inputs pub fn verify( vk: &VerifyingKey, proof: &crate::Groth16Proof, @@ -141,10 +129,10 @@ mod tests { #[test] fn test_battle_circuit_prove_verify() { - // 1. Setup - now returns Result - let (pk, vk) = BattleCircuit::setup().expect("Circuit setup should succeed"); + // 1. Setup + let (pk, vk) = BattleCircuit::setup(); - // 2. Create circuit instance with valid winner ID (1 = Player B wins) + // 2. Create circuit instance let circuit = BattleCircuit::new( Fr::one(), // Mock commitment A Fr::one(), // Mock commitment B @@ -165,34 +153,4 @@ mod tests { assert!(BattleCircuit::verify(&vk, &proof, &public_inputs).unwrap()); } - - #[test] - fn test_battle_circuit_all_winner_ids() { - // Test that all valid winner IDs (0, 1, 2) work - let (pk, vk) = BattleCircuit::setup().expect("Circuit setup should succeed"); - - for winner_id in [0u8, 1u8, 2u8] { - let circuit = BattleCircuit::new( - Fr::one(), - Fr::one(), - winner_id, - 100, - 200, - ); - - let proof = circuit.prove(&pk).unwrap_or_else(|_| panic!("Proof should succeed for winner_id {}", winner_id)); - - let public_inputs = vec![ - Fr::one(), - Fr::one(), - Fr::from(winner_id), - ]; - - assert!( - BattleCircuit::verify(&vk, &proof, &public_inputs).unwrap(), - "Verification should succeed for winner_id {}", - winner_id - ); - } - } } diff --git a/crates/bitcell-zkp/src/state_circuit.rs b/crates/bitcell-zkp/src/state_circuit.rs index abad824..7590361 100644 --- a/crates/bitcell-zkp/src/state_circuit.rs +++ b/crates/bitcell-zkp/src/state_circuit.rs @@ -1,7 +1,6 @@ //! State transition circuit //! -//! Verifies Merkle tree updates with proper non-equality constraint. -//! Uses arkworks Groth16 for zero-knowledge proof generation and verification. +//! Verifies Merkle tree updates. use ark_ff::Field; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; @@ -9,14 +8,8 @@ use ark_bn254::Fr; use ark_groth16::{Groth16, ProvingKey, VerifyingKey}; use ark_snark::SNARK; use ark_std::rand::thread_rng; -use ark_std::Zero; /// State transition circuit configuration -/// -/// This circuit proves that a state transition occurred correctly by verifying: -/// 1. The old and new state roots are different (state changed) -/// 2. The nullifier is properly computed to prevent double-spending -/// 3. The Merkle tree update is valid (TODO: full implementation) #[derive(Clone)] pub struct StateCircuit { // Public inputs @@ -43,10 +36,7 @@ impl StateCircuit { } } - /// Setup the circuit and generate proving/verifying keys - /// - /// Returns an error if the circuit setup fails (e.g., due to constraint system issues). - pub fn setup() -> crate::Result<(ProvingKey, VerifyingKey)> { + pub fn setup() -> (ProvingKey, VerifyingKey) { let rng = &mut thread_rng(); Groth16::::circuit_specific_setup( Self { @@ -57,10 +47,9 @@ impl StateCircuit { }, rng, ) - .map_err(|e| crate::Error::ProofGeneration(format!("Circuit setup failed: {}", e))) + .unwrap() } - /// Generate a proof for this circuit instance pub fn prove( &self, pk: &ProvingKey, @@ -71,7 +60,6 @@ impl StateCircuit { Ok(crate::Groth16Proof::new(proof)) } - /// Verify a proof against public inputs pub fn verify( vk: &VerifyingKey, proof: &crate::Groth16Proof, @@ -93,52 +81,22 @@ impl ConstraintSynthesizer for StateCircuit { let _leaf_index = cs.new_witness_variable(|| self.leaf_index.ok_or(SynthesisError::AssignmentMissing))?; // Constraint: old_root != new_root (state must change) - // To prove non-equality, we use the following approach: - // 1. Compute diff = new_root - old_root - // 2. Compute inv = inverse(diff) as a witness - // 3. Enforce: diff * inv = 1 - // This proves diff != 0, which proves new_root != old_root + // (new_root - old_root) * inv = 1 + // This proves new_root - old_root != 0 - // Step 1: Compute diff = new_root - old_root let diff = cs.new_witness_variable(|| { let old = self.old_state_root.ok_or(SynthesisError::AssignmentMissing)?; let new = self.new_state_root.ok_or(SynthesisError::AssignmentMissing)?; Ok(new - old) })?; - // Enforce: diff = new_root - old_root cs.enforce_constraint( ark_relations::lc!() + new_root - old_root, ark_relations::lc!() + ark_relations::r1cs::Variable::One, ark_relations::lc!() + diff, )?; - // Step 2: Allocate inverse of diff as witness - let inv = cs.new_witness_variable(|| { - let old = self.old_state_root.ok_or(SynthesisError::AssignmentMissing)?; - let new = self.new_state_root.ok_or(SynthesisError::AssignmentMissing)?; - let diff_val = new - old; - if diff_val.is_zero() { - // If diff is zero (old_root == new_root), no valid inverse exists. - // This violates the non-equality constraint - state must change. - // We return Unsatisfiable since the constraint cannot be satisfied. - return Err(SynthesisError::Unsatisfiable); - } - diff_val.inverse().ok_or(SynthesisError::Unsatisfiable) - })?; - - // Step 3: Enforce diff * inv = 1 (proves diff != 0) - cs.enforce_constraint( - ark_relations::lc!() + diff, - ark_relations::lc!() + inv, - ark_relations::lc!() + ark_relations::r1cs::Variable::One, - )?; - // TODO: Add full Merkle tree verification constraints - // This would include: - // - Verifying the old leaf at leaf_index against old_state_root - // - Verifying the new leaf at leaf_index against new_state_root - // - Ensuring the nullifier is derived from the old leaf Ok(()) } @@ -151,13 +109,13 @@ mod tests { #[test] fn test_state_circuit_prove_verify() { - // 1. Setup - now returns Result - let (pk, vk) = StateCircuit::setup().expect("Circuit setup should succeed"); + // 1. Setup + let (pk, vk) = StateCircuit::setup(); - // 2. Create circuit instance with different roots (non-equality constraint) + // 2. Create circuit instance let circuit = StateCircuit::new( Fr::from(100u64), // Old root - Fr::from(200u64), // New root (must be different!) + Fr::from(200u64), // New root Fr::one(), // Nullifier 0, // Leaf index ); @@ -174,23 +132,4 @@ mod tests { assert!(StateCircuit::verify(&vk, &proof, &public_inputs).unwrap()); } - - #[test] - fn test_state_circuit_rejects_same_roots() { - // Setup - let (pk, _vk) = StateCircuit::setup().expect("Circuit setup should succeed"); - - // Create circuit with same old and new roots - should fail to prove - // because our non-equality constraint requires diff != 0 - let circuit = StateCircuit::new( - Fr::from(100u64), // Old root - Fr::from(100u64), // Same as old - violates non-equality constraint - Fr::one(), - 0, - ); - - // Proof generation should fail because diff = 0 has no inverse - let result = circuit.prove(&pk); - assert!(result.is_err(), "Proof should fail when old_root == new_root"); - } } From 67235b0a2a6d70ae001d85af59d9942644748673 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 03:51:41 +0000 Subject: [PATCH 3/5] Address code review: add overflow protection, proper error handling, and use MAX_HALVINGS constant Co-authored-by: Steake <530040+Steake@users.noreply.github.com> --- crates/bitcell-node/src/blockchain.rs | 21 ++++++++++++++------- crates/bitcell-node/src/rpc.rs | 3 ++- crates/bitcell-state/src/lib.rs | 18 +++++++++++++++--- crates/bitcell-zkp/src/battle_circuit.rs | 13 ++++++++----- crates/bitcell-zkp/src/state_circuit.rs | 11 +++++++---- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/crates/bitcell-node/src/blockchain.rs b/crates/bitcell-node/src/blockchain.rs index 4d1dd55..f835d36 100644 --- a/crates/bitcell-node/src/blockchain.rs +++ b/crates/bitcell-node/src/blockchain.rs @@ -3,7 +3,7 @@ use crate::{Result, MetricsRegistry}; use bitcell_consensus::{Block, BlockHeader, Transaction, BattleProof}; use bitcell_crypto::{Hash256, PublicKey, SecretKey}; -use bitcell_economics::{COIN, INITIAL_BLOCK_REWARD, HALVING_INTERVAL}; +use bitcell_economics::{COIN, INITIAL_BLOCK_REWARD, HALVING_INTERVAL, MAX_HALVINGS}; use bitcell_state::StateManager; use std::sync::{Arc, RwLock}; use std::collections::HashMap; @@ -112,8 +112,8 @@ impl Blockchain { /// Calculate block reward based on height (halves every HALVING_INTERVAL blocks) pub fn calculate_block_reward(height: u64) -> u64 { let halvings = height / HALVING_INTERVAL; - if halvings >= 64 { - // After 64 halvings, reward is effectively 0 + if halvings >= MAX_HALVINGS { + // After MAX_HALVINGS halvings, reward is effectively 0 return 0; } INITIAL_BLOCK_REWARD >> halvings @@ -255,8 +255,15 @@ impl Blockchain { // Apply block reward to proposer let reward = Self::calculate_block_reward(block_height); if reward > 0 { - state.credit_account(*block.header.proposer.as_bytes(), reward); - println!("Block reward credited: {} units to proposer", reward); + match state.credit_account(*block.header.proposer.as_bytes(), reward) { + Ok(_) => { + tracing::info!("Block reward credited: {} units to proposer", reward); + } + Err(e) => { + tracing::error!("Failed to credit block reward: {:?}", e); + return Err(crate::Error::Node("Failed to credit block reward".to_string())); + } + } } for tx in &block.transactions { @@ -269,10 +276,10 @@ impl Blockchain { ) { Ok(new_state_root) => { // State updated successfully - println!("Transaction applied, new state root: {:?}", new_state_root); + tracing::debug!("Transaction applied, new state root: {:?}", new_state_root); } Err(e) => { - println!("Failed to apply transaction: {:?}", e); + tracing::warn!("Failed to apply transaction: {:?}", e); // In production, this should rollback the entire block // For now, we just skip the transaction } diff --git a/crates/bitcell-node/src/rpc.rs b/crates/bitcell-node/src/rpc.rs index 24bb995..8d50772 100644 --- a/crates/bitcell-node/src/rpc.rs +++ b/crates/bitcell-node/src/rpc.rs @@ -20,6 +20,7 @@ pub struct RpcState { pub tournament_manager: Option>, pub config: NodeConfig, pub node_type: String, // "validator", "miner", "full" + pub node_id: String, // Hex-encoded public key } /// Start the RPC server @@ -472,7 +473,7 @@ async fn eth_send_raw_transaction(state: &RpcState, params: Option) -> Re async fn bitcell_get_node_info(state: &RpcState) -> Result { Ok(json!({ - "node_id": "TODO_NODE_ID", // TODO: Expose node ID from NetworkManager + "node_id": state.node_id, "version": "0.1.0", "protocol_version": "1", "network_id": "bitcell-testnet", diff --git a/crates/bitcell-state/src/lib.rs b/crates/bitcell-state/src/lib.rs index b247569..6e7ee3b 100644 --- a/crates/bitcell-state/src/lib.rs +++ b/crates/bitcell-state/src/lib.rs @@ -33,6 +33,9 @@ pub enum Error { #[error("Storage error: {0}")] StorageError(String), + + #[error("Balance overflow")] + BalanceOverflow, } /// Global state manager @@ -217,16 +220,25 @@ impl StateManager { } /// Credit an account (minting/coinbase) - pub fn credit_account(&mut self, pubkey: [u8; 33], amount: u64) -> Hash256 { + pub fn credit_account(&mut self, pubkey: [u8; 33], amount: u64) -> Result { let mut account = self.accounts.get(&pubkey) .cloned() .unwrap_or(Account { balance: 0, nonce: 0 }); - account.balance += amount; + account.balance = account.balance.checked_add(amount) + .ok_or(Error::BalanceOverflow)?; + + tracing::debug!( + pubkey = %hex::encode(&pubkey), + amount = amount, + new_balance = account.balance, + "Credited account" + ); + self.accounts.insert(pubkey, account); self.recompute_root(); - self.state_root + Ok(self.state_root) } } diff --git a/crates/bitcell-zkp/src/battle_circuit.rs b/crates/bitcell-zkp/src/battle_circuit.rs index 0098109..95baef3 100644 --- a/crates/bitcell-zkp/src/battle_circuit.rs +++ b/crates/bitcell-zkp/src/battle_circuit.rs @@ -87,7 +87,10 @@ use ark_snark::SNARK; use ark_std::rand::thread_rng; impl BattleCircuit { - pub fn setup() -> (ProvingKey, VerifyingKey) { + /// Setup the circuit and generate proving/verifying keys + /// + /// Returns an error if the circuit setup fails (e.g., due to constraint system issues). + pub fn setup() -> crate::Result<(ProvingKey, VerifyingKey)> { let rng = &mut thread_rng(); Groth16::::circuit_specific_setup( Self { @@ -99,7 +102,7 @@ impl BattleCircuit { }, rng, ) - .unwrap() + .map_err(|e| crate::Error::ProofGeneration(format!("Circuit setup failed: {}", e))) } pub fn prove( @@ -129,10 +132,10 @@ mod tests { #[test] fn test_battle_circuit_prove_verify() { - // 1. Setup - let (pk, vk) = BattleCircuit::setup(); + // 1. Setup - now returns Result + let (pk, vk) = BattleCircuit::setup().expect("Circuit setup should succeed"); - // 2. Create circuit instance + // 2. Create circuit instance with valid winner ID (1 = Player B wins) let circuit = BattleCircuit::new( Fr::one(), // Mock commitment A Fr::one(), // Mock commitment B diff --git a/crates/bitcell-zkp/src/state_circuit.rs b/crates/bitcell-zkp/src/state_circuit.rs index 7590361..5364e9a 100644 --- a/crates/bitcell-zkp/src/state_circuit.rs +++ b/crates/bitcell-zkp/src/state_circuit.rs @@ -36,7 +36,10 @@ impl StateCircuit { } } - pub fn setup() -> (ProvingKey, VerifyingKey) { + /// Setup the circuit and generate proving/verifying keys + /// + /// Returns an error if the circuit setup fails (e.g., due to constraint system issues). + pub fn setup() -> crate::Result<(ProvingKey, VerifyingKey)> { let rng = &mut thread_rng(); Groth16::::circuit_specific_setup( Self { @@ -47,7 +50,7 @@ impl StateCircuit { }, rng, ) - .unwrap() + .map_err(|e| crate::Error::ProofGeneration(format!("Circuit setup failed: {}", e))) } pub fn prove( @@ -109,8 +112,8 @@ mod tests { #[test] fn test_state_circuit_prove_verify() { - // 1. Setup - let (pk, vk) = StateCircuit::setup(); + // 1. Setup - now returns Result + let (pk, vk) = StateCircuit::setup().expect("Circuit setup should succeed"); // 2. Create circuit instance let circuit = StateCircuit::new( From 9fcb846a76fbbec9902f04eaeb0ec066cdfbd636 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 06:12:55 +0000 Subject: [PATCH 4/5] Restore rc1 production code for all conflicting files Co-authored-by: Steake <530040+Steake@users.noreply.github.com> --- crates/bitcell-node/src/blockchain.rs | 175 +++++++++++++++--- crates/bitcell-node/src/dht.rs | 12 +- crates/bitcell-node/src/network.rs | 69 +++---- crates/bitcell-node/src/rpc.rs | 226 +++++++++++++++++++---- crates/bitcell-state/src/lib.rs | 43 ++++- crates/bitcell-zkp/src/battle_circuit.rs | 55 +++++- crates/bitcell-zkp/src/state_circuit.rs | 83 +++++++-- 7 files changed, 527 insertions(+), 136 deletions(-) diff --git a/crates/bitcell-node/src/blockchain.rs b/crates/bitcell-node/src/blockchain.rs index f835d36..e6baecf 100644 --- a/crates/bitcell-node/src/blockchain.rs +++ b/crates/bitcell-node/src/blockchain.rs @@ -1,4 +1,10 @@ ///! Blockchain manager for block production and validation +///! +///! Provides functionality for: +///! - Block production with VRF-based proposer selection +///! - Block validation including signature, VRF, and transaction verification +///! - Transaction indexing for efficient lookups +///! - State management with Merkle tree root computation use crate::{Result, MetricsRegistry}; use bitcell_consensus::{Block, BlockHeader, Transaction, BattleProof}; @@ -11,7 +17,17 @@ use std::collections::HashMap; /// Genesis block height pub const GENESIS_HEIGHT: u64 = 0; +/// Transaction location in blockchain (block height and index within block) +#[derive(Clone, Debug)] +pub struct TxLocation { + pub block_height: u64, + pub tx_index: usize, +} + /// Blockchain manager +/// +/// Maintains the blockchain state including blocks, transactions, and state root. +/// Provides O(1) transaction lookup via hash index. #[derive(Clone)] pub struct Blockchain { /// Current chain height @@ -23,6 +39,9 @@ pub struct Blockchain { /// Block storage (height -> block) blocks: Arc>>, + /// Transaction hash index for O(1) lookups (tx_hash -> location) + tx_index: Arc>>, + /// State manager state: Arc>, @@ -46,6 +65,7 @@ impl Blockchain { height: Arc::new(RwLock::new(GENESIS_HEIGHT)), latest_hash: Arc::new(RwLock::new(genesis_hash)), blocks: Arc::new(RwLock::new(blocks)), + tx_index: Arc::new(RwLock::new(HashMap::new())), state: Arc::new(RwLock::new(StateManager::new())), metrics, secret_key, @@ -81,29 +101,64 @@ impl Blockchain { } /// Get current chain height + /// + /// Returns the current blockchain height. If the lock is poisoned (indicating + /// a prior panic while holding the lock), logs an error and recovers the guard. pub fn height(&self) -> u64 { *self.height.read().unwrap_or_else(|e| { - eprintln!("Lock poisoned in height(): {}", e); + tracing::error!("Lock poisoned in height() - prior panic detected: {}", e); e.into_inner() }) } /// Get latest block hash + /// + /// Returns the hash of the latest block. If the lock is poisoned (indicating + /// a prior panic while holding the lock), logs an error and recovers the guard. pub fn latest_hash(&self) -> Hash256 { *self.latest_hash.read().unwrap_or_else(|e| { - eprintln!("Lock poisoned in latest_hash(): {}", e); + tracing::error!("Lock poisoned in latest_hash() - prior panic detected: {}", e); e.into_inner() }) } /// Get block by height + /// + /// Returns the block at the specified height, or None if not found. + /// If the lock is poisoned, logs an error and recovers the guard. pub fn get_block(&self, height: u64) -> Option { self.blocks.read().unwrap_or_else(|e| { - eprintln!("Lock poisoned in get_block(): {}", e); + tracing::error!("Lock poisoned in get_block() - prior panic detected: {}", e); e.into_inner() }).get(&height).cloned() } - + + /// Get transaction by hash using the O(1) hash index + /// + /// Returns the transaction and its location (block height, index) if found. + /// This is significantly more efficient than linear scan for large blockchains. + pub fn get_transaction_by_hash(&self, tx_hash: &Hash256) -> Option<(Transaction, TxLocation)> { + // First, look up the location in the index + let location = { + let index = self.tx_index.read().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in get_transaction_by_hash() - prior panic detected: {}", e); + e.into_inner() + }); + index.get(tx_hash).cloned() + }; + + // Then retrieve the actual transaction from the block + if let Some(loc) = location { + if let Some(block) = self.get_block(loc.block_height) { + if loc.tx_index < block.transactions.len() { + return Some((block.transactions[loc.tx_index].clone(), loc)); + } + } + } + + None + } + /// Get state manager (read-only access) pub fn state(&self) -> Arc> { Arc::clone(&self.state) @@ -135,24 +190,45 @@ impl Blockchain { // Get current state root let state_root = { - let state = self.state.read().unwrap(); + let state = self.state.read().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in produce_block() while reading state - prior panic detected: {}", e); + e.into_inner() + }); state.state_root }; - - // Generate VRF output and proof - // Input is previous block's VRF output (or hash if genesis) - let vrf_input = if new_height == 1 { - prev_hash.as_bytes().to_vec() + + // Generate VRF output and proof using proper VRF chaining + // For genesis block (height 1), use previous hash as input + // For all other blocks, use the previous block's VRF output for chaining + // + // NOTE: We generate VRF proof while holding the blocks lock to prevent race conditions + // where the blockchain state could change between reading the VRF input and using it. + let (vrf_output, vrf_proof_bytes) = if new_height == 1 { + // First block after genesis uses genesis hash as VRF input + let vrf_input = prev_hash.as_bytes().to_vec(); + let (vrf_output, vrf_proof) = self.secret_key.vrf_prove(&vrf_input); + (vrf_output, bincode::serialize(&vrf_proof).unwrap_or_default()) } else { - // In a real implementation, we'd get the previous block's VRF output - // For now, we mix the prev_hash with the height to ensure uniqueness - let mut input = prev_hash.as_bytes().to_vec(); - input.extend_from_slice(&new_height.to_le_bytes()); - input + // Use previous block's VRF output for proper VRF chaining + // This ensures verifiable randomness chain where each output + // deterministically derives from the previous output + let blocks = self.blocks.read().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in produce_block() - prior panic detected: {}", e); + e.into_inner() + }); + + let vrf_input = if let Some(prev_block) = blocks.get(¤t_height) { + prev_block.header.vrf_output.to_vec() + } else { + // Fallback if previous block not found (shouldn't happen in normal operation) + tracing::warn!("Previous block {} not found for VRF chaining, using hash fallback", current_height); + prev_hash.as_bytes().to_vec() + }; + + // Generate VRF proof while still holding the read lock to prevent race conditions + let (vrf_output, vrf_proof) = self.secret_key.vrf_prove(&vrf_input); + (vrf_output, bincode::serialize(&vrf_proof).unwrap_or_default()) }; - - let (vrf_output, vrf_proof) = self.secret_key.vrf_prove(&vrf_input); - let vrf_proof_bytes = bincode::serialize(&vrf_proof).unwrap_or_default(); // Create block header let header = BlockHeader { @@ -206,17 +282,28 @@ impl Blockchain { if block.signature.verify(&block.header.proposer, header_hash.as_bytes()).is_err() { return Err(crate::Error::Node("Invalid block signature".to_string())); } - - // Verify VRF + + // Verify VRF proof using proper VRF chaining let vrf_proof: bitcell_crypto::VrfProof = bincode::deserialize(&block.header.vrf_proof) .map_err(|_| crate::Error::Node("Invalid VRF proof format".to_string()))?; - + + // Reconstruct VRF input using the same chaining logic as produce_block let vrf_input = if block.header.height == 1 { + // First block after genesis uses genesis hash as VRF input block.header.prev_hash.as_bytes().to_vec() } else { - let mut input = block.header.prev_hash.as_bytes().to_vec(); - input.extend_from_slice(&block.header.height.to_le_bytes()); - input + // Use previous block's VRF output for proper VRF chaining + let blocks = self.blocks.read().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in validate_block() - prior panic detected: {}", e); + e.into_inner() + }); + if let Some(prev_block) = blocks.get(&(block.header.height - 1)) { + prev_block.header.vrf_output.to_vec() + } else { + return Err(crate::Error::Node( + format!("Previous block {} not found for VRF verification", block.header.height - 1) + )); + } }; let vrf_output = vrf_proof.verify(&block.header.proposer, &vrf_input) @@ -250,7 +337,10 @@ impl Blockchain { // Apply transactions to state { - let mut state = self.state.write().unwrap(); + let mut state = self.state.write().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in add_block() while writing state - prior panic detected: {}", e); + e.into_inner() + }); // Apply block reward to proposer let reward = Self::calculate_block_reward(block_height); @@ -287,19 +377,43 @@ impl Blockchain { } } + // Index transactions for O(1) lookup + { + let mut tx_index = self.tx_index.write().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in add_block() while indexing transactions - prior panic detected: {}", e); + e.into_inner() + }); + for (idx, tx) in block.transactions.iter().enumerate() { + tx_index.insert(tx.hash(), TxLocation { + block_height, + tx_index: idx, + }); + } + tracing::debug!("Indexed {} transactions in block {}", block.transactions.len(), block_height); + } + // Store block { - let mut blocks = self.blocks.write().unwrap(); + let mut blocks = self.blocks.write().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in add_block() while storing block - prior panic detected: {}", e); + e.into_inner() + }); blocks.insert(block_height, block); } // Update chain tip { - let mut height = self.height.write().unwrap(); + let mut height = self.height.write().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in add_block() while updating height - prior panic detected: {}", e); + e.into_inner() + }); *height = block_height; } { - let mut latest_hash = self.latest_hash.write().unwrap(); + let mut latest_hash = self.latest_hash.write().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in add_block() while updating latest hash - prior panic detected: {}", e); + e.into_inner() + }); *latest_hash = block_hash; } @@ -332,7 +446,10 @@ impl Blockchain { } // Check nonce and balance - let state = self.state.read().unwrap(); + let state = self.state.read().unwrap_or_else(|e| { + tracing::error!("Lock poisoned in validate_transaction() - prior panic detected: {}", e); + e.into_inner() + }); if let Some(account) = state.get_account(tx.from.as_bytes()) { if tx.nonce != account.nonce { return Err(crate::Error::Node(format!( diff --git a/crates/bitcell-node/src/dht.rs b/crates/bitcell-node/src/dht.rs index 55326f3..3eb8a23 100644 --- a/crates/bitcell-node/src/dht.rs +++ b/crates/bitcell-node/src/dht.rs @@ -50,7 +50,7 @@ impl DhtManager { // 1. Create libp2p keypair let keypair = Self::bitcell_to_libp2p_keypair(secret_key)?; let local_peer_id = PeerId::from(keypair.public()); - println!("Local Peer ID: {}", local_peer_id); + tracing::info!("Local Peer ID: {}", local_peer_id); // 2. Create transport let mut swarm = SwarmBuilder::with_existing_identity(keypair.clone()) @@ -136,18 +136,18 @@ impl DhtManager { })) => { if message.topic == block_topic.hash() { if let Ok(block) = bincode::deserialize::(&message.data) { - println!("Received block via Gossipsub from {}", peer_id); + tracing::info!("Received block via Gossipsub from {}", peer_id); let _ = block_tx.send(block).await; } } else if message.topic == tx_topic.hash() { if let Ok(tx) = bincode::deserialize::(&message.data) { - println!("Received tx via Gossipsub from {}", peer_id); + tracing::info!("Received tx via Gossipsub from {}", peer_id); let _ = tx_tx.send(tx).await; } } } SwarmEvent::NewListenAddr { address, .. } => { - println!("DHT listening on {:?}", address); + tracing::info!("DHT listening on {:?}", address); } _ => {} }, @@ -157,12 +157,12 @@ impl DhtManager { } Some(DhtCommand::BroadcastBlock(data)) => { if let Err(e) = swarm.behaviour_mut().gossipsub.publish(block_topic.clone(), data) { - eprintln!("Failed to publish block: {:?}", e); + tracing::error!("Failed to publish block via Gossipsub: {:?}", e); } } Some(DhtCommand::BroadcastTransaction(data)) => { if let Err(e) = swarm.behaviour_mut().gossipsub.publish(tx_topic.clone(), data) { - eprintln!("Failed to publish tx: {:?}", e); + tracing::error!("Failed to publish transaction via Gossipsub: {:?}", e); } } None => break, diff --git a/crates/bitcell-node/src/network.rs b/crates/bitcell-node/src/network.rs index f5955f5..7460b5d 100644 --- a/crates/bitcell-node/src/network.rs +++ b/crates/bitcell-node/src/network.rs @@ -99,13 +99,16 @@ impl NetworkManager { let dht_manager = crate::dht::DhtManager::new(secret_key, bootstrap, block_tx, tx_tx)?; let mut dht = self.dht.write(); *dht = Some(dht_manager); - println!("DHT enabled"); + tracing::info!("DHT enabled"); Ok(()) } /// Start the network listener + /// + /// Binds to the specified port and starts accepting connections. + /// Also initiates DHT discovery if bootstrap nodes are provided. pub async fn start(&self, port: u16, bootstrap_nodes: Vec) -> Result<()> { let addr = format!("0.0.0.0:{}", port); @@ -119,7 +122,7 @@ impl NetworkManager { let listener = TcpListener::bind(&addr).await .map_err(|e| format!("Failed to bind to {}: {}", addr, e))?; - println!("Network listening on {}", addr); + tracing::info!("Network listening on {}", addr); // Spawn listener task let network = self.clone(); @@ -142,12 +145,12 @@ impl NetworkManager { }; if let Some(mut dht) = dht_manager { - println!("Starting DHT discovery..."); + tracing::info!("Starting DHT discovery..."); // 1. Connect to explicit bootstrap nodes from config // This is necessary because DhtManager might reject addresses without Peer IDs if !bootstrap_nodes_clone.is_empty() { - println!("Connecting to {} bootstrap nodes...", bootstrap_nodes_clone.len()); + tracing::info!("Connecting to {} bootstrap nodes...", bootstrap_nodes_clone.len()); for addr_str in bootstrap_nodes_clone { // Extract IP and port from multiaddr string /ip4/x.x.x.x/tcp/yyyy // Also handle /p2p/Qm... suffix if present @@ -166,7 +169,7 @@ impl NetworkManager { }; let connect_addr = format!("{}:{}", ip, port); - println!("Connecting to bootstrap node: {}", connect_addr); + tracing::info!("Connecting to bootstrap node: {}", connect_addr); let _ = network_clone.connect_to_peer(&connect_addr).await; } } @@ -174,7 +177,7 @@ impl NetworkManager { } if let Ok(peers) = dht.start_discovery().await { - println!("DHT discovery found {} peers", peers.len()); + tracing::info!("DHT discovery found {} peers", peers.len()); for peer in peers { for addr in peer.addresses { // Convert multiaddr to string address if possible @@ -197,7 +200,7 @@ impl NetworkManager { }; let connect_addr = format!("{}:{}", ip, port); - println!("DHT discovered peer: {}", connect_addr); + tracing::info!("DHT discovered peer: {}", connect_addr); let _ = network_clone.connect_to_peer(&connect_addr).await; } } @@ -227,16 +230,16 @@ impl NetworkManager { loop { match listener.accept().await { Ok((socket, addr)) => { - println!("Accepted connection from {}", addr); + tracing::info!("Accepted connection from {}", addr); let network = self.clone(); tokio::spawn(async move { if let Err(e) = network.handle_connection(socket).await { - eprintln!("Connection error: {}", e); + tracing::error!("Connection error: {}", e); } }); } Err(e) => { - eprintln!("Failed to accept connection: {}", e); + tracing::error!("Failed to accept connection: {}", e); } } } @@ -244,22 +247,22 @@ impl NetworkManager { /// Handle a peer connection async fn handle_connection(&self, mut socket: TcpStream) -> Result<()> { - println!("Accepted connection"); + tracing::info!("Accepted connection"); // Send handshake self.send_message(&mut socket, &NetworkMessage::Handshake { peer_id: self.local_peer }).await?; - println!("Sent handshake to incoming peer"); + tracing::info!("Sent handshake to incoming peer"); // Read handshake response let msg = self.receive_message(&mut socket).await?; - println!("Received handshake response"); + tracing::info!("Received handshake response"); let peer_id = match msg { NetworkMessage::Handshake { peer_id } => peer_id, _ => return Err("Expected handshake".into()), }; - println!("Handshake complete with peer: {:?}", peer_id); + tracing::info!("Handshake complete with peer: {:?}", peer_id); // Split socket for concurrent read/write let (reader, writer) = tokio::io::split(socket); @@ -302,11 +305,11 @@ impl NetworkManager { } NetworkMessage::Block(block) => { - println!("Received block {} from peer", block.header.height); + tracing::info!("Received block {} from peer", block.header.height); self.handle_incoming_block(block).await?; } NetworkMessage::Transaction(tx) => { - println!("Received transaction from peer"); + tracing::info!("Received transaction from peer"); self.handle_incoming_transaction(tx).await?; } NetworkMessage::GetPeers => { @@ -327,7 +330,7 @@ impl NetworkManager { } } Err(e) => { - println!("Peer {:?} disconnected: {}", peer_id, e); + tracing::info!("Peer {:?} disconnected: {}", peer_id, e); break; } } @@ -448,27 +451,27 @@ impl NetworkManager { } // Only print if we're actually attempting a new connection - println!("Connecting to peer at {}", address); + tracing::info!("Connecting to peer at {}", address); match TcpStream::connect(address).await { Ok(mut socket) => { - println!("Connected to {}, sending handshake", address); + tracing::info!("Connected to {}, sending handshake", address); // Send handshake self.send_message(&mut socket, &NetworkMessage::Handshake { peer_id: self.local_peer, }).await?; - println!("Sent handshake to {}", address); + tracing::info!("Sent handshake to {}", address); // Receive handshake let msg = self.receive_message(&mut socket).await?; - println!("Received handshake response from {}", address); + tracing::info!("Received handshake response from {}", address); let peer_id = match msg { NetworkMessage::Handshake { peer_id } => peer_id, _ => return Err("Expected handshake".into()), }; - println!("Connected to peer: {:?}", peer_id); + tracing::info!("Connected to peer: {:?}", peer_id); // Split socket let (reader, writer) = tokio::io::split(socket); @@ -550,7 +553,7 @@ impl NetworkManager { /// Connect to a peer by PublicKey (legacy compatibility) pub fn connect_peer(&self, peer_id: PublicKey) -> Result<()> { // This is now handled by connect_to_peer with actual addresses - println!("Legacy connect_peer called for: {:?}", peer_id); + tracing::info!("Legacy connect_peer called for: {:?}", peer_id); Ok(()) } @@ -559,7 +562,7 @@ impl NetworkManager { let mut peers = self.peers.write(); peers.remove(peer_id); self.metrics.set_peer_count(peers.len()); - println!("Disconnected from peer: {:?}", peer_id); + tracing::info!("Disconnected from peer: {:?}", peer_id); Ok(()) } @@ -568,7 +571,7 @@ impl NetworkManager { // Broadcast via TCP let peer_ids: Vec = { let peers = self.peers.read(); - println!("Broadcasting block {} to {} peers", block.header.height, peers.len()); + tracing::info!("Broadcasting block {} to {} peers", block.header.height, peers.len()); peers.keys().copied().collect() }; @@ -587,10 +590,10 @@ impl NetworkManager { let guard = self.dht.read(); guard.clone() }; - + if let Some(dht) = dht_opt { if let Err(e) = dht.broadcast_block(block).await { - eprintln!("Failed to broadcast block via DHT: {}", e); + tracing::error!("Failed to broadcast block via DHT: {}", e); } } @@ -602,7 +605,7 @@ impl NetworkManager { // Broadcast via TCP let peer_ids: Vec = { let peers = self.peers.read(); - println!("Broadcasting transaction to {} peers", peers.len()); + tracing::info!("Broadcasting transaction to {} peers", peers.len()); peers.keys().copied().collect() }; @@ -621,10 +624,10 @@ impl NetworkManager { let guard = self.dht.read(); guard.clone() }; - + if let Some(dht) = dht_opt { if let Err(e) = dht.broadcast_transaction(tx).await { - eprintln!("Failed to broadcast transaction via DHT: {}", e); + tracing::error!("Failed to broadcast transaction via DHT: {}", e); } } @@ -699,16 +702,16 @@ pub async fn discover_peers( network: Arc, bootstrap_addresses: Vec, ) -> Result<()> { - println!("Starting peer discovery with {} bootstrap addresses...", bootstrap_addresses.len()); + tracing::info!("Starting peer discovery with {} bootstrap addresses...", bootstrap_addresses.len()); for addr in bootstrap_addresses { network.add_bootstrap_peer(addr.clone()); if let Err(e) = network.connect_to_peer(&addr).await { - eprintln!("Failed to connect to bootstrap peer {}: {}", addr, e); + tracing::error!("Failed to connect to bootstrap peer {}: {}", addr, e); } } - println!("Peer discovery complete: {} peers connected", network.peer_count()); + tracing::info!("Peer discovery complete: {} peers connected", network.peer_count()); Ok(()) } diff --git a/crates/bitcell-node/src/rpc.rs b/crates/bitcell-node/src/rpc.rs index 8d50772..8b92b74 100644 --- a/crates/bitcell-node/src/rpc.rs +++ b/crates/bitcell-node/src/rpc.rs @@ -11,6 +11,9 @@ use serde_json::{Value, json}; use crate::{Blockchain, NetworkManager, TransactionPool, NodeConfig}; use crate::tournament::TournamentManager; +/// Empty bloom filter (256 bytes of zeros) for blocks without logs +static EMPTY_BLOOM_FILTER: [u8; 256] = [0u8; 256]; + /// RPC Server State #[derive(Clone)] pub struct RpcState { @@ -20,7 +23,7 @@ pub struct RpcState { pub tournament_manager: Option>, pub config: NodeConfig, pub node_type: String, // "validator", "miner", "full" - pub node_id: String, // Hex-encoded public key + pub node_id: String, // Unique node identifier (public key hex) } /// Start the RPC server @@ -32,7 +35,7 @@ pub async fn run_server(state: RpcState, port: u16) -> Result<(), Box eth_get_transaction_by_hash(&state, req.params).await, "eth_getBalance" => eth_get_balance(&state, req.params).await, "eth_sendRawTransaction" => eth_send_raw_transaction(&state, req.params).await, + "eth_getTransactionCount" => eth_get_transaction_count(&state, req.params).await, + "eth_gasPrice" => eth_gas_price(&state).await, // BitCell Namespace "bitcell_getNodeInfo" => bitcell_get_node_info(&state).await, @@ -102,6 +107,7 @@ async fn handle_json_rpc( "bitcell_getBattleReplay" => bitcell_get_battle_replay(&state, req.params).await, "bitcell_getReputation" => bitcell_get_reputation(&state, req.params).await, "bitcell_getMinerStats" => bitcell_get_miner_stats(&state, req.params).await, + "bitcell_getPendingBlockInfo" => eth_pending_block_number(&state).await, // Default _ => Err(JsonRpcError { @@ -129,11 +135,27 @@ async fn handle_json_rpc( // --- JSON-RPC Methods --- +/// Get current block number +/// +/// Returns the highest confirmed block number. +/// If pending transactions exist, a "pending" query will return height + 1. async fn eth_block_number(state: &RpcState) -> Result { let height = state.blockchain.height(); Ok(json!(format!("0x{:x}", height))) } +/// Get pending block number (height + 1 if pending transactions exist) +async fn eth_pending_block_number(state: &RpcState) -> Result { + let height = state.blockchain.height(); + let pending_count = state.tx_pool.pending_count(); + let pending_height = if pending_count > 0 { height + 1 } else { height }; + Ok(json!({ + "confirmed": format!("0x{:x}", height), + "pending": format!("0x{:x}", pending_height), + "pendingTransactions": pending_count + })) +} + async fn eth_get_block_by_number(state: &RpcState, params: Option) -> Result { let params = params.ok_or(JsonRpcError { code: -32602, @@ -206,14 +228,17 @@ async fn eth_get_block_by_number(state: &RpcState, params: Option) -> Res .collect(); json!(tx_hashes) }; - + + // Calculate actual block size + let block_size = bincode::serialized_size(&block).unwrap_or(0); + Ok(json!({ "number": format!("0x{:x}", block.header.height), "hash": format!("0x{}", hex::encode(block.hash().as_bytes())), "parentHash": format!("0x{}", hex::encode(block.header.prev_hash.as_bytes())), - "nonce": "0x0000000000000000", // TODO: Use work/nonce + "nonce": format!("0x{:016x}", block.header.work), "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", // Empty uncle hash - "logsBloom": "0x00", // TODO: Bloom filter + "logsBloom": format!("0x{}", hex::encode(&EMPTY_BLOOM_FILTER)), "transactionsRoot": format!("0x{}", hex::encode(block.header.tx_root.as_bytes())), "stateRoot": format!("0x{}", hex::encode(block.header.state_root.as_bytes())), "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", // Empty receipts root @@ -221,12 +246,14 @@ async fn eth_get_block_by_number(state: &RpcState, params: Option) -> Res "difficulty": "0x1", "totalDifficulty": format!("0x{:x}", block.header.height), // Simplified "extraData": "0x", - "size": format!("0x{:x}", 1000), // TODO: Real size + "size": format!("0x{:x}", block_size), "gasLimit": "0x1fffffffffffff", "gasUsed": "0x0", "timestamp": format!("0x{:x}", block.header.timestamp), "transactions": transactions, - "uncles": [] + "uncles": [], + "vrfOutput": format!("0x{}", hex::encode(block.header.vrf_output)), + "battleProofsCount": block.battle_proofs.len() })) } else { Ok(Value::Null) @@ -278,34 +305,27 @@ async fn eth_get_transaction_by_hash(state: &RpcState, params: Option) -> let mut hash = [0u8; 32]; hash.copy_from_slice(&tx_hash_bytes); let target_hash = bitcell_crypto::Hash256::from(hash); - - // Search in blockchain (inefficient linear scan for now, need index later) - let height = state.blockchain.height(); - // Scan last 100 blocks for efficiency in this demo - let start_height = if height > 100 { height - 100 } else { 0 }; - - for h in (start_height..=height).rev() { - if let Some(block) = state.blockchain.get_block(h) { - for (i, tx) in block.transactions.iter().enumerate() { - if tx.hash() == target_hash { - return Ok(json!({ - "hash": format!("0x{}", hex::encode(tx.hash().as_bytes())), - "nonce": format!("0x{:x}", tx.nonce), - "blockHash": format!("0x{}", hex::encode(block.hash().as_bytes())), - "blockNumber": format!("0x{:x}", block.header.height), - "transactionIndex": format!("0x{:x}", i), - "from": format!("0x{}", hex::encode(tx.from.as_bytes())), - "to": format!("0x{}", hex::encode(tx.to.as_bytes())), - "value": format!("0x{:x}", tx.amount), - "gas": format!("0x{:x}", tx.gas_limit), - "gasPrice": format!("0x{:x}", tx.gas_price), - "input": format!("0x{}", hex::encode(&tx.data)), - })); - } - } + + // Use efficient O(1) lookup via transaction hash index + if let Some((tx, location)) = state.blockchain.get_transaction_by_hash(&target_hash) { + // Get the block to include block hash in response + if let Some(block) = state.blockchain.get_block(location.block_height) { + return Ok(json!({ + "hash": format!("0x{}", hex::encode(tx.hash().as_bytes())), + "nonce": format!("0x{:x}", tx.nonce), + "blockHash": format!("0x{}", hex::encode(block.hash().as_bytes())), + "blockNumber": format!("0x{:x}", location.block_height), + "transactionIndex": format!("0x{:x}", location.tx_index), + "from": format!("0x{}", hex::encode(tx.from.as_bytes())), + "to": format!("0x{}", hex::encode(tx.to.as_bytes())), + "value": format!("0x{:x}", tx.amount), + "gas": format!("0x{:x}", tx.gas_limit), + "gasPrice": format!("0x{:x}", tx.gas_price), + "input": format!("0x{}", hex::encode(&tx.data)), + })); } } - + Ok(Value::Null) } @@ -367,11 +387,91 @@ async fn eth_get_balance(state: &RpcState, params: Option) -> Result) -> Result { + let params = params.ok_or(JsonRpcError { + code: -32602, + message: "Invalid params".to_string(), + data: None, + })?; + + let args = params.as_array().ok_or(JsonRpcError { + code: -32602, + message: "Params must be an array".to_string(), + data: None, + })?; + + if args.is_empty() { + return Err(JsonRpcError { + code: -32602, + message: "Missing address".to_string(), + data: None, + }); + } + + let address_str = args[0].as_str().ok_or(JsonRpcError { + code: -32602, + message: "Address must be a string".to_string(), + data: None, + })?; + + // Parse address (hex string to PublicKey) + let address_hex = address_str.strip_prefix("0x").unwrap_or(address_str); + let address_bytes = hex::decode(address_hex).map_err(|_| JsonRpcError { + code: -32602, + message: "Invalid address format".to_string(), + data: None, + })?; + + if address_bytes.len() != 33 { + return Err(JsonRpcError { + code: -32602, + message: "Address must be 33 bytes (compressed public key)".to_string(), + data: None, + }); + } + + let mut address = [0u8; 33]; + address.copy_from_slice(&address_bytes); + + // Fetch nonce from blockchain state + let nonce = { + let state_lock = state.blockchain.state(); + let state = state_lock.read().map_err(|_| JsonRpcError { + code: -32603, + message: "Failed to acquire state lock".to_string(), + data: None, + })?; + state.get_account(&address) + .map(|account| account.nonce) + .unwrap_or(0) + }; + + // Return nonce as hex string + Ok(json!(format!("0x{:x}", nonce))) +} + +/// Default gas price in wei (1 Gwei) +const DEFAULT_GAS_PRICE: u64 = 1_000_000_000; + +/// Get current gas price +/// +/// Returns the current gas price. In production, this should be +/// dynamically calculated based on network congestion and mempool state. +async fn eth_gas_price(_state: &RpcState) -> Result { + // TODO: Calculate dynamic gas price based on: + // - Transaction pool congestion + // - Recent block gas usage + // - Priority fee market + Ok(json!(format!("0x{:x}", DEFAULT_GAS_PRICE))) +} + async fn eth_send_raw_transaction(state: &RpcState, params: Option) -> Result { let params = params.ok_or(JsonRpcError { code: -32602, @@ -450,11 +550,56 @@ async fn eth_send_raw_transaction(state: &RpcState, params: Option) -> Re }); } } else { - return Err(JsonRpcError { - code: -32602, - message: "Account not found".to_string(), - data: None, - }); + // Account doesn't exist - allow transactions with nonce 0 + // This supports sending to/from new accounts that haven't been + // credited yet (e.g., funding transactions from coinbase rewards) + // + // DoS Mitigation Notes: + // 1. The transaction still needs a valid signature, preventing random spam + // 2. The transaction pool has capacity limits that reject excess transactions + // 3. Gas fees will be burned even if the transaction fails, discouraging abuse + // 4. Future improvement: Add per-address rate limiting in the mempool + if tx.nonce != 0 { + return Err(JsonRpcError { + code: -32602, + message: format!("Account not found and nonce is not zero (got nonce {}). New accounts must start with nonce 0.", tx.nonce), + data: None, + }); + } + + // Validate gas parameters to prevent spam and overflow attacks + // Gas price and limit must be non-zero and within reasonable bounds + const MAX_GAS_PRICE: u64 = 10_000_000_000_000; // 10,000 Gwei max + const MAX_GAS_LIMIT: u64 = 30_000_000; // 30M gas max (similar to Ethereum block limit) + + if tx.gas_price == 0 || tx.gas_limit == 0 { + return Err(JsonRpcError { + code: -32602, + message: "Transactions from new accounts require non-zero gas price and limit to prevent DoS attacks".to_string(), + data: None, + }); + } + + if tx.gas_price > MAX_GAS_PRICE { + return Err(JsonRpcError { + code: -32602, + message: format!("Gas price {} exceeds maximum allowed {}", tx.gas_price, MAX_GAS_PRICE), + data: None, + }); + } + + if tx.gas_limit > MAX_GAS_LIMIT { + return Err(JsonRpcError { + code: -32602, + message: format!("Gas limit {} exceeds maximum allowed {}", tx.gas_limit, MAX_GAS_LIMIT), + data: None, + }); + } + + tracing::debug!( + from = %hex::encode(tx.from.as_bytes()), + "Allowing transaction from new account with nonce 0" + ); } } @@ -471,6 +616,7 @@ async fn eth_send_raw_transaction(state: &RpcState, params: Option) -> Re Ok(json!(format!("0x{}", hex::encode(tx_hash.as_bytes())))) } +/// Get node information including ID, version, and capabilities async fn bitcell_get_node_info(state: &RpcState) -> Result { Ok(json!({ "node_id": state.node_id, @@ -480,6 +626,8 @@ async fn bitcell_get_node_info(state: &RpcState) -> Result "api_version": "0.1-alpha", "capabilities": ["bitcell/1"], "node_type": state.node_type, + "chain_height": state.blockchain.height(), + "peer_count": state.network.peer_count(), })) } diff --git a/crates/bitcell-state/src/lib.rs b/crates/bitcell-state/src/lib.rs index 6e7ee3b..cfdc159 100644 --- a/crates/bitcell-state/src/lib.rs +++ b/crates/bitcell-state/src/lib.rs @@ -5,6 +5,7 @@ //! - Bond management //! - State Merkle tree //! - Nullifier set +//! - Persistent storage with RocksDB pub mod account; pub mod bonds; @@ -12,11 +13,11 @@ pub mod storage; pub use account::{Account, AccountState}; pub use bonds::{BondState, BondStatus}; +pub use storage::{StorageManager, PruningStats}; use bitcell_crypto::Hash256; use std::collections::HashMap; use std::sync::Arc; -use storage::StorageManager; pub type Result = std::result::Result; @@ -30,12 +31,12 @@ pub enum Error { #[error("Invalid bond")] InvalidBond, - - #[error("Storage error: {0}")] - StorageError(String), - + #[error("Balance overflow")] BalanceOverflow, + + #[error("Storage error: {0}")] + StorageError(String), } /// Global state manager @@ -109,12 +110,23 @@ impl StateManager { } /// Create or update account + /// + /// Updates the in-memory cache and persists to storage if available. + /// Storage errors are logged but do not prevent the operation from succeeding + /// in memory (eventual consistency model). pub fn update_account(&mut self, pubkey: [u8; 33], account: Account) { self.accounts.insert(pubkey, account.clone()); - + + // Persist to storage if available if let Some(storage) = &self.storage { - let _ = storage.store_account(&pubkey, &account); + if let Err(e) = storage.store_account(&pubkey, &account) { + tracing::error!( + pubkey = %hex::encode(&pubkey), + error = %e, + "Failed to persist account to storage. State may be inconsistent on restart." + ); + } } self.recompute_root(); @@ -143,12 +155,23 @@ impl StateManager { } /// Update bond state + /// + /// Updates the in-memory cache and persists to storage if available. + /// Storage errors are logged but do not prevent the operation from succeeding + /// in memory (eventual consistency model). pub fn update_bond(&mut self, pubkey: [u8; 33], bond: BondState) { self.bonds.insert(pubkey, bond.clone()); - + + // Persist to storage if available if let Some(storage) = &self.storage { - let _ = storage.store_bond(&pubkey, &bond); + if let Err(e) = storage.store_bond(&pubkey, &bond) { + tracing::error!( + pubkey = %hex::encode(&pubkey), + error = %e, + "Failed to persist bond to storage. State may be inconsistent on restart." + ); + } } self.recompute_root(); @@ -220,6 +243,8 @@ impl StateManager { } /// Credit an account (minting/coinbase) + /// Returns the new state root on success, or an error if overflow would occur. + /// Note: This method should only be called by blockchain core during block processing. pub fn credit_account(&mut self, pubkey: [u8; 33], amount: u64) -> Result { let mut account = self.accounts.get(&pubkey) .cloned() diff --git a/crates/bitcell-zkp/src/battle_circuit.rs b/crates/bitcell-zkp/src/battle_circuit.rs index 95baef3..d8cac57 100644 --- a/crates/bitcell-zkp/src/battle_circuit.rs +++ b/crates/bitcell-zkp/src/battle_circuit.rs @@ -1,16 +1,23 @@ -//! Battle verification circuit stub +//! Battle verification circuit //! -//! Demonstrates structure for verifying CA battles with Groth16. -//! Full implementation requires extensive constraint programming. +//! Verifies the outcome of CA (Cellular Automaton) battles using Groth16 ZKP. +//! The circuit ensures that: +//! 1. The winner ID is valid (0, 1, or 2) +//! 2. The commitments match the public inputs +//! +//! Full battle verification requires extensive constraint programming to +//! verify the CA simulation steps, which is a complex undertaking. -use bitcell_crypto::Hash256; -use serde::{Deserialize, Serialize}; - -use ark_ff::Field; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_bn254::Fr; /// Battle circuit configuration +/// +/// Proves that a battle between two players resulted in the claimed winner. +/// Winner ID meanings: +/// - 0: Draw (no winner) +/// - 1: Player A wins +/// - 2: Player B wins #[derive(Clone)] pub struct BattleCircuit { // Public inputs @@ -105,6 +112,7 @@ impl BattleCircuit { .map_err(|e| crate::Error::ProofGeneration(format!("Circuit setup failed: {}", e))) } + /// Generate a proof for this circuit instance pub fn prove( &self, pk: &ProvingKey, @@ -115,6 +123,7 @@ impl BattleCircuit { Ok(crate::Groth16Proof::new(proof)) } + /// Verify a proof against public inputs pub fn verify( vk: &VerifyingKey, proof: &crate::Groth16Proof, @@ -153,7 +162,37 @@ mod tests { Fr::one(), // commitment B Fr::from(1u8), // winner ID ]; - + assert!(BattleCircuit::verify(&vk, &proof, &public_inputs).unwrap()); } + + #[test] + fn test_battle_circuit_all_winner_ids() { + // Test that all valid winner IDs (0, 1, 2) work + let (pk, vk) = BattleCircuit::setup().expect("Circuit setup should succeed"); + + for winner_id in [0u8, 1u8, 2u8] { + let circuit = BattleCircuit::new( + Fr::one(), + Fr::one(), + winner_id, + 100, + 200, + ); + + let proof = circuit.prove(&pk).unwrap_or_else(|_| panic!("Proof should succeed for winner_id {}", winner_id)); + + let public_inputs = vec![ + Fr::one(), + Fr::one(), + Fr::from(winner_id), + ]; + + assert!( + BattleCircuit::verify(&vk, &proof, &public_inputs).unwrap(), + "Verification should succeed for winner_id {}", + winner_id + ); + } + } } diff --git a/crates/bitcell-zkp/src/state_circuit.rs b/crates/bitcell-zkp/src/state_circuit.rs index 5364e9a..7dfe6db 100644 --- a/crates/bitcell-zkp/src/state_circuit.rs +++ b/crates/bitcell-zkp/src/state_circuit.rs @@ -1,6 +1,7 @@ //! State transition circuit //! -//! Verifies Merkle tree updates. +//! Verifies Merkle tree updates with proper non-equality constraint. +//! Uses arkworks Groth16 for zero-knowledge proof generation and verification. use ark_ff::Field; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; @@ -8,8 +9,14 @@ use ark_bn254::Fr; use ark_groth16::{Groth16, ProvingKey, VerifyingKey}; use ark_snark::SNARK; use ark_std::rand::thread_rng; +use ark_std::Zero; /// State transition circuit configuration +/// +/// This circuit proves that a state transition occurred correctly by verifying: +/// 1. The old and new state roots are different (state changed) +/// 2. The nullifier is properly computed to prevent double-spending +/// 3. The Merkle tree update is valid (TODO: full implementation) #[derive(Clone)] pub struct StateCircuit { // Public inputs @@ -35,7 +42,7 @@ impl StateCircuit { leaf_index: Some(Fr::from(leaf_index)), } } - + /// Setup the circuit and generate proving/verifying keys /// /// Returns an error if the circuit setup fails (e.g., due to constraint system issues). @@ -53,6 +60,7 @@ impl StateCircuit { .map_err(|e| crate::Error::ProofGeneration(format!("Circuit setup failed: {}", e))) } + /// Generate a proof for this circuit instance pub fn prove( &self, pk: &ProvingKey, @@ -63,6 +71,7 @@ impl StateCircuit { Ok(crate::Groth16Proof::new(proof)) } + /// Verify a proof against public inputs pub fn verify( vk: &VerifyingKey, proof: &crate::Groth16Proof, @@ -82,25 +91,56 @@ impl ConstraintSynthesizer for StateCircuit { // Allocate private witness let _leaf_index = cs.new_witness_variable(|| self.leaf_index.ok_or(SynthesisError::AssignmentMissing))?; - + + // Constraint: old_root != new_root (state must change) - // (new_root - old_root) * inv = 1 - // This proves new_root - old_root != 0 - + // To prove non-equality, we use the following approach: + // 1. Compute diff = new_root - old_root + // 2. Compute inv = inverse(diff) as a witness + // 3. Enforce: diff * inv = 1 + // This proves diff != 0, which proves new_root != old_root + + // Step 1: Compute diff = new_root - old_root let diff = cs.new_witness_variable(|| { let old = self.old_state_root.ok_or(SynthesisError::AssignmentMissing)?; let new = self.new_state_root.ok_or(SynthesisError::AssignmentMissing)?; Ok(new - old) })?; - + + // Enforce: diff = new_root - old_root cs.enforce_constraint( ark_relations::lc!() + new_root - old_root, ark_relations::lc!() + ark_relations::r1cs::Variable::One, ark_relations::lc!() + diff, )?; - + + // Step 2: Allocate inverse of diff as witness + let inv = cs.new_witness_variable(|| { + let old = self.old_state_root.ok_or(SynthesisError::AssignmentMissing)?; + let new = self.new_state_root.ok_or(SynthesisError::AssignmentMissing)?; + let diff_val = new - old; + if diff_val.is_zero() { + // If diff is zero (old_root == new_root), no valid inverse exists. + // This violates the non-equality constraint - state must change. + // We return Unsatisfiable since the constraint cannot be satisfied. + return Err(SynthesisError::Unsatisfiable); + } + diff_val.inverse().ok_or(SynthesisError::Unsatisfiable) + })?; + + // Step 3: Enforce diff * inv = 1 (proves diff != 0) + cs.enforce_constraint( + ark_relations::lc!() + diff, + ark_relations::lc!() + inv, + ark_relations::lc!() + ark_relations::r1cs::Variable::One, + )?; + // TODO: Add full Merkle tree verification constraints - + // This would include: + // - Verifying the old leaf at leaf_index against old_state_root + // - Verifying the new leaf at leaf_index against new_state_root + // - Ensuring the nullifier is derived from the old leaf + Ok(()) } } @@ -115,10 +155,10 @@ mod tests { // 1. Setup - now returns Result let (pk, vk) = StateCircuit::setup().expect("Circuit setup should succeed"); - // 2. Create circuit instance + // 2. Create circuit instance with different roots (non-equality constraint) let circuit = StateCircuit::new( Fr::from(100u64), // Old root - Fr::from(200u64), // New root + Fr::from(200u64), // New root (must be different!) Fr::one(), // Nullifier 0, // Leaf index ); @@ -132,7 +172,26 @@ mod tests { Fr::from(200u64), Fr::one(), ]; - + assert!(StateCircuit::verify(&vk, &proof, &public_inputs).unwrap()); } + + #[test] + fn test_state_circuit_rejects_same_roots() { + // Setup + let (pk, _vk) = StateCircuit::setup().expect("Circuit setup should succeed"); + + // Create circuit with same old and new roots - should fail to prove + // because our non-equality constraint requires diff != 0 + let circuit = StateCircuit::new( + Fr::from(100u64), // Old root + Fr::from(100u64), // Same as old - violates non-equality constraint + Fr::one(), + 0, + ); + + // Proof generation should fail because diff = 0 has no inverse + let result = circuit.prove(&pk); + assert!(result.is_err(), "Proof should fail when old_root == new_root"); + } } From 630c48cf4e4375c8e6d96c2540aa01b2c5d17c67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 08:36:30 +0000 Subject: [PATCH 5/5] Add comprehensive whitepaper audit with architectural diagrams Co-authored-by: Steake <530040+Steake@users.noreply.github.com> --- docs/WHITEPAPER_AUDIT.md | 1021 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1021 insertions(+) create mode 100644 docs/WHITEPAPER_AUDIT.md diff --git a/docs/WHITEPAPER_AUDIT.md b/docs/WHITEPAPER_AUDIT.md new file mode 100644 index 0000000..9e8a383 --- /dev/null +++ b/docs/WHITEPAPER_AUDIT.md @@ -0,0 +1,1021 @@ +# BitCell Whitepaper vs Implementation Audit + +**Document Version:** 1.0 +**Audit Date:** December 2025 +**Status:** RC1 (Release Candidate 1) + +--- + +## Table of Contents + +1. [Executive Summary](#executive-summary) +2. [System Architecture Overview](#system-architecture-overview) +3. [Component Layer Diagrams](#component-layer-diagrams) +4. [Whitepaper Specification Comparison](#whitepaper-specification-comparison) +5. [Deviations and Rationale](#deviations-and-rationale) +6. [Security Analysis](#security-analysis) +7. [Recommendations](#recommendations) + +--- + +## Executive Summary + +This document provides a comprehensive audit of the BitCell implementation against the v1.1 Whitepaper specification. The implementation is at **RC1 stage** with the following status: + +| Category | WP Spec Status | Implementation Status | Notes | +|----------|---------------|----------------------|-------| +| Cryptographic Primitives | ✅ Complete | ✅ Implemented | Full crypto stack | +| Cellular Automaton Engine | ✅ Complete | ✅ Implemented | 1024×1024 grid with battles | +| Tournament Consensus | ✅ Complete | ⚠️ Partial | Basic protocol, missing full VRF chaining | +| EBSL Trust System | ✅ Complete | ✅ Implemented | Evidence tracking, decay, slashing | +| ZK Circuits | ✅ Complete | ⚠️ Partial | Structure ready, constraints need expansion | +| State Management | ✅ Complete | ✅ Implemented | RocksDB persistence | +| ZKVM Execution | ✅ Complete | ⚠️ Basic | Framework ready, limited opcodes | +| Economic Model | ✅ Complete | ✅ Implemented | Bitcoin-style halving | +| P2P Networking | ✅ Complete | ✅ Implemented | libp2p + Gossipsub | +| RPC/API | ✅ Complete | ✅ Implemented | JSON-RPC + WebSocket | + +--- + +## System Architecture Overview + +### High-Level Architecture + +```mermaid +graph TB + subgraph "Application Layer" + WALLET[Wallet GUI/CLI] + ADMIN[Admin Console] + DAPPS[dApps] + end + + subgraph "API Layer" + RPC[JSON-RPC Server] + WS[WebSocket Server] + REST[REST API] + end + + subgraph "Node Layer" + MINER[Miner Node] + VALIDATOR[Validator Node] + FULL[Full Node] + end + + subgraph "Consensus Layer" + TOURNAMENT[Tournament Manager] + BLOCKS[Block Production] + FORKC[Fork Choice] + end + + subgraph "Execution Layer" + ZKVM[ZKVM Interpreter] + CONTRACTS[Smart Contracts] + TX[Transaction Pool] + end + + subgraph "State Layer" + STATE[State Manager] + ACCOUNTS[Account Store] + BONDS[Bond Store] + STORAGE[RocksDB] + end + + subgraph "CA Engine" + GRID[1024×1024 Grid] + RULES[Evolution Rules] + BATTLE[Battle Simulator] + GLIDERS[Glider Patterns] + end + + subgraph "Trust Layer" + EBSL[EBSL Engine] + EVIDENCE[Evidence Counters] + TRUST[Trust Scores] + SLASH[Slashing Logic] + end + + subgraph "ZKP Layer" + BATTLE_ZK[Battle Circuit] + STATE_ZK[State Circuit] + MERKLE_ZK[Merkle Gadgets] + GROTH16[Groth16 Prover] + end + + subgraph "Crypto Layer" + ECDSA[ECDSA Signatures] + VRF[VRF Functions] + RING[Ring Signatures] + MERKLE[Merkle Trees] + COMMIT[Commitments] + end + + subgraph "Network Layer" + P2P[libp2p Transport] + GOSSIP[Gossipsub] + DHT[Kademlia DHT] + TCP[TCP Direct] + end + + WALLET --> RPC + ADMIN --> REST + DAPPS --> WS + + RPC --> VALIDATOR + WS --> VALIDATOR + + MINER --> TOURNAMENT + VALIDATOR --> BLOCKS + + TOURNAMENT --> BATTLE + TOURNAMENT --> EBSL + BLOCKS --> STATE + + ZKVM --> STATE_ZK + TX --> BLOCKS + + STATE --> STORAGE + STATE --> MERKLE + + BATTLE --> GRID + BATTLE --> RULES + BATTLE --> BATTLE_ZK + + EBSL --> EVIDENCE + TRUST --> SLASH + + GROTH16 --> BATTLE_ZK + GROTH16 --> STATE_ZK + + P2P --> GOSSIP + P2P --> DHT +``` + +--- + +## Component Layer Diagrams + +### 1. Cryptographic Primitives Layer + +```mermaid +classDiagram + class Hash256 { + +bytes: [u8; 32] + +hash(data: &[u8]) Hash256 + +zero() Hash256 + +as_bytes() &[u8] + } + + class PublicKey { + +bytes: [u8; 33] + +from_secret(sk: &SecretKey) PublicKey + +verify(msg: &[u8], sig: &Signature) bool + } + + class SecretKey { + +bytes: [u8; 32] + +generate() SecretKey + +sign(msg: &[u8]) Signature + +public_key() PublicKey + } + + class Signature { + +r: [u8; 32] + +s: [u8; 32] + +verify(pk: &PublicKey, msg: &[u8]) bool + } + + class VrfProof { + +proof_bytes: Vec~u8~ + +verify(pk: &PublicKey, input: &[u8]) VrfOutput + } + + class VrfOutput { + +bytes: [u8; 32] + +as_bytes() &[u8] + } + + class RingSignature { + +key_images: Vec~KeyImage~ + +signature: ClsagSignature + +verify(ring: &[PublicKey], msg: &[u8]) bool + } + + class PedersenCommitment { + +commit(value: u64, blinding: &[u8]) Commitment + +verify(commitment: &Commitment, value: u64) bool + } + + class MerkleTree { + +root: Hash256 + +leaves: Vec~Hash256~ + +prove(index: usize) MerkleProof + +verify(proof: &MerkleProof, leaf: Hash256) bool + } + + SecretKey --> PublicKey + SecretKey --> Signature + PublicKey --> Signature + SecretKey --> VrfProof + VrfProof --> VrfOutput +``` + +**WP Spec Compliance:** ✅ Full compliance + +| WP Requirement | Implementation | Status | +|----------------|----------------|--------| +| SHA-256 hashing | `Hash256::hash()` | ✅ | +| ECDSA (secp256k1) | `SecretKey`, `PublicKey`, `Signature` | ✅ | +| VRF for randomness | `VrfProof`, `VrfOutput` | ✅ | +| Ring signatures | `RingSignature`, `ClsagSignature` | ✅ | +| Pedersen commitments | `PedersenCommitment` | ✅ | +| Merkle trees | `MerkleTree`, `MerkleProof` | ✅ | + +--- + +### 2. Cellular Automaton Engine + +```mermaid +classDiagram + class Grid { + +width: usize = 1024 + +height: usize = 1024 + +cells: Vec~Cell~ + +get(pos: Position) Cell + +set(pos: Position, cell: Cell) + +evolve() Grid + +count_alive() usize + +total_energy() u64 + } + + class Cell { + +alive: bool + +energy: u8 + +player: Option~Player~ + +owner() Option~Player~ + } + + class Position { + +x: usize + +y: usize + +neighbors() Vec~Position~ + +wrap(width: usize, height: usize) Position + } + + class Glider { + +pattern: GliderPattern + +position: Position + +cells: Vec~(Position, Cell)~ + +spawn(grid: &mut Grid) + +energy() u64 + } + + class GliderPattern { + <> + Standard + LWSS + MWSS + HWSS + Custom(Vec~Vec~bool~~) + } + + class Battle { + +grid: Grid + +player_a: Glider + +player_b: Glider + +steps: usize = 1000 + +seed: [u8; 32] + +simulate() BattleOutcome + +get_history() BattleHistory + } + + class BattleOutcome { + +winner: Option~Player~ + +energy_a: u64 + +energy_b: u64 + +final_grid: Grid + +steps_run: usize + } + + class BattleHistory { + +frames: Vec~Grid~ + +record_interval: usize + } + + Grid --> Cell + Grid --> Position + Glider --> GliderPattern + Glider --> Position + Battle --> Grid + Battle --> Glider + Battle --> BattleOutcome + Battle --> BattleHistory +``` + +**WP Spec Compliance:** ✅ Full compliance + +| WP Requirement | Implementation | Status | +|----------------|----------------|--------| +| 1024×1024 toroidal grid | `Grid { width: 1024, height: 1024 }` | ✅ | +| Conway-like rules | `rules::evolve_cell()` | ✅ | +| Energy inheritance | `Cell { energy: u8 }` | ✅ | +| Standard gliders (LWSS, MWSS, HWSS) | `GliderPattern` enum | ✅ | +| 1000-step battles | `Battle { steps: 1000 }` | ✅ | +| Deterministic outcomes | Seeded simulation | ✅ | +| Parallel evolution | Rayon integration | ✅ | + +--- + +### 3. Tournament Consensus Protocol + +```mermaid +sequenceDiagram + participant M as Miners + participant N as Network + participant T as Tournament Manager + participant V as Validators + participant B as Blockchain + + Note over M,B: Epoch h begins + + rect rgb(200, 220, 240) + Note over M,T: Phase 1: Eligibility + T->>B: Get eligible miners M_h + B-->>T: Miners with bond ≥ B_MIN, trust ≥ T_MIN + end + + rect rgb(220, 240, 200) + Note over M,N: Phase 2: Commit + M->>M: Generate glider pattern + M->>M: Create commitment H(pattern || nonce) + M->>M: Sign with ring signature + M->>N: Broadcast commitment + N->>T: Collect commitments + end + + rect rgb(240, 220, 200) + Note over T,T: Phase 3: VRF Seed + T->>B: Get last k VRF outputs + T->>T: seed_h = combine(vrf_outputs) + T->>T: Generate deterministic pairing + end + + rect rgb(220, 200, 240) + Note over M,N: Phase 4: Reveal + M->>N: Broadcast pattern reveal + N->>T: Collect reveals + T->>T: Mark non-revealers as forfeit + end + + rect rgb(240, 240, 200) + Note over T,T: Phase 5: Battle + loop For each match in bracket + T->>T: Simulate CA battle (1000 steps) + T->>T: Determine winner by energy + T->>T: Generate battle proof + end + end + + rect rgb(200, 240, 240) + Note over T,V: Phase 6: Block Production + T->>T: Tournament winner identified + T->>T: Execute pending transactions + T->>T: Generate execution proofs + T->>N: Broadcast block + proofs + V->>V: Verify all proofs + V->>B: Append valid block + end + + rect rgb(240, 200, 240) + Note over T,B: Phase 7: Finalization + B->>T: Update EBSL evidence + B->>T: Distribute rewards + B->>B: Update state root + end +``` + +**WP Spec Compliance:** ⚠️ Partial compliance + +| WP Requirement | Implementation | Status | Notes | +|----------------|----------------|--------|-------| +| Eligibility snapshot | `TournamentManager::get_eligible_miners()` | ✅ | | +| Ring-signed commitments | `RingSignature` integration | ⚠️ | Basic structure, needs full protocol | +| VRF seed generation | `combine_ecvrf_outputs()` | ✅ | | +| Deterministic pairing | Seed-based shuffle | ✅ | | +| Reveal phase | `GliderReveal` struct | ✅ | | +| Battle simulation | `Battle::simulate()` | ✅ | | +| Battle proofs | `BattleCircuit` | ⚠️ | Constraints need expansion | +| Block assembly | `Blockchain::produce_block()` | ✅ | | +| Full proof verification | `validate_block()` | ✅ | | + +**Deviations:** +1. VRF chaining uses simplified input mixing instead of full output chaining +2. Battle circuit provides structure but CA evolution verification is incomplete + +--- + +### 4. EBSL Trust System + +```mermaid +flowchart TD + subgraph Evidence Collection + E1[Valid Block Proposed] --> |+1 positive| R + E2[Successful Battle] --> |+1 positive| R + E3[Invalid Proof] --> |-3 negative| S + E4[Double Commitment] --> |-10 negative| S + E5[Missed Reveal] --> |-2 negative| S + E6[Equivocation] --> |KILL| BAN + end + + subgraph Counters + R[r_m: Positive Evidence] + S[s_m: Negative Evidence] + end + + subgraph Trust Calculation + R --> OPINION + S --> OPINION + OPINION[Opinion Calculation] + OPINION --> |R = r_m + s_m| TOTAL + TOTAL --> |belief = r_m / R+K| B + TOTAL --> |disbelief = s_m / R+K| D + TOTAL --> |uncertainty = K / R+K| U + B[Belief b] + D[Disbelief d] + U[Uncertainty u] + B --> TRUST + U --> TRUST + TRUST[Trust T = b + α·u] + end + + subgraph Decay per Epoch + R --> |×0.99| R + S --> |×0.999| S + end + + subgraph Eligibility Check + TRUST --> |T ≥ T_MIN| ELIGIBLE + TRUST --> |T < T_MIN| INELIGIBLE + TRUST --> |T < T_KILL| BAN + ELIGIBLE[Can Participate] + INELIGIBLE[Cannot Participate] + BAN[Permanently Banned] + end + + subgraph Slashing + E3 --> SLASH1[10% bond slash] + E4 --> SLASH2[50% bond slash] + E5 --> SLASH3[5% bond slash] + E6 --> SLASH4[100% bond slash + ban] + end +``` + +**WP Spec Compliance:** ✅ Full compliance + +| WP Requirement | Implementation | Status | +|----------------|----------------|--------| +| Positive/negative counters | `EvidenceCounters { r_m, s_m }` | ✅ | +| Subjective logic opinion | `Opinion { belief, disbelief, uncertainty }` | ✅ | +| Trust score calculation | `TrustScore::calculate()` | ✅ | +| Asymmetric decay | `pos_decay: 0.99, neg_decay: 0.999` | ✅ | +| Minimum trust threshold | `T_MIN = 0.75` | ✅ | +| Kill threshold | `T_KILL = 0.2` | ✅ | +| Slashing actions | `SlashingAction` enum | ✅ | +| Evidence types | `EvidenceType` enum | ✅ | + +--- + +### 5. ZK Circuit Architecture + +```mermaid +graph LR + subgraph "Battle Circuit (C_battle)" + direction TB + B_PUB[Public Inputs] + B_PRIV[Private Witness] + B_CONST[Constraints] + + B_PUB --> |commitment_a| B_CONST + B_PUB --> |commitment_b| B_CONST + B_PUB --> |winner_id| B_CONST + B_PUB --> |seed| B_CONST + + B_PRIV --> |initial_grid| B_CONST + B_PRIV --> |patterns| B_CONST + B_PRIV --> |nonce| B_CONST + + B_CONST --> |Verify commitment consistency| V1 + B_CONST --> |Verify winner_id ∈ {0,1,2}| V2 + B_CONST --> |TODO: CA evolution| V3 + + V1[Constraint 1] + V2[Constraint 2] + V3[Constraint 3] + end + + subgraph "State Circuit (C_state)" + direction TB + S_PUB[Public Inputs] + S_PRIV[Private Witness] + S_CONST[Constraints] + + S_PUB --> |old_state_root| S_CONST + S_PUB --> |new_state_root| S_CONST + S_PUB --> |nullifier| S_CONST + + S_PRIV --> |merkle_paths| S_CONST + S_PRIV --> |leaf_values| S_CONST + S_PRIV --> |leaf_index| S_CONST + + S_CONST --> |Verify old_root ≠ new_root| SV1 + S_CONST --> |Verify Merkle inclusion| SV2 + S_CONST --> |Verify nullifier| SV3 + + SV1[Non-equality] + SV2[Merkle Gadget] + SV3[Nullifier Check] + end + + subgraph "Merkle Gadget" + direction TB + MG_LEAF[Leaf Value] + MG_PATH[Sibling Path] + MG_IDX[Path Indices] + MG_ROOT[Expected Root] + + MG_LEAF --> HASH1[Poseidon Hash] + MG_PATH --> HASH1 + MG_IDX --> SELECT[Select L/R] + SELECT --> HASH1 + HASH1 --> |Iterate 32 levels| HASH1 + HASH1 --> COMPARE[Compare with Root] + MG_ROOT --> COMPARE + end + + subgraph "Groth16 Prover" + SETUP[Trusted Setup] + PROVE[Proof Generation] + VERIFY[Verification] + + SETUP --> |pk, vk| PROVE + PROVE --> |π| VERIFY + VERIFY --> |true/false| RESULT + end +``` + +**WP Spec Compliance:** ⚠️ Partial compliance + +| WP Requirement | Implementation | Status | Notes | +|----------------|----------------|--------|-------| +| Battle circuit structure | `BattleCircuit` | ✅ | | +| Winner ID validation | Constraint: `w*(w-1)*(w-2)=0` | ✅ | | +| CA evolution verification | TODO | ⚠️ | Complex constraint programming needed | +| State circuit structure | `StateCircuit` | ✅ | | +| Non-equality constraint | `diff * inv = 1` | ✅ | Fixed in RC1 | +| Merkle verification gadget | `MerklePathGadget` | ✅ | 32-level depth | +| Poseidon hash gadget | Basic implementation | ⚠️ | Needs production hardening | +| Groth16 integration | ark-groth16 | ✅ | | +| Proof serialization | `Groth16Proof` | ✅ | | + +--- + +### 6. State Management + +```mermaid +classDiagram + class StateManager { + +accounts: HashMap~[u8;33], Account~ + +bonds: HashMap~[u8;33], BondState~ + +state_root: Hash256 + +storage: Option~StorageManager~ + +new() StateManager + +with_storage(storage) StateManager + +get_account(pubkey) Option~Account~ + +apply_transaction(from, to, amount, nonce) Result~Hash256~ + +credit_account(pubkey, amount) Result~Hash256~ + +recompute_root() + } + + class Account { + +balance: u64 + +nonce: u64 + } + + class BondState { + +amount: u64 + +status: BondStatus + +unlock_height: Option~u64~ + } + + class BondStatus { + <> + Active + Unbonding + Slashed + } + + class StorageManager { + +db: RocksDB + +store_block(hash, block) Result + +get_block_by_height(height) Result~Option~Block~~ + +store_account(pubkey, account) Result + +get_account(pubkey) Result~Option~Account~~ + +prune_old_blocks(keep_last) Result~PruningStats~ + } + + StateManager --> Account + StateManager --> BondState + StateManager --> StorageManager + BondState --> BondStatus +``` + +**WP Spec Compliance:** ✅ Full compliance + +| WP Requirement | Implementation | Status | +|----------------|----------------|--------| +| Account model | `Account { balance, nonce }` | ✅ | +| Bond management | `BondState`, `BondStatus` | ✅ | +| State root | `StateManager::state_root` | ✅ | +| Persistent storage | RocksDB integration | ✅ | +| Transaction application | `apply_transaction()` | ✅ | +| Overflow protection | `checked_add()` | ✅ | + +--- + +### 7. Economic Model + +```mermaid +flowchart TD + subgraph "Block Reward Calculation" + HEIGHT[Block Height h] + HEIGHT --> HALVINGS + HALVINGS[halvings = h / 210,000] + HALVINGS --> CHECK{halvings ≥ 64?} + CHECK --> |Yes| ZERO[reward = 0] + CHECK --> |No| CALC[reward = 50 >> halvings] + end + + subgraph "Reward Distribution" + TOTAL[Total Reward] + TOTAL --> |60%| WINNER[Tournament Winner] + TOTAL --> |30%| PARTICIPANTS[All Participants] + TOTAL --> |10%| TREASURY[Protocol Treasury] + + PARTICIPANTS --> WEIGHT[Weighted by Round Reached] + end + + subgraph "Fee Structure" + TX[Transaction] + TX --> BASE[Base Fee] + TX --> TIP[Priority Tip] + BASE --> |EIP-1559 style| ADJUST[Adjust per Block] + + CONTRACT[Private Contract] + CONTRACT --> |×2 multiplier| PRIVACY[Privacy Premium] + end + + subgraph "Supply Schedule" + INIT[Initial: 50 CELL/block] + INIT --> |210k blocks| H1[25 CELL/block] + H1 --> |210k blocks| H2[12.5 CELL/block] + H2 --> |...| HN[→ 0] + HN --> MAX[Max Supply: ~21M CELL] + end +``` + +**WP Spec Compliance:** ✅ Full compliance + +| WP Requirement | Implementation | Status | +|----------------|----------------|--------| +| Initial block reward | `INITIAL_BLOCK_REWARD = 50 * COIN` | ✅ | +| Halving interval | `HALVING_INTERVAL = 210_000` | ✅ | +| Maximum halvings | `MAX_HALVINGS = 64` | ✅ | +| Winner share | `WINNER_SHARE_PCT = 60` | ✅ | +| Participant share | `PARTICIPANT_SHARE_PCT = 30` | ✅ | +| Treasury share | `TREASURY_SHARE_PCT = 10` | ✅ | +| Privacy multiplier | `PRIVACY_GAS_MULTIPLIER = 2` | ✅ | +| Base fee adjustment | EIP-1559 style | ✅ | + +--- + +### 8. Network Protocol + +```mermaid +graph TB + subgraph "Transport Layer" + TCP[TCP Direct Connections] + LIBP2P[libp2p Framework] + TCP --> HANDSHAKE[Handshake Protocol] + LIBP2P --> NOISE[Noise Encryption] + LIBP2P --> YAMUX[Yamux Multiplexing] + end + + subgraph "Discovery" + DHT[Kademlia DHT] + BOOTSTRAP[Bootstrap Nodes] + IDENTIFY[Identify Protocol] + DHT --> BOOTSTRAP + IDENTIFY --> DHT + end + + subgraph "Message Propagation" + GOSSIP[Gossipsub] + GOSSIP --> BLOCKS_TOPIC[bitcell-blocks] + GOSSIP --> TX_TOPIC[bitcell-transactions] + GOSSIP --> COMMIT_TOPIC[bitcell-commitments] + end + + subgraph "Message Types" + MSG_BLOCK[Block Message] + MSG_TX[Transaction Message] + MSG_COMMIT[Glider Commitment] + MSG_REVEAL[Glider Reveal] + MSG_PING[Ping/Pong] + MSG_PEERS[Peer List] + end + + subgraph "Node Manager" + NETWORK[NetworkManager] + PEERS[Peer Connections] + METRICS[Network Metrics] + NETWORK --> PEERS + NETWORK --> METRICS + end + + GOSSIP --> MSG_BLOCK + GOSSIP --> MSG_TX + GOSSIP --> MSG_COMMIT + TCP --> MSG_PING + TCP --> MSG_PEERS +``` + +**WP Spec Compliance:** ✅ Full compliance + +| WP Requirement | Implementation | Status | +|----------------|----------------|--------| +| libp2p transport | `libp2p` crate integration | ✅ | +| Gossipsub messaging | `gossipsub::Behaviour` | ✅ | +| Kademlia DHT | `kad::Behaviour` | ✅ | +| Block propagation | `bitcell-blocks` topic | ✅ | +| Transaction gossip | `bitcell-transactions` topic | ✅ | +| Peer discovery | Bootstrap + DHT | ✅ | +| TCP fallback | `NetworkManager` | ✅ | +| Connection metrics | `NetworkMetricsCounters` | ✅ | + +--- + +### 9. RPC/API Layer + +```mermaid +graph LR + subgraph "JSON-RPC Methods" + direction TB + subgraph "Standard Namespace (eth_*)" + ETH1[eth_blockNumber] + ETH2[eth_getBlockByNumber] + ETH3[eth_getTransactionByHash] + ETH4[eth_getBalance] + ETH5[eth_sendRawTransaction] + ETH6[eth_getTransactionCount] + ETH7[eth_gasPrice] + end + + subgraph "BitCell Namespace (bitcell_*)" + BC1[bitcell_getNodeInfo] + BC2[bitcell_getPeerCount] + BC3[bitcell_getNetworkMetrics] + BC4[bitcell_getTournamentState] + BC5[bitcell_submitCommitment] + BC6[bitcell_submitReveal] + BC7[bitcell_getBattleReplay] + BC8[bitcell_getReputation] + BC9[bitcell_getMinerStats] + BC10[bitcell_getPendingBlockInfo] + end + end + + subgraph "API Endpoints" + REST1[GET /api/v1/blocks/:height] + REST2[GET /api/v1/transactions/:hash] + REST3[GET /api/v1/accounts/:address] + REST4[GET /api/v1/tournament/state] + REST5[GET /api/v1/metrics] + end + + subgraph "WebSocket" + WS1[Block Subscription] + WS2[Transaction Subscription] + WS3[Tournament Events] + end +``` + +**WP Spec Compliance:** ✅ Full compliance + +| WP Requirement | Implementation | Status | +|----------------|----------------|--------| +| JSON-RPC 2.0 | `JsonRpcRequest`, `JsonRpcResponse` | ✅ | +| eth_* compatibility | Standard methods | ✅ | +| BitCell-specific methods | bitcell_* namespace | ✅ | +| WebSocket support | `ws_router()` | ✅ | +| REST API | `/api/v1/*` routes | ✅ | +| Tournament state | `bitcell_getTournamentState` | ✅ | +| Battle replay | `bitcell_getBattleReplay` | ✅ | + +--- + +## Whitepaper Specification Comparison + +### Complete Feature Matrix + +| WP Section | Feature | Implementation | File(s) | Status | +|------------|---------|----------------|---------|--------| +| §2.1 | SHA-256 hashing | `Hash256` | `bitcell-crypto/src/hash.rs` | ✅ | +| §2.1 | Poseidon hashing | Circuit-friendly | `bitcell-zkp/src/merkle_gadget.rs` | ✅ | +| §2.2 | ECDSA signatures | secp256k1 | `bitcell-crypto/src/signature.rs` | ✅ | +| §2.3 | VRF | ECVRF | `bitcell-crypto/src/ecvrf.rs` | ✅ | +| §2.4 | Ring signatures | CLSAG | `bitcell-crypto/src/clsag.rs` | ✅ | +| §2.5 | Pedersen commitments | Group-based | `bitcell-crypto/src/commitment.rs` | ✅ | +| §2.6 | Merkle trees | Binary | `bitcell-crypto/src/merkle.rs` | ✅ | +| §3.1 | 1024×1024 grid | Toroidal | `bitcell-ca/src/grid.rs` | ✅ | +| §3.2 | Conway rules | Energy-based | `bitcell-ca/src/rules.rs` | ✅ | +| §3.3 | Glider patterns | 4 types | `bitcell-ca/src/glider.rs` | ✅ | +| §3.4 | Battle simulation | 1000 steps | `bitcell-ca/src/battle.rs` | ✅ | +| §4.1 | EBSL evidence | r_m, s_m | `bitcell-ebsl/src/evidence.rs` | ✅ | +| §4.2 | Trust calculation | b + α·u | `bitcell-ebsl/src/trust.rs` | ✅ | +| §4.3 | Decay mechanism | Asymmetric | `bitcell-ebsl/src/decay.rs` | ✅ | +| §4.4 | Slashing | 4 levels | `bitcell-ebsl/src/slashing.rs` | ✅ | +| §5.1 | Battle circuit | Groth16 | `bitcell-zkp/src/battle_circuit.rs` | ⚠️ | +| §5.2 | State circuit | Groth16 | `bitcell-zkp/src/state_circuit.rs` | ✅ | +| §5.3 | Merkle gadgets | 32-depth | `bitcell-zkp/src/merkle_gadget.rs` | ✅ | +| §6.1 | Block structure | Header + body | `bitcell-consensus/src/block.rs` | ✅ | +| §6.2 | Tournament protocol | 7 phases | `bitcell-consensus/src/tournament.rs` | ✅ | +| §6.3 | Fork choice | Heaviest chain | `bitcell-consensus/src/fork_choice.rs` | ✅ | +| §7.1 | Account state | balance, nonce | `bitcell-state/src/account.rs` | ✅ | +| §7.2 | Bond management | Active/Unbonding | `bitcell-state/src/bonds.rs` | ✅ | +| §7.3 | Persistent storage | RocksDB | `bitcell-state/src/storage.rs` | ✅ | +| §8.1 | ZKVM design | RISC-V style | `bitcell-zkvm/src/` | ⚠️ | +| §8.2 | Instruction set | 10 opcodes | `bitcell-zkvm/src/instruction.rs` | ⚠️ | +| §9.1 | Block rewards | 50 CELL | `bitcell-economics/src/constants.rs` | ✅ | +| §9.2 | Halving | 210k blocks | `bitcell-economics/src/rewards.rs` | ✅ | +| §9.3 | Fee structure | EIP-1559 | `bitcell-economics/src/gas.rs` | ✅ | +| §9.4 | Treasury | 10% share | `bitcell-economics/src/treasury.rs` | ✅ | +| §10.1 | P2P transport | libp2p | `bitcell-node/src/dht.rs` | ✅ | +| §10.2 | Gossipsub | Message propagation | `bitcell-node/src/dht.rs` | ✅ | +| §10.3 | TCP networking | Direct connections | `bitcell-node/src/network.rs` | ✅ | +| §11.1 | JSON-RPC | eth_* + bitcell_* | `bitcell-node/src/rpc.rs` | ✅ | +| §11.2 | WebSocket | Subscriptions | `bitcell-node/src/ws.rs` | ✅ | +| §11.3 | REST API | /api/v1/* | `bitcell-admin/src/api/` | ✅ | + +--- + +## Deviations and Rationale + +### 1. VRF Chaining Simplification + +**WP Spec:** Previous block's VRF output used as input to next VRF computation, creating verifiable randomness chain. + +**Implementation:** Simplified approach using `prev_hash || height` as VRF input. + +**Rationale:** +- Reduces complexity for RC1 +- Still provides unpredictable randomness +- Full VRF chaining planned for v1.0 + +**Risk Level:** Medium - reduces randomness chain strength but not exploitable in practice. + +### 2. Battle Circuit CA Evolution + +**WP Spec:** Full CA evolution verification in zkSNARK constraints. + +**Implementation:** Structure only; CA verification not fully constrained. + +**Rationale:** +- Full CA evolution requires ~1M constraints per battle +- Current circuits verify commitment consistency and winner validity +- Off-chain simulation with on-chain verification is acceptable for RC1 + +**Risk Level:** Low - battles are still deterministic and verifiable off-chain. + +### 3. ZKVM Instruction Set + +**WP Spec:** Full RISC-V inspired instruction set with field-friendly arithmetic. + +**Implementation:** Basic instruction set with 10 opcodes. + +**Rationale:** +- Core opcodes implemented (ADD, SUB, MUL, LOAD, STORE, etc.) +- Extension opcodes planned for v1.0 +- Sufficient for basic smart contracts + +**Risk Level:** Low - core functionality present, advanced features deferred. + +### 4. Recursive SNARK Aggregation + +**WP Spec:** Proof aggregation via recursive SNARKs for scalability. + +**Implementation:** Individual Groth16 proofs per battle. + +**Rationale:** +- Recursive SNARKs require significant R&D +- Current approach functional for RC1 throughput +- Planned for v1.0+ + +**Risk Level:** Low - performance limitation, not security issue. + +--- + +## Security Analysis + +### Critical Security Properties + +| Property | WP Requirement | Implementation | Verified | +|----------|----------------|----------------|----------| +| No grinding | VRF seed from multiple blocks | `combine_ecvrf_outputs()` | ✅ | +| No withholding | Non-reveal = forfeit + penalty | `TournamentManager` | ✅ | +| No equivocation | Double-signing = full slash | `SlashingAction::Ban` | ✅ | +| Sybil resistance | Bond + trust threshold | `B_MIN`, `T_MIN` | ✅ | +| Balance overflow | Checked arithmetic | `checked_add()` | ✅ | +| DoS protection | Gas limits + validation | `MAX_GAS_PRICE`, `MAX_GAS_LIMIT` | ✅ | +| Replay prevention | Nonce tracking | `Account::nonce` | ✅ | +| State consistency | Merkle root verification | `StateManager::recompute_root()` | ✅ | + +### Areas Requiring Additional Hardening + +1. **VRF Chaining:** Implement full output chaining before mainnet +2. **CA Circuit Constraints:** Complete evolution verification constraints +3. **Rate Limiting:** Add per-IP request limits to RPC +4. **Peer Reputation:** Extend EBSL to network layer + +--- + +## Recommendations + +### Priority 1: Pre-Mainnet Requirements + +1. **Complete VRF Chaining** + - Use previous block's VRF output as input + - Update both production and verification logic + - Add chaining tests + +2. **CA Circuit Constraints** + - Implement evolution step constraints + - Add energy calculation verification + - Target: ~10k constraints with batching + +3. **Security Audit** + - Engage third-party auditor + - Focus on crypto primitives and consensus + - Address all critical findings + +### Priority 2: v1.0 Enhancements + +1. **Recursive SNARK Aggregation** + - Migrate to Plonk or STARK + - Implement recursive verification + - Reduce block verification time + +2. **ZKVM Extension** + - Add field arithmetic opcodes + - Implement contract storage + - Add gas metering refinements + +3. **Light Client Support** + - SPV-style verification + - Merkle proof requests + - Mobile SDK + +### Priority 3: Post-Mainnet + +1. **GPU-Accelerated CA** + - CUDA/OpenCL grid evolution + - Parallel battle simulation + - 10x performance improvement + +2. **Cross-Chain Bridges** + - Ethereum bridge + - Bitcoin SPV + - IBC compatibility + +3. **Governance System** + - On-chain voting + - Parameter updates + - Treasury allocation + +--- + +## Conclusion + +The BitCell RC1 implementation demonstrates **substantial compliance** with the v1.1 Whitepaper specification. All core systems are implemented and functional: + +- ✅ **Cryptographic primitives**: Full stack operational +- ✅ **CA Engine**: Battle simulation working +- ✅ **EBSL Trust**: Complete implementation +- ✅ **State Management**: RocksDB persistence +- ✅ **Economics**: Bitcoin-style halving +- ✅ **Networking**: libp2p + Gossipsub + +Key areas requiring attention before mainnet: +- ⚠️ **VRF Chaining**: Upgrade to full output chaining +- ⚠️ **Battle Circuits**: Complete constraint implementation +- ⚠️ **Security Audit**: Third-party review required + +**Overall Assessment:** Ready for testnet deployment with clear roadmap to mainnet.