diff --git a/crates/node/src/execution_bridge.rs b/crates/node/src/execution_bridge.rs index b6b9e9f5..91b3a103 100644 --- a/crates/node/src/execution_bridge.rs +++ b/crates/node/src/execution_bridge.rs @@ -198,6 +198,33 @@ impl ExecutionBridge { self.gas_limit } + /// Set the initial block hash for chain connectivity. + /// + /// This must be called after the genesis block is created in storage to ensure + /// that block 1's parent_hash correctly references the genesis block hash. + /// If not called, block 1 would have parent_hash = 0x000...000 (the default). + /// + /// # Arguments + /// + /// * `genesis_hash` - The hash of the genesis block (block 0) + pub fn set_genesis_block_hash(&self, genesis_hash: B256) { + match self.last_block_hash.write() { + Ok(mut guard) => { + *guard = genesis_hash; + debug!( + genesis_hash = %genesis_hash, + "Execution bridge initialized with genesis block hash" + ); + } + Err(e) => { + warn!( + error = %e, + "Failed to set genesis block hash - lock poisoned, block 1 may have incorrect parent_hash" + ); + } + } + } + /// Validate a transaction for mempool CheckTx /// /// This is called by workers before accepting transactions into batches. @@ -699,6 +726,26 @@ mod tests { ); } + #[tokio::test] + async fn test_set_genesis_block_hash() { + let bridge = create_default_bridge().unwrap(); + + // Initially should be B256::ZERO + let initial_hash = bridge.last_block_hash.read().map(|guard| *guard).unwrap(); + assert_eq!(initial_hash, B256::ZERO); + + // Set genesis hash + let genesis_hash = B256::from([0x42u8; 32]); + bridge.set_genesis_block_hash(genesis_hash); + + // Should now be updated + let updated_hash = bridge.last_block_hash.read().map(|guard| *guard).unwrap(); + assert_eq!( + updated_hash, genesis_hash, + "set_genesis_block_hash should update last_block_hash" + ); + } + #[tokio::test] async fn test_gas_limit_from_config() { let bridge = create_default_bridge().unwrap(); diff --git a/crates/node/src/node.rs b/crates/node/src/node.rs index b84289d6..5bc07896 100644 --- a/crates/node/src/node.rs +++ b/crates/node/src/node.rs @@ -19,7 +19,7 @@ use crate::config::NodeConfig; use crate::execution_bridge::{BlockExecutionResult, ExecutionBridge}; use crate::network::{TcpPrimaryNetwork, TcpWorkerNetwork}; use crate::supervisor::NodeSupervisor; -use alloy_primitives::Address; +use alloy_primitives::{Address, B256}; use anyhow::{Context, Result}; use cipherbft_consensus::{ create_context, default_consensus_params, default_engine_config_single_part, spawn_host, @@ -1126,30 +1126,41 @@ impl Node { // Ensure genesis block (block 0) exists for Ethereum RPC compatibility // Block explorers like Blockscout expect block 0 to exist - match storage.block_store().get_block_by_number(0).await { - Ok(Some(_)) => { - debug!("Genesis block (block 0) already exists in storage"); - } - Ok(None) => { - // Create and store genesis block - let genesis_timestamp = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - let genesis_block = - Self::create_genesis_block(genesis_timestamp, self.gas_limit); - if let Err(e) = storage.block_store().put_block(&genesis_block).await { - error!("Failed to store genesis block: {}", e); - } else { - info!( - "Created genesis block (block 0) with hash 0x{}", - hex::encode(&genesis_block.hash[..8]) - ); + let genesis_hash: Option<[u8; 32]> = + match storage.block_store().get_block_by_number(0).await { + Ok(Some(existing_block)) => { + debug!("Genesis block (block 0) already exists in storage"); + Some(existing_block.hash) } - } - Err(e) => { - warn!("Failed to check for genesis block: {}", e); - } + Ok(None) => { + // Create and store genesis block + let genesis_timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + let genesis_block = + Self::create_genesis_block(genesis_timestamp, self.gas_limit); + let hash = genesis_block.hash; + if let Err(e) = storage.block_store().put_block(&genesis_block).await { + error!("Failed to store genesis block: {}", e); + None + } else { + info!( + "Created genesis block (block 0) with hash 0x{}", + hex::encode(&genesis_block.hash[..8]) + ); + Some(hash) + } + } + Err(e) => { + warn!("Failed to check for genesis block: {}", e); + None + } + }; + + // Synchronize genesis block hash with execution bridge for correct parent_hash in block 1 + if let (Some(hash), Some(ref bridge)) = (genesis_hash, &self.execution_bridge) { + bridge.set_genesis_block_hash(B256::from(hash)); } // Initialize latest_block from storage (important for restart scenarios) @@ -1508,11 +1519,12 @@ impl Node { // Store receipts for eth_getBlockReceipts queries if !block_result.execution_result.receipts.is_empty() { + let block_hash_bytes = block_result.block_hash.0; let storage_receipts: Vec = block_result .execution_result .receipts .iter() - .map(Self::execution_receipt_to_storage) + .map(|r| Self::execution_receipt_to_storage(r, block_hash_bytes)) .collect(); if let Err(e) = storage.receipt_store().put_receipts(&storage_receipts).await { error!("Failed to store {} receipts for block {}: {}", storage_receipts.len(), height.0, e); @@ -1774,7 +1786,12 @@ impl Node { /// Convert an execution TransactionReceipt to a storage Receipt for MDBX persistence. /// /// This bridges the execution layer receipt format to the storage layer format. - fn execution_receipt_to_storage(receipt: &ExecutionReceipt) -> StorageReceipt { + /// The block_hash parameter is passed explicitly because the execution receipt + /// is created before the block hash is computed, so it contains a placeholder value. + fn execution_receipt_to_storage( + receipt: &ExecutionReceipt, + block_hash: [u8; 32], + ) -> StorageReceipt { // Convert logs let logs: Vec = receipt .logs @@ -1788,7 +1805,7 @@ impl Node { StorageReceipt { transaction_hash: receipt.transaction_hash.0, block_number: receipt.block_number, - block_hash: receipt.block_hash.0, + block_hash, transaction_index: receipt.transaction_index as u32, from: receipt.from.0 .0, to: receipt.to.map(|a| a.0 .0),