Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions crates/node/src/execution_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand Down
71 changes: 44 additions & 27 deletions crates/node/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<StorageReceipt> = 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);
Expand Down Expand Up @@ -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<StorageLog> = receipt
.logs
Expand All @@ -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),
Expand Down