diff --git a/Cargo.lock b/Cargo.lock index 6427833300e..282ee811050 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10397,6 +10397,7 @@ name = "reth-stateless" version = "1.8.2" dependencies = [ "alloy-consensus", + "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-debug", @@ -10417,6 +10418,7 @@ dependencies = [ "serde", "serde_with", "thiserror 2.0.16", + "tracing", ] [[package]] diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index a0cccfcc449..7db3bf0f777 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -15,10 +15,10 @@ use alloy_consensus::{ Header, }; use alloy_eips::{ - eip1559::INITIAL_BASE_FEE, eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams, - eip7892::BlobScheduleBlobParams, + eip1559::INITIAL_BASE_FEE, eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS, + eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams, eip7892::BlobScheduleBlobParams, }; -use alloy_genesis::Genesis; +use alloy_genesis::{ChainConfig, Genesis}; use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256}; use alloy_trie::root::state_root_ref_unhashed; use core::fmt::Debug; @@ -88,9 +88,15 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea /// The Ethereum mainnet spec pub static MAINNET: LazyLock> = LazyLock::new(|| { - let genesis = serde_json::from_str(include_str!("../res/genesis/mainnet.json")) + let mut genesis: Genesis = serde_json::from_str(include_str!("../res/genesis/mainnet.json")) .expect("Can't deserialize Mainnet genesis json"); let hardforks = EthereumHardfork::mainnet().into(); + fill_chainconfig( + &mut genesis.config, + &hardforks, + Some(Chain::mainnet()), + Some(MAINNET_DEPOSIT_CONTRACT_ADDRESS), + ); let mut spec = ChainSpec { chain: Chain::mainnet(), genesis_header: SealedHeader::new( @@ -398,7 +404,7 @@ impl ChainSpec { // given timestamp. for (fork, params) in bf_params.iter().rev() { if self.hardforks.is_fork_active_at_timestamp(fork.clone(), timestamp) { - return *params + return *params; } } @@ -512,7 +518,7 @@ impl ChainSpec { } else { // we can return here because this block fork is not active, so we set the // `next` value - return ForkId { hash: forkhash, next: block } + return ForkId { hash: forkhash, next: block }; } } } @@ -534,7 +540,7 @@ impl ChainSpec { // can safely return here because we have already handled all block forks and // have handled all active timestamp forks, and set the next value to the // timestamp that is known but not active yet - return ForkId { hash: forkhash, next: timestamp } + return ForkId { hash: forkhash, next: timestamp }; } } @@ -805,6 +811,7 @@ pub struct ChainSpecBuilder { chain: Option, genesis: Option, hardforks: ChainHardforks, + fill_genesis_config: bool, } impl ChainSpecBuilder { @@ -814,6 +821,7 @@ impl ChainSpecBuilder { chain: Some(MAINNET.chain), genesis: Some(MAINNET.genesis.clone()), hardforks: MAINNET.hardforks.clone(), + fill_genesis_config: true, } } } @@ -871,9 +879,16 @@ impl ChainSpecBuilder { self } + /// Enable Dao at genesis. + pub fn dao_activated(mut self) -> Self { + self = self.frontier_activated(); + self.hardforks.insert(EthereumHardfork::Dao, ForkCondition::Block(0)); + self + } + /// Enable Homestead at genesis. pub fn homestead_activated(mut self) -> Self { - self = self.frontier_activated(); + self = self.dao_activated(); self.hardforks.insert(EthereumHardfork::Homestead, ForkCondition::Block(0)); self } @@ -920,9 +935,16 @@ impl ChainSpecBuilder { self } + /// Enable Muir Glacier at genesis. + pub fn muirglacier_activated(mut self) -> Self { + self = self.istanbul_activated(); + self.hardforks.insert(EthereumHardfork::MuirGlacier, ForkCondition::Block(0)); + self + } + /// Enable Berlin at genesis. pub fn berlin_activated(mut self) -> Self { - self = self.istanbul_activated(); + self = self.muirglacier_activated(); self.hardforks.insert(EthereumHardfork::Berlin, ForkCondition::Block(0)); self } @@ -934,9 +956,23 @@ impl ChainSpecBuilder { self } + /// Enable Arrow Glacier at genesis. + pub fn arrowglacier_activated(mut self) -> Self { + self = self.london_activated(); + self.hardforks.insert(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0)); + self + } + + /// Enable Gray Glacier at genesis. + pub fn grayglacier_activated(mut self) -> Self { + self = self.arrowglacier_activated(); + self.hardforks.insert(EthereumHardfork::GrayGlacier, ForkCondition::Block(0)); + self + } + /// Enable Paris at genesis. pub fn paris_activated(mut self) -> Self { - self = self.london_activated(); + self = self.grayglacier_activated(); self.hardforks.insert( EthereumHardfork::Paris, ForkCondition::TTD { @@ -982,6 +1018,12 @@ impl ChainSpecBuilder { self } + /// Enable overriding genesis chain configuration from defined hardforks. + pub const fn fill_genesis_config(mut self, enable: bool) -> Self { + self.fill_genesis_config = enable; + self + } + /// Enable Osaka at the given timestamp. pub fn with_osaka_at(mut self, timestamp: u64) -> Self { self.hardforks.insert(EthereumHardfork::Osaka, ForkCondition::Timestamp(timestamp)); @@ -1004,7 +1046,10 @@ impl ChainSpecBuilder { } }) }; - let genesis = self.genesis.expect("The genesis is required"); + let mut genesis = self.genesis.expect("The genesis is required"); + if self.fill_genesis_config { + fill_chainconfig(&mut genesis.config, &self.hardforks, self.chain, None); + } ChainSpec { chain: self.chain.expect("The chain is required"), genesis_header: SealedHeader::new_unhashed(make_genesis_header( @@ -1020,12 +1065,69 @@ impl ChainSpecBuilder { } } +fn fill_chainconfig( + cfg: &mut ChainConfig, + hardforks: &ChainHardforks, + chain: Option, + deposit_contract: Option
, +) { + cfg.chain_id = chain.map(|c| c.id()).unwrap_or_default(); + cfg.deposit_contract_address = deposit_contract; + // Helpers to extract activation values from ForkCondition + let get_block = |hf: EthereumHardfork| -> Option { + match hardforks.fork(hf) { + ForkCondition::Block(b) => Some(b), + ForkCondition::TTD { activation_block_number, .. } => Some(activation_block_number), + _ => None, + } + }; + let get_time = |hf: EthereumHardfork| -> Option { + match hardforks.fork(hf) { + ForkCondition::Timestamp(t) => Some(t), + _ => None, + } + }; + + // Legacy block-based forks + cfg.homestead_block = get_block(EthereumHardfork::Homestead); + cfg.dao_fork_block = get_block(EthereumHardfork::Dao); + cfg.dao_fork_support = cfg.dao_fork_block.is_some(); + cfg.eip150_block = get_block(EthereumHardfork::Tangerine); + cfg.eip158_block = get_block(EthereumHardfork::Tangerine); + cfg.eip155_block = get_block(EthereumHardfork::SpuriousDragon); + cfg.byzantium_block = get_block(EthereumHardfork::Byzantium); + cfg.constantinople_block = get_block(EthereumHardfork::Constantinople); + cfg.petersburg_block = get_block(EthereumHardfork::Petersburg); + cfg.istanbul_block = get_block(EthereumHardfork::Istanbul); + cfg.muir_glacier_block = get_block(EthereumHardfork::MuirGlacier); + cfg.berlin_block = get_block(EthereumHardfork::Berlin); + cfg.london_block = get_block(EthereumHardfork::London); + cfg.arrow_glacier_block = get_block(EthereumHardfork::ArrowGlacier); + cfg.gray_glacier_block = get_block(EthereumHardfork::GrayGlacier); + + // Merge (Paris) via TTD + if let ForkCondition::TTD { total_difficulty, fork_block, .. } = + hardforks.fork(EthereumHardfork::Paris) + { + cfg.terminal_total_difficulty = Some(total_difficulty); + cfg.terminal_total_difficulty_passed = true; + cfg.merge_netsplit_block = fork_block; + } + + // Timestamp-based forks + cfg.shanghai_time = get_time(EthereumHardfork::Shanghai); + cfg.cancun_time = get_time(EthereumHardfork::Cancun); + cfg.prague_time = get_time(EthereumHardfork::Prague); + cfg.osaka_time = get_time(EthereumHardfork::Osaka); +} + impl From<&Arc> for ChainSpecBuilder { fn from(value: &Arc) -> Self { Self { chain: Some(value.chain), genesis: Some(value.genesis.clone()), hardforks: value.hardforks.clone(), + fill_genesis_config: false, } } } diff --git a/crates/stateless/Cargo.toml b/crates/stateless/Cargo.toml index 8adbae28ae3..13508b1285f 100644 --- a/crates/stateless/Cargo.toml +++ b/crates/stateless/Cargo.toml @@ -18,11 +18,15 @@ alloy-rlp.workspace = true alloy-trie.workspace = true alloy-consensus.workspace = true alloy-rpc-types-debug.workspace = true +alloy-genesis = { workspace = true, features = ["serde-bincode-compat"] } # reth reth-ethereum-consensus.workspace = true reth-primitives-traits.workspace = true -reth-ethereum-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"] } +reth-ethereum-primitives = { workspace = true, features = [ + "serde", + "serde-bincode-compat", +] } reth-errors.workspace = true reth-evm.workspace = true reth-revm.workspace = true @@ -36,6 +40,7 @@ thiserror.workspace = true itertools.workspace = true serde.workspace = true serde_with.workspace = true +tracing.workspace = true k256 = { workspace = true, optional = true } secp256k1 = { workspace = true, optional = true } diff --git a/crates/stateless/src/lib.rs b/crates/stateless/src/lib.rs index 6813638485e..ac9e3480fc9 100644 --- a/crates/stateless/src/lib.rs +++ b/crates/stateless/src/lib.rs @@ -39,6 +39,7 @@ mod recover_block; /// Sparse trie implementation for stateless validation pub mod trie; +use alloy_genesis::ChainConfig; #[doc(inline)] pub use recover_block::UncompressedPublicKey; #[doc(inline)] @@ -46,6 +47,8 @@ pub use trie::StatelessTrie; #[doc(inline)] pub use validation::stateless_validation_with_trie; +pub use alloy_genesis::Genesis; + /// Implementation of stateless validation pub mod validation; pub(crate) mod witness_db; @@ -67,4 +70,25 @@ pub struct StatelessInput { pub block: Block, /// `ExecutionWitness` for the stateless validation function pub witness: ExecutionWitness, + /// Chain configuration for the stateless validation function + #[serde_as(as = "alloy_genesis::serde_bincode_compat::ChainConfig<'_>")] + pub chain_config: ChainConfig, +} + +/// Tracks the amount of cycles a region of code takes up +/// in a zkvm environment and is no-op otherwise. +#[macro_export] +macro_rules! track_cycles { + ($name:expr, $body:expr) => {{ + #[cfg(target_os = "zkvm")] + { + tracing::info!("cycle-tracker-report-start: {}", $name); + let result = $body; + tracing::info!("cycle-tracker-report-end: {}", $name); + result + } + + #[cfg(not(target_os = "zkvm"))] + $body + }}; } diff --git a/crates/stateless/src/validation.rs b/crates/stateless/src/validation.rs index a0475b09939..54fa76949b0 100644 --- a/crates/stateless/src/validation.rs +++ b/crates/stateless/src/validation.rs @@ -1,5 +1,6 @@ use crate::{ recover_block::{recover_block_with_public_keys, UncompressedPublicKey}, + track_cycles, trie::{StatelessSparseTrie, StatelessTrie}, witness_db::WitnessDatabase, ExecutionWitness, @@ -216,30 +217,37 @@ where validate_block_consensus(chain_spec.clone(), ¤t_block, parent)?; // First verify that the pre-state reads are correct - let (mut trie, bytecode) = T::new(&witness, parent.state_root)?; + let (mut trie, bytecode) = + track_cycles!("verify_witness", T::new(&witness, parent.state_root)?); // Create an in-memory database that will use the reads to validate the block let db = WitnessDatabase::new(&trie, bytecode, ancestor_hashes); // Execute the block let executor = evm_config.executor(db); - let output = executor - .execute(¤t_block) - .map_err(|e| StatelessValidationError::StatelessExecutionFailed(e.to_string()))?; + let output = track_cycles!( + "block_execution", + executor + .execute(¤t_block) + .map_err(|e| StatelessValidationError::StatelessExecutionFailed(e.to_string()))? + ); // Post validation checks validate_block_post_execution(¤t_block, &chain_spec, &output.receipts, &output.requests) .map_err(StatelessValidationError::ConsensusValidationFailed)?; // Compute and check the post state root - let hashed_state = HashedPostState::from_bundle_state::(&output.state.state); - let state_root = trie.calculate_state_root(hashed_state)?; - if state_root != current_block.state_root { - return Err(StatelessValidationError::PostStateRootMismatch { - got: state_root, - expected: current_block.state_root, - }); - } + track_cycles!("post_state_compute", { + let hashed_state = + HashedPostState::from_bundle_state::(&output.state.state); + let state_root = trie.calculate_state_root(hashed_state)?; + if state_root != current_block.state_root { + return Err(StatelessValidationError::PostStateRootMismatch { + got: state_root, + expected: current_block.state_root, + }); + } + }); // Return block hash Ok(current_block.hash_slow()) diff --git a/testing/ef-tests/src/models.rs b/testing/ef-tests/src/models.rs index 49c49bf1936..8604e2a7ab5 100644 --- a/testing/ef-tests/src/models.rs +++ b/testing/ef-tests/src/models.rs @@ -243,12 +243,12 @@ impl Account { } else { return Err(Error::Assertion(format!( "Slot {slot:?} is missing from the database. Expected {value:?}" - ))) + ))); } } else { return Err(Error::Assertion(format!( "Slot {slot:?} is missing from the database. Expected {value:?}" - ))) + ))); } } @@ -323,7 +323,7 @@ impl From for ChainSpec { fn from(fork_spec: ForkSpec) -> Self { let spec_builder = ChainSpecBuilder::mainnet().reset(); - match fork_spec { + let chain_spec = match fork_spec { ForkSpec::Frontier => spec_builder.frontier_activated(), ForkSpec::FrontierToHomesteadAt5 => spec_builder .frontier_activated() @@ -372,7 +372,9 @@ impl From for ChainSpec { .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(15_000)), ForkSpec::Prague => spec_builder.prague_activated(), } - .build() + .build(); + + chain_spec } }