diff --git a/crates/ethereum/cli/src/debug_cmd/execution.rs b/crates/ethereum/cli/src/debug_cmd/execution.rs index 0e0cd86638..63a9cc3a80 100644 --- a/crates/ethereum/cli/src/debug_cmd/execution.rs +++ b/crates/ethereum/cli/src/debug_cmd/execution.rs @@ -24,7 +24,7 @@ use reth_network_api::NetworkInfo; use reth_network_p2p::{headers::client::HeadersClient, EthBlockClient}; use reth_node_api::NodeTypesWithDBAdapter; use reth_node_core::{args::NetworkArgs, utils::get_single_header}; -use reth_node_ethereum::{consensus::EthBeaconConsensus, EthExecutorProvider}; +use reth_node_ethereum::{consensus::EthBeaconConsensus, EthEvmConfig}; use reth_node_events::node::NodeEvent; use reth_provider::{ providers::ProviderNodeTypes, ChainSpecProvider, ProviderFactory, StageCheckpointReader, @@ -86,7 +86,7 @@ impl> Command { let prune_modes = config.prune.clone().map(|prune| prune.segments).unwrap_or_default(); let (tip_tx, tip_rx) = watch::channel(B256::ZERO); - let executor = EthExecutorProvider::ethereum(provider_factory.chain_spec()); + let executor = EthEvmConfig::ethereum(provider_factory.chain_spec()); let pipeline = Pipeline::::builder() .with_tip_sender(tip_tx) diff --git a/crates/ethereum/cli/src/debug_cmd/merkle.rs b/crates/ethereum/cli/src/debug_cmd/merkle.rs index acfbd20ef9..63c18f9d2d 100644 --- a/crates/ethereum/cli/src/debug_cmd/merkle.rs +++ b/crates/ethereum/cli/src/debug_cmd/merkle.rs @@ -18,7 +18,7 @@ use reth_network_api::NetworkInfo; use reth_network_p2p::full_block::FullBlockClient; use reth_node_api::{BlockTy, NodePrimitives}; use reth_node_core::{args::NetworkArgs, utils::get_single_header}; -use reth_node_ethereum::{consensus::EthBeaconConsensus, EthExecutorProvider}; +use reth_node_ethereum::{consensus::EthBeaconConsensus, EthEvmConfig}; use reth_provider::{ providers::ProviderNodeTypes, BlockNumReader, BlockWriter, ChainSpecProvider, DatabaseProviderFactory, LatestStateProviderRef, OriginalValuesKnown, ProviderFactory, @@ -109,7 +109,7 @@ impl> Command { ) .await?; - let executor_provider = EthExecutorProvider::ethereum(provider_factory.chain_spec()); + let executor_provider = EthEvmConfig::ethereum(provider_factory.chain_spec()); // Initialize the fetch client info!(target: "reth::cli", target_block_number = self.to, "Downloading tip of block range"); diff --git a/crates/ethereum/cli/src/interface.rs b/crates/ethereum/cli/src/interface.rs index 64dbb02eff..6c96c6d299 100644 --- a/crates/ethereum/cli/src/interface.rs +++ b/crates/ethereum/cli/src/interface.rs @@ -18,7 +18,7 @@ use reth_node_core::{ args::LogArgs, version::{LONG_VERSION, SHORT_VERSION}, }; -use reth_node_ethereum::{consensus::EthBeaconConsensus, EthExecutorProvider, EthereumNode}; +use reth_node_ethereum::{consensus::EthBeaconConsensus, EthEvmConfig, EthereumNode}; use reth_node_metrics::recorder::install_prometheus_recorder; use reth_tracing::FileWorkerGuard; use std::{ffi::OsString, fmt, future::Future, sync::Arc}; @@ -149,7 +149,7 @@ impl, Ext: clap::Args + fmt::Debug> Cl let _ = install_prometheus_recorder(); let components = |spec: Arc| { - (EthExecutorProvider::ethereum(spec.clone()), EthBeaconConsensus::new(spec)) + (EthEvmConfig::ethereum(spec.clone()), EthBeaconConsensus::new(spec)) }; match self.command { Commands::Node(command) => runner.run_command_until_exit(|ctx| { diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs deleted file mode 100644 index 072f314ce7..0000000000 --- a/crates/ethereum/evm/src/execute.rs +++ /dev/null @@ -1,863 +0,0 @@ -//! Ethereum block execution strategy. - -/// Helper type with backwards compatible methods to obtain Ethereum executor -/// providers. -pub type EthExecutorProvider = crate::EthEvmConfig; - -#[cfg(test)] -mod tests { - use crate::EthEvmConfig; - use alloy_consensus::{constants::ETH_TO_WEI, Header, TxLegacy}; - use alloy_eips::{ - eip2935::{HISTORY_SERVE_WINDOW, HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE}, - eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS}, - eip4895::Withdrawal, - eip7002::{WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_CODE}, - eip7685::EMPTY_REQUESTS_HASH, - }; - use alloy_evm::block::BlockValidationError; - use alloy_primitives::{b256, fixed_bytes, keccak256, Bytes, TxKind, B256, U256}; - use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, ForkCondition, MAINNET}; - use reth_ethereum_primitives::{Block, BlockBody, Transaction}; - use reth_evm::{execute::Executor, ConfigureEvm}; - use reth_execution_types::BlockExecutionResult; - use reth_primitives_traits::{ - crypto::secp256k1::public_key_to_address, Block as _, RecoveredBlock, - }; - use reth_testing_utils::generators::{self, sign_tx_with_key_pair}; - use revm::{ - database::{CacheDB, EmptyDB, TransitionState}, - primitives::address, - state::{AccountInfo, Bytecode, EvmState}, - Database, - }; - use std::sync::{mpsc, Arc}; - - fn create_database_with_beacon_root_contract() -> CacheDB { - let mut db = CacheDB::new(Default::default()); - - let beacon_root_contract_account = AccountInfo { - balance: U256::ZERO, - code_hash: keccak256(BEACON_ROOTS_CODE.clone()), - nonce: 1, - code: Some(Bytecode::new_raw(BEACON_ROOTS_CODE.clone())), - }; - - db.insert_account_info(BEACON_ROOTS_ADDRESS, beacon_root_contract_account); - - db - } - - fn create_database_with_withdrawal_requests_contract() -> CacheDB { - let mut db = CacheDB::new(Default::default()); - - let withdrawal_requests_contract_account = AccountInfo { - nonce: 1, - balance: U256::ZERO, - code_hash: keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()), - code: Some(Bytecode::new_raw(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())), - }; - - db.insert_account_info( - WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, - withdrawal_requests_contract_account, - ); - - db - } - - fn evm_config(chain_spec: Arc) -> EthEvmConfig { - EthEvmConfig::new(chain_spec) - } - - #[test] - fn eip_4788_non_genesis_call() { - let mut header = - Header { timestamp: 1, number: 1, excess_blob_gas: Some(0), ..Header::default() }; - - let db = create_database_with_beacon_root_contract(); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) - .build(), - ); - - let provider = evm_config(chain_spec); - - let mut executor = provider.batch_executor(db); - - // attempt to execute a block without parent beacon block root, expect err - let err = executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { - header: header.clone(), - body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None }, - }, - vec![], - )) - .expect_err( - "Executing cancun block without parent beacon block root field should fail", - ); - - assert!(matches!( - err.as_validation().unwrap(), - BlockValidationError::MissingParentBeaconBlockRoot - )); - - // fix header, set a gas limit - header.parent_beacon_block_root = Some(B256::with_last_byte(0x69)); - - // Now execute a block with the fixed header, ensure that it does not fail - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { - header: header.clone(), - body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None }, - }, - vec![], - )) - .unwrap(); - - // check the actual storage of the contract - it should be: - // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be - // header.timestamp - // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH - // // should be parent_beacon_block_root - let history_buffer_length = 8191u64; - let timestamp_index = header.timestamp % history_buffer_length; - let parent_beacon_block_root_index = - timestamp_index % history_buffer_length + history_buffer_length; - - let timestamp_storage = executor.with_state_mut(|state| { - state.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap() - }); - assert_eq!(timestamp_storage, U256::from(header.timestamp)); - - // get parent beacon block root storage and compare - let parent_beacon_block_root_storage = executor.with_state_mut(|state| { - state - .storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index)) - .expect("storage value should exist") - }); - assert_eq!(parent_beacon_block_root_storage, U256::from(0x69)); - } - - #[test] - fn eip_4788_no_code_cancun() { - // This test ensures that we "silently fail" when cancun is active and there is no code at - // // BEACON_ROOTS_ADDRESS - let header = Header { - timestamp: 1, - number: 1, - parent_beacon_block_root: Some(B256::with_last_byte(0x69)), - excess_blob_gas: Some(0), - ..Header::default() - }; - - let db = CacheDB::new(EmptyDB::default()); - - // DON'T deploy the contract at genesis - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) - .build(), - ); - - let provider = evm_config(chain_spec); - - // attempt to execute an empty block with parent beacon block root, this should not fail - provider - .batch_executor(db) - .execute_one(&RecoveredBlock::new_unhashed( - Block { - header, - body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None }, - }, - vec![], - )) - .expect( - "Executing a block with no transactions while cancun is active should not fail", - ); - } - - #[test] - fn eip_4788_empty_account_call() { - // This test ensures that we do not increment the nonce of an empty SYSTEM_ADDRESS account - // // during the pre-block call - - let mut db = create_database_with_beacon_root_contract(); - - // insert an empty SYSTEM_ADDRESS - db.insert_account_info(SYSTEM_ADDRESS, Default::default()); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) - .build(), - ); - - let provider = evm_config(chain_spec); - - // construct the header for block one - let header = Header { - timestamp: 1, - number: 1, - parent_beacon_block_root: Some(B256::with_last_byte(0x69)), - excess_blob_gas: Some(0), - ..Header::default() - }; - - let mut executor = provider.batch_executor(db); - - // attempt to execute an empty block with parent beacon block root, this should not fail - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { - header, - body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None }, - }, - vec![], - )) - .expect( - "Executing a block with no transactions while cancun is active should not fail", - ); - - // ensure that the nonce of the system address account has not changed - let nonce = - executor.with_state_mut(|state| state.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce); - assert_eq!(nonce, 0); - } - - #[test] - fn eip_4788_genesis_call() { - let db = create_database_with_beacon_root_contract(); - - // activate cancun at genesis - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(0)) - .build(), - ); - - let mut header = chain_spec.genesis_header().clone(); - let provider = evm_config(chain_spec); - let mut executor = provider.batch_executor(db); - - // attempt to execute the genesis block with non-zero parent beacon block root, expect err - header.parent_beacon_block_root = Some(B256::with_last_byte(0x69)); - let _err = executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header: header.clone(), body: Default::default() }, - vec![], - )) - .expect_err( - "Executing genesis cancun block with non-zero parent beacon block root field - should fail", - ); - - // fix header - header.parent_beacon_block_root = Some(B256::ZERO); - - // now try to process the genesis block again, this time ensuring that a system contract - // call does not occur - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header, body: Default::default() }, - vec![], - )) - .unwrap(); - - // there is no system contract call so there should be NO STORAGE CHANGES - // this means we'll check the transition state - let transition_state = executor.with_state_mut(|state| { - state - .transition_state - .take() - .expect("the evm should be initialized with bundle updates") - }); - - // assert that it is the default (empty) transition state - assert_eq!(transition_state, TransitionState::default()); - } - - #[test] - fn eip_4788_high_base_fee() { - // This test ensures that if we have a base fee, then we don't return an error when the - // system contract is called, due to the gas price being less than the base fee. - let header = Header { - timestamp: 1, - number: 1, - parent_beacon_block_root: Some(B256::with_last_byte(0x69)), - base_fee_per_gas: Some(u64::MAX), - excess_blob_gas: Some(0), - ..Header::default() - }; - - let db = create_database_with_beacon_root_contract(); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) - .build(), - ); - - let provider = evm_config(chain_spec); - - // execute header - let mut executor = provider.batch_executor(db); - - // Now execute a block with the fixed header, ensure that it does not fail - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header: header.clone(), body: Default::default() }, - vec![], - )) - .unwrap(); - - // check the actual storage of the contract - it should be: - // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be - // header.timestamp - // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH - // // should be parent_beacon_block_root - let history_buffer_length = 8191u64; - let timestamp_index = header.timestamp % history_buffer_length; - let parent_beacon_block_root_index = - timestamp_index % history_buffer_length + history_buffer_length; - - // get timestamp storage and compare - let timestamp_storage = executor.with_state_mut(|state| { - state.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap() - }); - assert_eq!(timestamp_storage, U256::from(header.timestamp)); - - // get parent beacon block root storage and compare - let parent_beacon_block_root_storage = executor.with_state_mut(|state| { - state.storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index)).unwrap() - }); - assert_eq!(parent_beacon_block_root_storage, U256::from(0x69)); - } - - /// Create a state provider with blockhashes and the EIP-2935 system contract. - fn create_database_with_block_hashes(latest_block: u64) -> CacheDB { - let mut db = CacheDB::new(Default::default()); - for block_number in 0..=latest_block { - db.cache - .block_hashes - .insert(U256::from(block_number), keccak256(block_number.to_string())); - } - - let blockhashes_contract_account = AccountInfo { - balance: U256::ZERO, - code_hash: keccak256(HISTORY_STORAGE_CODE.clone()), - code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())), - nonce: 1, - }; - - db.insert_account_info(HISTORY_STORAGE_ADDRESS, blockhashes_contract_account); - - db - } - #[test] - fn eip_2935_pre_fork() { - let db = create_database_with_block_hashes(1); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(EthereumHardfork::Prague, ForkCondition::Never) - .build(), - ); - - let provider = evm_config(chain_spec); - let mut executor = provider.batch_executor(db); - - // construct the header for block one - let header = Header { timestamp: 1, number: 1, ..Header::default() }; - - // attempt to execute an empty block, this should not fail - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header, body: Default::default() }, - vec![], - )) - .expect( - "Executing a block with no transactions while Prague is active should not fail", - ); - - // ensure that the block hash was *not* written to storage, since this is before the fork - // was activated - // - // we load the account first, because revm expects it to be - // loaded - executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap()); - assert!(executor.with_state_mut(|state| { - state.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap().is_zero() - })); - } - - #[test] - fn eip_2935_fork_activation_genesis() { - let db = create_database_with_block_hashes(0); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .cancun_activated() - .prague_activated() - .build(), - ); - - let header = chain_spec.genesis_header().clone(); - let provider = evm_config(chain_spec); - let mut executor = provider.batch_executor(db); - - // attempt to execute genesis block, this should not fail - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header, body: Default::default() }, - vec![], - )) - .expect( - "Executing a block with no transactions while Prague is active should not fail", - ); - - // ensure that the block hash was *not* written to storage, since there are no blocks - // preceding genesis - // - // we load the account first, because revm expects it to be - // loaded - executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap()); - assert!(executor.with_state_mut(|state| { - state.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap().is_zero() - })); - } - - #[test] - fn eip_2935_fork_activation_within_window_bounds() { - let fork_activation_block = (HISTORY_SERVE_WINDOW - 10) as u64; - let db = create_database_with_block_hashes(fork_activation_block); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .cancun_activated() - .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1)) - .build(), - ); - - let header = Header { - parent_hash: B256::random(), - timestamp: 1, - number: fork_activation_block, - requests_hash: Some(EMPTY_REQUESTS_HASH), - excess_blob_gas: Some(0), - parent_beacon_block_root: Some(B256::random()), - ..Header::default() - }; - let provider = evm_config(chain_spec); - let mut executor = provider.batch_executor(db); - - // attempt to execute the fork activation block, this should not fail - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header, body: Default::default() }, - vec![], - )) - .expect( - "Executing a block with no transactions while Prague is active should not fail", - ); - - // the hash for the ancestor of the fork activation block should be present - assert!(executor - .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some())); - assert_ne!( - executor.with_state_mut(|state| state - .storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block - 1)) - .unwrap()), - U256::ZERO - ); - - // the hash of the block itself should not be in storage - assert!(executor.with_state_mut(|state| { - state - .storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block)) - .unwrap() - .is_zero() - })); - } - - // - #[test] - fn eip_2935_fork_activation_outside_window_bounds() { - let fork_activation_block = (HISTORY_SERVE_WINDOW + 256) as u64; - let db = create_database_with_block_hashes(fork_activation_block); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .cancun_activated() - .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1)) - .build(), - ); - - let provider = evm_config(chain_spec); - let mut executor = provider.batch_executor(db); - - let header = Header { - parent_hash: B256::random(), - timestamp: 1, - number: fork_activation_block, - requests_hash: Some(EMPTY_REQUESTS_HASH), - excess_blob_gas: Some(0), - parent_beacon_block_root: Some(B256::random()), - ..Header::default() - }; - - // attempt to execute the fork activation block, this should not fail - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header, body: Default::default() }, - vec![], - )) - .expect( - "Executing a block with no transactions while Prague is active should not fail", - ); - - // the hash for the ancestor of the fork activation block should be present - assert!(executor - .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some())); - } - - #[test] - fn eip_2935_state_transition_inside_fork() { - let db = create_database_with_block_hashes(2); - - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .cancun_activated() - .prague_activated() - .build(), - ); - - let header = chain_spec.genesis_header().clone(); - let header_hash = header.hash_slow(); - - let provider = evm_config(chain_spec); - let mut executor = provider.batch_executor(db); - - // attempt to execute the genesis block, this should not fail - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header, body: Default::default() }, - vec![], - )) - .expect( - "Executing a block with no transactions while Prague is active should not fail", - ); - - // nothing should be written as the genesis has no ancestors - // - // we load the account first, because revm expects it to be - // loaded - executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap()); - assert!(executor.with_state_mut(|state| { - state.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap().is_zero() - })); - - // attempt to execute block 1, this should not fail - let header = Header { - parent_hash: header_hash, - timestamp: 1, - number: 1, - requests_hash: Some(EMPTY_REQUESTS_HASH), - excess_blob_gas: Some(0), - parent_beacon_block_root: Some(B256::random()), - ..Header::default() - }; - let header_hash = header.hash_slow(); - - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header, body: Default::default() }, - vec![], - )) - .expect( - "Executing a block with no transactions while Prague is active should not fail", - ); - - // the block hash of genesis should now be in storage, but not block 1 - assert!(executor - .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some())); - assert_ne!( - executor.with_state_mut(|state| state - .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO) - .unwrap()), - U256::ZERO - ); - assert!(executor.with_state_mut(|state| { - state.storage(HISTORY_STORAGE_ADDRESS, U256::from(1)).unwrap().is_zero() - })); - - // attempt to execute block 2, this should not fail - let header = Header { - parent_hash: header_hash, - timestamp: 1, - number: 2, - requests_hash: Some(EMPTY_REQUESTS_HASH), - excess_blob_gas: Some(0), - parent_beacon_block_root: Some(B256::random()), - ..Header::default() - }; - - executor - .execute_one(&RecoveredBlock::new_unhashed( - Block { header, body: Default::default() }, - vec![], - )) - .expect( - "Executing a block with no transactions while Prague is active should not fail", - ); - - // the block hash of genesis and block 1 should now be in storage, but not block 2 - assert!(executor - .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some())); - assert_ne!( - executor.with_state_mut(|state| state - .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO) - .unwrap()), - U256::ZERO - ); - assert_ne!( - executor.with_state_mut(|state| state - .storage(HISTORY_STORAGE_ADDRESS, U256::from(1)) - .unwrap()), - U256::ZERO - ); - assert!(executor.with_state_mut(|state| { - state.storage(HISTORY_STORAGE_ADDRESS, U256::from(2)).unwrap().is_zero() - })); - } - - #[test] - fn eip_7002() { - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .cancun_activated() - .prague_activated() - .build(), - ); - - let mut db = create_database_with_withdrawal_requests_contract(); - - let sender_key_pair = generators::generate_key(&mut generators::rng()); - let sender_address = public_key_to_address(sender_key_pair.public_key()); - - db.insert_account_info( - sender_address, - AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() }, - ); - - // https://github.com/lightclient/sys-asm/blob/9282bdb9fd64e024e27f60f507486ffb2183cba2/test/Withdrawal.t.sol.in#L36 - let validator_public_key = fixed_bytes!( - "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" - ); - let withdrawal_amount = fixed_bytes!("0203040506070809"); - let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into(); - assert_eq!(input.len(), 56); - - let mut header = chain_spec.genesis_header().clone(); - header.gas_limit = 1_500_000; - // measured - header.gas_used = 135_856; - header.receipts_root = - b256!("0xb31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3"); - - let tx = sign_tx_with_key_pair( - sender_key_pair, - Transaction::Legacy(TxLegacy { - chain_id: Some(chain_spec.chain.id()), - nonce: 1, - gas_price: header.base_fee_per_gas.unwrap().into(), - gas_limit: header.gas_used, - to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS), - // `MIN_WITHDRAWAL_REQUEST_FEE` - value: U256::from(2), - input, - }), - ); - - let provider = evm_config(chain_spec); - - let mut executor = provider.batch_executor(db); - - let BlockExecutionResult { receipts, requests, .. } = executor - .execute_one( - &Block { header, body: BlockBody { transactions: vec![tx], ..Default::default() } } - .try_into_recovered() - .unwrap(), - ) - .unwrap(); - - let receipt = receipts.first().unwrap(); - assert!(receipt.success); - - // There should be exactly one entry with withdrawal requests - assert_eq!(requests.len(), 1); - assert_eq!(requests[0][0], 1); - } - - #[test] - fn block_gas_limit_error() { - // Create a chain specification with fork conditions set for Prague - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0)) - .build(), - ); - - // Create a state provider with the withdrawal requests contract pre-deployed - let mut db = create_database_with_withdrawal_requests_contract(); - - // Generate a new key pair for the sender - let sender_key_pair = generators::generate_key(&mut generators::rng()); - // Get the sender's address from the public key - let sender_address = public_key_to_address(sender_key_pair.public_key()); - - // Insert the sender account into the state with a nonce of 1 and a balance of 1 ETH in Wei - db.insert_account_info( - sender_address, - AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() }, - ); - - // Define the validator public key and withdrawal amount as fixed bytes - let validator_public_key = fixed_bytes!( - "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" - ); - let withdrawal_amount = fixed_bytes!("2222222222222222"); - // Concatenate the validator public key and withdrawal amount into a single byte array - let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into(); - // Ensure the input length is 56 bytes - assert_eq!(input.len(), 56); - - // Create a genesis block header with a specified gas limit and gas used - let mut header = chain_spec.genesis_header().clone(); - header.gas_limit = 1_500_000; - header.gas_used = 134_807; - header.receipts_root = - b256!("0xb31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3"); - - // Create a transaction with a gas limit higher than the block gas limit - let tx = sign_tx_with_key_pair( - sender_key_pair, - Transaction::Legacy(TxLegacy { - chain_id: Some(chain_spec.chain.id()), - nonce: 1, - gas_price: header.base_fee_per_gas.unwrap().into(), - gas_limit: 2_500_000, // higher than block gas limit - to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS), - value: U256::from(1), - input, - }), - ); - - // Create an executor from the state provider - let evm_config = evm_config(chain_spec); - let mut executor = evm_config.batch_executor(db); - - // Execute the block and capture the result - let exec_result = executor.execute_one( - &Block { header, body: BlockBody { transactions: vec![tx], ..Default::default() } } - .try_into_recovered() - .unwrap(), - ); - - // Check if the execution result is an error and assert the specific error type - match exec_result { - Ok(_) => panic!("Expected block gas limit error"), - Err(err) => assert!(matches!( - *err.as_validation().unwrap(), - BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { - transaction_gas_limit: 2_500_000, - block_available_gas: 1_500_000, - } - )), - } - } - - #[test] - fn test_balance_increment_not_duplicated() { - let chain_spec = Arc::new( - ChainSpecBuilder::from(&*MAINNET) - .shanghai_activated() - .cancun_activated() - .prague_activated() - .build(), - ); - - let withdrawal_recipient = address!("0x1000000000000000000000000000000000000000"); - - let mut db = CacheDB::new(EmptyDB::default()); - let initial_balance = 100; - db.insert_account_info( - withdrawal_recipient, - AccountInfo { balance: U256::from(initial_balance), nonce: 1, ..Default::default() }, - ); - - let withdrawal = - Withdrawal { index: 0, validator_index: 0, address: withdrawal_recipient, amount: 1 }; - - let header = Header { - timestamp: 1, - number: 1, - excess_blob_gas: Some(0), - parent_beacon_block_root: Some(B256::random()), - ..Header::default() - }; - - let block = &RecoveredBlock::new_unhashed( - Block { - header, - body: BlockBody { - transactions: vec![], - ommers: vec![], - withdrawals: Some(vec![withdrawal].into()), - }, - }, - vec![], - ); - - let provider = evm_config(chain_spec); - let executor = provider.batch_executor(db); - - let (tx, rx) = mpsc::channel(); - let tx_clone = tx.clone(); - - let _output = executor - .execute_with_state_hook(block, move |_, state: &EvmState| { - if let Some(account) = state.get(&withdrawal_recipient) { - let _ = tx_clone.send(account.info.balance); - } - }) - .expect("Block execution should succeed"); - - drop(tx); - let balance_changes: Vec = rx.try_iter().collect(); - - if let Some(final_balance) = balance_changes.last() { - let expected_final_balance = U256::from(initial_balance) + U256::from(1_000_000_000); // initial + 1 Gwei in Wei - assert_eq!( - *final_balance, expected_final_balance, - "Final balance should match expected value after withdrawal" - ); - } - } -} diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 8a133ca9ad..ad77ae74ea 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -44,7 +44,15 @@ use alloy_eips::{eip1559::INITIAL_BASE_FEE, eip7840::BlobParams}; pub use config::{revm_spec, revm_spec_by_timestamp_and_block_number}; use reth_ethereum_forks::EthereumHardfork; -pub mod execute; +/// Helper type with backwards compatible methods to obtain Ethereum executor +/// providers. +#[doc(hidden)] +pub mod execute { + use crate::EthEvmConfig; + + #[deprecated(note = "Use `EthEvmConfig` instead")] + pub type EthExecutorProvider = EthEvmConfig; +} mod build; pub use build::EthBlockAssembler; diff --git a/crates/ethereum/evm/tests/execute.rs b/crates/ethereum/evm/tests/execute.rs new file mode 100644 index 0000000000..c7f408f3f1 --- /dev/null +++ b/crates/ethereum/evm/tests/execute.rs @@ -0,0 +1,825 @@ +//! Execution tests. + +use alloy_consensus::{constants::ETH_TO_WEI, Header, TxLegacy}; +use alloy_eips::{ + eip2935::{HISTORY_SERVE_WINDOW, HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE}, + eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS}, + eip4895::Withdrawal, + eip7002::{WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_CODE}, + eip7685::EMPTY_REQUESTS_HASH, +}; +use alloy_evm::block::BlockValidationError; +use alloy_primitives::{b256, fixed_bytes, keccak256, Bytes, TxKind, B256, U256}; +use reth_chainspec::{ChainSpecBuilder, EthereumHardfork, ForkCondition, MAINNET}; +use reth_ethereum_primitives::{Block, BlockBody, Transaction}; +use reth_evm::{execute::Executor, ConfigureEvm}; +use reth_evm_ethereum::EthEvmConfig; +use reth_execution_types::BlockExecutionResult; +use reth_primitives_traits::{ + crypto::secp256k1::public_key_to_address, Block as _, RecoveredBlock, +}; +use reth_testing_utils::generators::{self, sign_tx_with_key_pair}; +use revm::{ + database::{CacheDB, EmptyDB, TransitionState}, + primitives::address, + state::{AccountInfo, Bytecode, EvmState}, + Database, +}; +use std::sync::{mpsc, Arc}; + +fn create_database_with_beacon_root_contract() -> CacheDB { + let mut db = CacheDB::new(Default::default()); + + let beacon_root_contract_account = AccountInfo { + balance: U256::ZERO, + code_hash: keccak256(BEACON_ROOTS_CODE.clone()), + nonce: 1, + code: Some(Bytecode::new_raw(BEACON_ROOTS_CODE.clone())), + }; + + db.insert_account_info(BEACON_ROOTS_ADDRESS, beacon_root_contract_account); + + db +} + +fn create_database_with_withdrawal_requests_contract() -> CacheDB { + let mut db = CacheDB::new(Default::default()); + + let withdrawal_requests_contract_account = AccountInfo { + nonce: 1, + balance: U256::ZERO, + code_hash: keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()), + code: Some(Bytecode::new_raw(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())), + }; + + db.insert_account_info( + WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, + withdrawal_requests_contract_account, + ); + + db +} + +#[test] +fn eip_4788_non_genesis_call() { + let mut header = + Header { timestamp: 1, number: 1, excess_blob_gas: Some(0), ..Header::default() }; + + let db = create_database_with_beacon_root_contract(); + + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) + .build(), + ); + + let provider = EthEvmConfig::new(chain_spec); + + let mut executor = provider.batch_executor(db); + + // attempt to execute a block without parent beacon block root, expect err + let err = executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { + header: header.clone(), + body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None }, + }, + vec![], + )) + .expect_err("Executing cancun block without parent beacon block root field should fail"); + + assert!(matches!( + err.as_validation().unwrap(), + BlockValidationError::MissingParentBeaconBlockRoot + )); + + // fix header, set a gas limit + header.parent_beacon_block_root = Some(B256::with_last_byte(0x69)); + + // Now execute a block with the fixed header, ensure that it does not fail + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { + header: header.clone(), + body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None }, + }, + vec![], + )) + .unwrap(); + + // check the actual storage of the contract - it should be: + // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be + // header.timestamp + // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH // + // should be parent_beacon_block_root + let history_buffer_length = 8191u64; + let timestamp_index = header.timestamp % history_buffer_length; + let parent_beacon_block_root_index = + timestamp_index % history_buffer_length + history_buffer_length; + + let timestamp_storage = executor.with_state_mut(|state| { + state.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap() + }); + assert_eq!(timestamp_storage, U256::from(header.timestamp)); + + // get parent beacon block root storage and compare + let parent_beacon_block_root_storage = executor.with_state_mut(|state| { + state + .storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index)) + .expect("storage value should exist") + }); + assert_eq!(parent_beacon_block_root_storage, U256::from(0x69)); +} + +#[test] +fn eip_4788_no_code_cancun() { + // This test ensures that we "silently fail" when cancun is active and there is no code at + // // BEACON_ROOTS_ADDRESS + let header = Header { + timestamp: 1, + number: 1, + parent_beacon_block_root: Some(B256::with_last_byte(0x69)), + excess_blob_gas: Some(0), + ..Header::default() + }; + + let db = CacheDB::new(EmptyDB::default()); + + // DON'T deploy the contract at genesis + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) + .build(), + ); + + let provider = EthEvmConfig::new(chain_spec); + + // attempt to execute an empty block with parent beacon block root, this should not fail + provider + .batch_executor(db) + .execute_one(&RecoveredBlock::new_unhashed( + Block { + header, + body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None }, + }, + vec![], + )) + .expect("Executing a block with no transactions while cancun is active should not fail"); +} + +#[test] +fn eip_4788_empty_account_call() { + // This test ensures that we do not increment the nonce of an empty SYSTEM_ADDRESS account + // // during the pre-block call + + let mut db = create_database_with_beacon_root_contract(); + + // insert an empty SYSTEM_ADDRESS + db.insert_account_info(SYSTEM_ADDRESS, Default::default()); + + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) + .build(), + ); + + let provider = EthEvmConfig::new(chain_spec); + + // construct the header for block one + let header = Header { + timestamp: 1, + number: 1, + parent_beacon_block_root: Some(B256::with_last_byte(0x69)), + excess_blob_gas: Some(0), + ..Header::default() + }; + + let mut executor = provider.batch_executor(db); + + // attempt to execute an empty block with parent beacon block root, this should not fail + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { + header, + body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None }, + }, + vec![], + )) + .expect("Executing a block with no transactions while cancun is active should not fail"); + + // ensure that the nonce of the system address account has not changed + let nonce = + executor.with_state_mut(|state| state.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce); + assert_eq!(nonce, 0); +} + +#[test] +fn eip_4788_genesis_call() { + let db = create_database_with_beacon_root_contract(); + + // activate cancun at genesis + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(0)) + .build(), + ); + + let mut header = chain_spec.genesis_header().clone(); + let provider = EthEvmConfig::new(chain_spec); + let mut executor = provider.batch_executor(db); + + // attempt to execute the genesis block with non-zero parent beacon block root, expect err + header.parent_beacon_block_root = Some(B256::with_last_byte(0x69)); + let _err = executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header: header.clone(), body: Default::default() }, + vec![], + )) + .expect_err( + "Executing genesis cancun block with non-zero parent beacon block root field + should fail", + ); + + // fix header + header.parent_beacon_block_root = Some(B256::ZERO); + + // now try to process the genesis block again, this time ensuring that a system contract + // call does not occur + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header, body: Default::default() }, + vec![], + )) + .unwrap(); + + // there is no system contract call so there should be NO STORAGE CHANGES + // this means we'll check the transition state + let transition_state = executor.with_state_mut(|state| { + state.transition_state.take().expect("the evm should be initialized with bundle updates") + }); + + // assert that it is the default (empty) transition state + assert_eq!(transition_state, TransitionState::default()); +} + +#[test] +fn eip_4788_high_base_fee() { + // This test ensures that if we have a base fee, then we don't return an error when the + // system contract is called, due to the gas price being less than the base fee. + let header = Header { + timestamp: 1, + number: 1, + parent_beacon_block_root: Some(B256::with_last_byte(0x69)), + base_fee_per_gas: Some(u64::MAX), + excess_blob_gas: Some(0), + ..Header::default() + }; + + let db = create_database_with_beacon_root_contract(); + + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) + .build(), + ); + + let provider = EthEvmConfig::new(chain_spec); + + // execute header + let mut executor = provider.batch_executor(db); + + // Now execute a block with the fixed header, ensure that it does not fail + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header: header.clone(), body: Default::default() }, + vec![], + )) + .unwrap(); + + // check the actual storage of the contract - it should be: + // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be + // header.timestamp + // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH // + // should be parent_beacon_block_root + let history_buffer_length = 8191u64; + let timestamp_index = header.timestamp % history_buffer_length; + let parent_beacon_block_root_index = + timestamp_index % history_buffer_length + history_buffer_length; + + // get timestamp storage and compare + let timestamp_storage = executor.with_state_mut(|state| { + state.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap() + }); + assert_eq!(timestamp_storage, U256::from(header.timestamp)); + + // get parent beacon block root storage and compare + let parent_beacon_block_root_storage = executor.with_state_mut(|state| { + state.storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index)).unwrap() + }); + assert_eq!(parent_beacon_block_root_storage, U256::from(0x69)); +} + +/// Create a state provider with blockhashes and the EIP-2935 system contract. +fn create_database_with_block_hashes(latest_block: u64) -> CacheDB { + let mut db = CacheDB::new(Default::default()); + for block_number in 0..=latest_block { + db.cache.block_hashes.insert(U256::from(block_number), keccak256(block_number.to_string())); + } + + let blockhashes_contract_account = AccountInfo { + balance: U256::ZERO, + code_hash: keccak256(HISTORY_STORAGE_CODE.clone()), + code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())), + nonce: 1, + }; + + db.insert_account_info(HISTORY_STORAGE_ADDRESS, blockhashes_contract_account); + + db +} +#[test] +fn eip_2935_pre_fork() { + let db = create_database_with_block_hashes(1); + + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .with_fork(EthereumHardfork::Prague, ForkCondition::Never) + .build(), + ); + + let provider = EthEvmConfig::new(chain_spec); + let mut executor = provider.batch_executor(db); + + // construct the header for block one + let header = Header { timestamp: 1, number: 1, ..Header::default() }; + + // attempt to execute an empty block, this should not fail + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header, body: Default::default() }, + vec![], + )) + .expect("Executing a block with no transactions while Prague is active should not fail"); + + // ensure that the block hash was *not* written to storage, since this is before the fork + // was activated + // + // we load the account first, because revm expects it to be + // loaded + executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap()); + assert!(executor.with_state_mut(|state| { + state.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap().is_zero() + })); +} + +#[test] +fn eip_2935_fork_activation_genesis() { + let db = create_database_with_block_hashes(0); + + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .cancun_activated() + .prague_activated() + .build(), + ); + + let header = chain_spec.genesis_header().clone(); + let provider = EthEvmConfig::new(chain_spec); + let mut executor = provider.batch_executor(db); + + // attempt to execute genesis block, this should not fail + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header, body: Default::default() }, + vec![], + )) + .expect("Executing a block with no transactions while Prague is active should not fail"); + + // ensure that the block hash was *not* written to storage, since there are no blocks + // preceding genesis + // + // we load the account first, because revm expects it to be + // loaded + executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap()); + assert!(executor.with_state_mut(|state| { + state.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap().is_zero() + })); +} + +#[test] +fn eip_2935_fork_activation_within_window_bounds() { + let fork_activation_block = (HISTORY_SERVE_WINDOW - 10) as u64; + let db = create_database_with_block_hashes(fork_activation_block); + + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .cancun_activated() + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1)) + .build(), + ); + + let header = Header { + parent_hash: B256::random(), + timestamp: 1, + number: fork_activation_block, + requests_hash: Some(EMPTY_REQUESTS_HASH), + excess_blob_gas: Some(0), + parent_beacon_block_root: Some(B256::random()), + ..Header::default() + }; + let provider = EthEvmConfig::new(chain_spec); + let mut executor = provider.batch_executor(db); + + // attempt to execute the fork activation block, this should not fail + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header, body: Default::default() }, + vec![], + )) + .expect("Executing a block with no transactions while Prague is active should not fail"); + + // the hash for the ancestor of the fork activation block should be present + assert!( + executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()) + ); + assert_ne!( + executor.with_state_mut(|state| state + .storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block - 1)) + .unwrap()), + U256::ZERO + ); + + // the hash of the block itself should not be in storage + assert!(executor.with_state_mut(|state| { + state.storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block)).unwrap().is_zero() + })); +} + +// +#[test] +fn eip_2935_fork_activation_outside_window_bounds() { + let fork_activation_block = (HISTORY_SERVE_WINDOW + 256) as u64; + let db = create_database_with_block_hashes(fork_activation_block); + + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .cancun_activated() + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1)) + .build(), + ); + + let provider = EthEvmConfig::new(chain_spec); + let mut executor = provider.batch_executor(db); + + let header = Header { + parent_hash: B256::random(), + timestamp: 1, + number: fork_activation_block, + requests_hash: Some(EMPTY_REQUESTS_HASH), + excess_blob_gas: Some(0), + parent_beacon_block_root: Some(B256::random()), + ..Header::default() + }; + + // attempt to execute the fork activation block, this should not fail + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header, body: Default::default() }, + vec![], + )) + .expect("Executing a block with no transactions while Prague is active should not fail"); + + // the hash for the ancestor of the fork activation block should be present + assert!( + executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()) + ); +} + +#[test] +fn eip_2935_state_transition_inside_fork() { + let db = create_database_with_block_hashes(2); + + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .cancun_activated() + .prague_activated() + .build(), + ); + + let header = chain_spec.genesis_header().clone(); + let header_hash = header.hash_slow(); + + let provider = EthEvmConfig::new(chain_spec); + let mut executor = provider.batch_executor(db); + + // attempt to execute the genesis block, this should not fail + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header, body: Default::default() }, + vec![], + )) + .expect("Executing a block with no transactions while Prague is active should not fail"); + + // nothing should be written as the genesis has no ancestors + // + // we load the account first, because revm expects it to be + // loaded + executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap()); + assert!(executor.with_state_mut(|state| { + state.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap().is_zero() + })); + + // attempt to execute block 1, this should not fail + let header = Header { + parent_hash: header_hash, + timestamp: 1, + number: 1, + requests_hash: Some(EMPTY_REQUESTS_HASH), + excess_blob_gas: Some(0), + parent_beacon_block_root: Some(B256::random()), + ..Header::default() + }; + let header_hash = header.hash_slow(); + + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header, body: Default::default() }, + vec![], + )) + .expect("Executing a block with no transactions while Prague is active should not fail"); + + // the block hash of genesis should now be in storage, but not block 1 + assert!( + executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()) + ); + assert_ne!( + executor + .with_state_mut(|state| state.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap()), + U256::ZERO + ); + assert!(executor.with_state_mut(|state| { + state.storage(HISTORY_STORAGE_ADDRESS, U256::from(1)).unwrap().is_zero() + })); + + // attempt to execute block 2, this should not fail + let header = Header { + parent_hash: header_hash, + timestamp: 1, + number: 2, + requests_hash: Some(EMPTY_REQUESTS_HASH), + excess_blob_gas: Some(0), + parent_beacon_block_root: Some(B256::random()), + ..Header::default() + }; + + executor + .execute_one(&RecoveredBlock::new_unhashed( + Block { header, body: Default::default() }, + vec![], + )) + .expect("Executing a block with no transactions while Prague is active should not fail"); + + // the block hash of genesis and block 1 should now be in storage, but not block 2 + assert!( + executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()) + ); + assert_ne!( + executor + .with_state_mut(|state| state.storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap()), + U256::ZERO + ); + assert_ne!( + executor + .with_state_mut(|state| state.storage(HISTORY_STORAGE_ADDRESS, U256::from(1)).unwrap()), + U256::ZERO + ); + assert!(executor.with_state_mut(|state| { + state.storage(HISTORY_STORAGE_ADDRESS, U256::from(2)).unwrap().is_zero() + })); +} + +#[test] +fn eip_7002() { + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .cancun_activated() + .prague_activated() + .build(), + ); + + let mut db = create_database_with_withdrawal_requests_contract(); + + let sender_key_pair = generators::generate_key(&mut generators::rng()); + let sender_address = public_key_to_address(sender_key_pair.public_key()); + + db.insert_account_info( + sender_address, + AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() }, + ); + + // https://github.com/lightclient/sys-asm/blob/9282bdb9fd64e024e27f60f507486ffb2183cba2/test/Withdrawal.t.sol.in#L36 + let validator_public_key = fixed_bytes!( + "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + ); + let withdrawal_amount = fixed_bytes!("0203040506070809"); + let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into(); + assert_eq!(input.len(), 56); + + let mut header = chain_spec.genesis_header().clone(); + header.gas_limit = 1_500_000; + // measured + header.gas_used = 135_856; + header.receipts_root = + b256!("0xb31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3"); + + let tx = sign_tx_with_key_pair( + sender_key_pair, + Transaction::Legacy(TxLegacy { + chain_id: Some(chain_spec.chain.id()), + nonce: 1, + gas_price: header.base_fee_per_gas.unwrap().into(), + gas_limit: header.gas_used, + to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS), + // `MIN_WITHDRAWAL_REQUEST_FEE` + value: U256::from(2), + input, + }), + ); + + let provider = EthEvmConfig::new(chain_spec); + + let mut executor = provider.batch_executor(db); + + let BlockExecutionResult { receipts, requests, .. } = executor + .execute_one( + &Block { header, body: BlockBody { transactions: vec![tx], ..Default::default() } } + .try_into_recovered() + .unwrap(), + ) + .unwrap(); + + let receipt = receipts.first().unwrap(); + assert!(receipt.success); + + // There should be exactly one entry with withdrawal requests + assert_eq!(requests.len(), 1); + assert_eq!(requests[0][0], 1); +} + +#[test] +fn block_gas_limit_error() { + // Create a chain specification with fork conditions set for Prague + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0)) + .build(), + ); + + // Create a state provider with the withdrawal requests contract pre-deployed + let mut db = create_database_with_withdrawal_requests_contract(); + + // Generate a new key pair for the sender + let sender_key_pair = generators::generate_key(&mut generators::rng()); + // Get the sender's address from the public key + let sender_address = public_key_to_address(sender_key_pair.public_key()); + + // Insert the sender account into the state with a nonce of 1 and a balance of 1 ETH in Wei + db.insert_account_info( + sender_address, + AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() }, + ); + + // Define the validator public key and withdrawal amount as fixed bytes + let validator_public_key = fixed_bytes!( + "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + ); + let withdrawal_amount = fixed_bytes!("2222222222222222"); + // Concatenate the validator public key and withdrawal amount into a single byte array + let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into(); + // Ensure the input length is 56 bytes + assert_eq!(input.len(), 56); + + // Create a genesis block header with a specified gas limit and gas used + let mut header = chain_spec.genesis_header().clone(); + header.gas_limit = 1_500_000; + header.gas_used = 134_807; + header.receipts_root = + b256!("0xb31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3"); + + // Create a transaction with a gas limit higher than the block gas limit + let tx = sign_tx_with_key_pair( + sender_key_pair, + Transaction::Legacy(TxLegacy { + chain_id: Some(chain_spec.chain.id()), + nonce: 1, + gas_price: header.base_fee_per_gas.unwrap().into(), + gas_limit: 2_500_000, // higher than block gas limit + to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS), + value: U256::from(1), + input, + }), + ); + + // Create an executor from the state provider + let evm_config = EthEvmConfig::new(chain_spec); + let mut executor = evm_config.batch_executor(db); + + // Execute the block and capture the result + let exec_result = executor.execute_one( + &Block { header, body: BlockBody { transactions: vec![tx], ..Default::default() } } + .try_into_recovered() + .unwrap(), + ); + + // Check if the execution result is an error and assert the specific error type + match exec_result { + Ok(_) => panic!("Expected block gas limit error"), + Err(err) => assert!(matches!( + *err.as_validation().unwrap(), + BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { + transaction_gas_limit: 2_500_000, + block_available_gas: 1_500_000, + } + )), + } +} + +#[test] +fn test_balance_increment_not_duplicated() { + let chain_spec = Arc::new( + ChainSpecBuilder::from(&*MAINNET) + .shanghai_activated() + .cancun_activated() + .prague_activated() + .build(), + ); + + let withdrawal_recipient = address!("0x1000000000000000000000000000000000000000"); + + let mut db = CacheDB::new(EmptyDB::default()); + let initial_balance = 100; + db.insert_account_info( + withdrawal_recipient, + AccountInfo { balance: U256::from(initial_balance), nonce: 1, ..Default::default() }, + ); + + let withdrawal = + Withdrawal { index: 0, validator_index: 0, address: withdrawal_recipient, amount: 1 }; + + let header = Header { + timestamp: 1, + number: 1, + excess_blob_gas: Some(0), + parent_beacon_block_root: Some(B256::random()), + ..Header::default() + }; + + let block = &RecoveredBlock::new_unhashed( + Block { + header, + body: BlockBody { + transactions: vec![], + ommers: vec![], + withdrawals: Some(vec![withdrawal].into()), + }, + }, + vec![], + ); + + let provider = EthEvmConfig::new(chain_spec); + let executor = provider.batch_executor(db); + + let (tx, rx) = mpsc::channel(); + let tx_clone = tx.clone(); + + let _output = executor + .execute_with_state_hook(block, move |_, state: &EvmState| { + if let Some(account) = state.get(&withdrawal_recipient) { + let _ = tx_clone.send(account.info.balance); + } + }) + .expect("Block execution should succeed"); + + drop(tx); + let balance_changes: Vec = rx.try_iter().collect(); + + if let Some(final_balance) = balance_changes.last() { + let expected_final_balance = U256::from(initial_balance) + U256::from(1_000_000_000); // initial + 1 Gwei in Wei + assert_eq!( + *final_balance, expected_final_balance, + "Final balance should match expected value after withdrawal" + ); + } +} diff --git a/crates/ethereum/node/src/evm.rs b/crates/ethereum/node/src/evm.rs index 99fd6dfd69..4e8fd99f82 100644 --- a/crates/ethereum/node/src/evm.rs +++ b/crates/ethereum/node/src/evm.rs @@ -1,6 +1,7 @@ //! Ethereum EVM support #[doc(inline)] +#[allow(deprecated)] pub use reth_evm_ethereum::execute::EthExecutorProvider; #[doc(inline)] pub use reth_evm_ethereum::{EthEvm, EthEvmConfig}; diff --git a/crates/ethereum/node/src/lib.rs b/crates/ethereum/node/src/lib.rs index 0f6d8ed312..b513eaac19 100644 --- a/crates/ethereum/node/src/lib.rs +++ b/crates/ethereum/node/src/lib.rs @@ -17,7 +17,10 @@ use revm as _; pub use reth_ethereum_engine_primitives::EthEngineTypes; pub mod evm; -pub use evm::{EthEvmConfig, EthExecutorProvider}; +pub use evm::EthEvmConfig; + +#[allow(deprecated)] +pub use evm::EthExecutorProvider; pub use reth_ethereum_consensus as consensus; pub mod node; diff --git a/crates/exex/exex/src/backfill/job.rs b/crates/exex/exex/src/backfill/job.rs index 393d00c62e..bbfd6c2a89 100644 --- a/crates/exex/exex/src/backfill/job.rs +++ b/crates/exex/exex/src/backfill/job.rs @@ -247,7 +247,7 @@ mod tests { BackfillJobFactory, }; use reth_db_common::init::init_genesis; - use reth_evm_ethereum::execute::EthExecutorProvider; + use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::crypto::secp256k1::public_key_to_address; use reth_provider::{ providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, @@ -264,7 +264,7 @@ mod tests { let chain_spec = chain_spec(address); - let executor = EthExecutorProvider::ethereum(chain_spec.clone()); + let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; @@ -300,7 +300,7 @@ mod tests { let chain_spec = chain_spec(address); - let executor = EthExecutorProvider::ethereum(chain_spec.clone()); + let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; diff --git a/crates/exex/exex/src/backfill/stream.rs b/crates/exex/exex/src/backfill/stream.rs index d9328db183..2525f80422 100644 --- a/crates/exex/exex/src/backfill/stream.rs +++ b/crates/exex/exex/src/backfill/stream.rs @@ -247,7 +247,7 @@ mod tests { }; use futures::StreamExt; use reth_db_common::init::init_genesis; - use reth_evm_ethereum::execute::EthExecutorProvider; + use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::crypto::secp256k1::public_key_to_address; use reth_provider::{ providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, @@ -265,7 +265,7 @@ mod tests { let chain_spec = chain_spec(address); - let executor = EthExecutorProvider::ethereum(chain_spec.clone()); + let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; @@ -302,7 +302,7 @@ mod tests { let chain_spec = chain_spec(address); - let executor = EthExecutorProvider::ethereum(chain_spec.clone()); + let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; diff --git a/crates/exex/exex/src/backfill/test_utils.rs b/crates/exex/exex/src/backfill/test_utils.rs index 00bfbd94ee..0485257fa2 100644 --- a/crates/exex/exex/src/backfill/test_utils.rs +++ b/crates/exex/exex/src/backfill/test_utils.rs @@ -9,7 +9,7 @@ use reth_evm::{ execute::{BlockExecutionOutput, Executor}, ConfigureEvm, }; -use reth_evm_ethereum::{execute::EthExecutorProvider, EthEvmConfig}; +use reth_evm_ethereum::EthEvmConfig; use reth_node_api::FullNodePrimitives; use reth_primitives_traits::{Block as _, RecoveredBlock}; use reth_provider::{ @@ -68,7 +68,7 @@ where let provider = provider_factory.provider()?; // Execute the block to produce a block execution output - let mut block_execution_output = EthExecutorProvider::ethereum(chain_spec) + let mut block_execution_output = EthEvmConfig::ethereum(chain_spec) .batch_executor(StateProviderDatabase::new(LatestStateProviderRef::new(&provider))) .execute(block)?; block_execution_output.state.reverts.sort(); diff --git a/crates/exex/exex/src/manager.rs b/crates/exex/exex/src/manager.rs index 9e839d7cf7..d5006dd9f1 100644 --- a/crates/exex/exex/src/manager.rs +++ b/crates/exex/exex/src/manager.rs @@ -663,7 +663,7 @@ mod tests { use futures::{StreamExt, TryStreamExt}; use rand::Rng; use reth_db_common::init::init_genesis; - use reth_evm_ethereum::{execute::EthExecutorProvider, EthEvmConfig}; + use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::RecoveredBlock; use reth_provider::{ providers::BlockchainProvider, test_utils::create_test_provider_factory, BlockReader, @@ -1107,7 +1107,7 @@ mod tests { "test_exex".to_string(), Default::default(), provider, - EthExecutorProvider::mainnet(), + EthEvmConfig::mainnet(), wal.handle(), ); @@ -1162,7 +1162,7 @@ mod tests { "test_exex".to_string(), Default::default(), provider, - EthExecutorProvider::mainnet(), + EthEvmConfig::mainnet(), wal.handle(), ); @@ -1212,7 +1212,7 @@ mod tests { "test_exex".to_string(), Default::default(), provider, - EthExecutorProvider::mainnet(), + EthEvmConfig::mainnet(), wal.handle(), ); @@ -1255,7 +1255,7 @@ mod tests { "test_exex".to_string(), Default::default(), provider, - EthExecutorProvider::mainnet(), + EthEvmConfig::mainnet(), wal.handle(), ); @@ -1315,7 +1315,7 @@ mod tests { "test_exex".to_string(), Default::default(), provider.clone(), - EthExecutorProvider::mainnet(), + EthEvmConfig::mainnet(), wal.handle(), ); diff --git a/crates/exex/exex/src/notifications.rs b/crates/exex/exex/src/notifications.rs index eac5208f2f..651bd7d5b2 100644 --- a/crates/exex/exex/src/notifications.rs +++ b/crates/exex/exex/src/notifications.rs @@ -453,7 +453,7 @@ mod tests { use futures::StreamExt; use reth_db_common::init::init_genesis; use reth_ethereum_primitives::Block; - use reth_evm_ethereum::execute::EthExecutorProvider; + use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::Block as _; use reth_provider::{ providers::BlockchainProvider, test_utils::create_test_provider_factory, BlockWriter, @@ -511,7 +511,7 @@ mod tests { let mut notifications = ExExNotificationsWithoutHead::new( node_head, provider, - EthExecutorProvider::mainnet(), + EthEvmConfig::mainnet(), notifications_rx, wal.handle(), ) @@ -579,7 +579,7 @@ mod tests { let mut notifications = ExExNotificationsWithoutHead::new( node_head, provider, - EthExecutorProvider::mainnet(), + EthEvmConfig::mainnet(), notifications_rx, wal.handle(), ) @@ -618,7 +618,7 @@ mod tests { provider_rw.commit()?; let node_head_notification = ExExNotification::ChainCommitted { new: Arc::new( - BackfillJobFactory::new(EthExecutorProvider::mainnet(), provider.clone()) + BackfillJobFactory::new(EthEvmConfig::mainnet(), provider.clone()) .backfill(node_head.number..=node_head.number) .next() .ok_or_else(|| eyre::eyre!("failed to backfill"))??, @@ -660,7 +660,7 @@ mod tests { let mut notifications = ExExNotificationsWithoutHead::new( node_head, provider, - EthExecutorProvider::mainnet(), + EthEvmConfig::mainnet(), notifications_rx, wal.handle(), ) @@ -736,7 +736,7 @@ mod tests { let mut notifications = ExExNotificationsWithoutHead::new( node_head, provider, - EthExecutorProvider::mainnet(), + EthEvmConfig::mainnet(), notifications_rx, wal.handle(), ) diff --git a/crates/stages/stages/src/lib.rs b/crates/stages/stages/src/lib.rs index 278f6dac1c..2c29bad871 100644 --- a/crates/stages/stages/src/lib.rs +++ b/crates/stages/stages/src/lib.rs @@ -16,7 +16,6 @@ //! # use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; //! # use reth_downloaders::headers::reverse_headers::ReverseHeadersDownloaderBuilder; //! # use reth_network_p2p::test_utils::{TestBodiesClient, TestHeadersClient}; -//! # use reth_evm_ethereum::execute::EthExecutorProvider; //! # use alloy_primitives::B256; //! # use reth_chainspec::MAINNET; //! # use reth_prune_types::PruneModes; @@ -47,7 +46,7 @@ //! # provider_factory.clone() //! # ); //! # let (tip_tx, tip_rx) = watch::channel(B256::default()); -//! # let executor_provider = EthExecutorProvider::mainnet(); +//! # let executor_provider = EthEvmConfig::mainnet(); //! # let static_file_producer = StaticFileProducer::new( //! # provider_factory.clone(), //! # PruneModes::default() diff --git a/crates/stages/stages/src/stages/mod.rs b/crates/stages/stages/src/stages/mod.rs index 7d216cb48f..e1b952db79 100644 --- a/crates/stages/stages/src/stages/mod.rs +++ b/crates/stages/stages/src/stages/mod.rs @@ -64,7 +64,7 @@ mod tests { }; use reth_ethereum_consensus::EthBeaconConsensus; use reth_ethereum_primitives::Block; - use reth_evm_ethereum::execute::EthExecutorProvider; + use reth_evm_ethereum::EthEvmConfig; use reth_exex::ExExManagerHandle; use reth_primitives_traits::{Account, Bytecode, SealedBlock}; use reth_provider::{ @@ -157,7 +157,7 @@ mod tests { // Check execution and create receipts and changesets according to the pruning // configuration let mut execution_stage = ExecutionStage::new( - EthExecutorProvider::ethereum(Arc::new( + EthEvmConfig::ethereum(Arc::new( ChainSpecBuilder::mainnet().berlin_activated().build(), )), Arc::new(EthBeaconConsensus::new(Arc::new( diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index 0a79f3fbe2..1cf905ff2d 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -12,7 +12,7 @@ use reth_db_common::init::{insert_genesis_hashes, insert_genesis_history, insert use reth_ethereum_consensus::{validate_block_post_execution, EthBeaconConsensus}; use reth_ethereum_primitives::Block; use reth_evm::{execute::Executor, ConfigureEvm}; -use reth_evm_ethereum::{execute::EthExecutorProvider, EthEvmConfig}; +use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::{RecoveredBlock, SealedBlock}; use reth_provider::{ test_utils::create_test_provider_factory_with_chain_spec, BlockWriter, DatabaseProviderFactory, @@ -212,7 +212,7 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> { // Decode blocks let blocks = decode_blocks(&case.blocks)?; - let executor_provider = EthExecutorProvider::ethereum(chain_spec.clone()); + let executor_provider = EthEvmConfig::ethereum(chain_spec.clone()); let mut parent = genesis_block; let mut program_inputs = Vec::new();