From b2ead06d1d0804101de0d1eb3a070e08d8eab857 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 27 Feb 2025 17:25:04 +0400 Subject: [PATCH] feat: pending block support in `BlockExecutionStrategyFactory` (#14730) --- .github/assets/check_rv32imac.sh | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- crates/engine/util/src/reorg.rs | 2 +- crates/ethereum/evm/Cargo.toml | 12 +- crates/ethereum/evm/src/config.rs | 2 +- crates/ethereum/evm/src/execute.rs | 277 +++++++++--------- crates/ethereum/evm/src/lib.rs | 8 +- crates/ethereum/payload/src/lib.rs | 46 ++- crates/evm/src/execute.rs | 125 +++++++- crates/evm/src/lib.rs | 10 +- crates/node/api/src/node.rs | 4 +- crates/optimism/evm/src/execute.rs | 123 ++++---- crates/optimism/evm/src/lib.rs | 2 +- crates/optimism/payload/src/builder.rs | 124 +++----- crates/optimism/rpc/src/eth/pending_block.rs | 63 ++-- crates/optimism/rpc/src/witness.rs | 4 +- crates/rpc/rpc-eth-api/src/helpers/call.rs | 174 +++++------ .../rpc-eth-api/src/helpers/pending_block.rs | 204 +++++-------- crates/rpc/rpc-eth-types/src/error/mod.rs | 8 +- crates/rpc/rpc-eth-types/src/pending_block.rs | 8 +- crates/rpc/rpc-eth-types/src/simulate.rs | 58 +++- .../rpc/rpc/src/eth/helpers/pending_block.rs | 48 ++- .../custom-beacon-withdrawals/src/main.rs | 61 +++- 24 files changed, 711 insertions(+), 657 deletions(-) diff --git a/.github/assets/check_rv32imac.sh b/.github/assets/check_rv32imac.sh index f2be927f9c..9d409055ca 100755 --- a/.github/assets/check_rv32imac.sh +++ b/.github/assets/check_rv32imac.sh @@ -19,6 +19,7 @@ crates_to_check=( reth-evm ## ethereum + reth-evm-ethereum reth-ethereum-forks reth-ethereum-primitives diff --git a/Cargo.lock b/Cargo.lock index 1e31959eb6..7c83c63570 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7781,8 +7781,8 @@ dependencies = [ "reth-execution-types", "reth-primitives", "reth-primitives-traits", - "reth-revm", "reth-testing-utils", + "revm", "secp256k1 0.30.0", "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index eede14e276..78c47b1fa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -449,7 +449,7 @@ alloy-eip2124 = { version = "0.1.0", default-features = false } alloy-evm = { version = "0.1", default-features = false } alloy-primitives = { version = "0.8.20", default-features = false, features = ["map-foldhash"] } alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] } -alloy-sol-types = "0.8.20" +alloy-sol-types = { version = "0.8.20", default-features = false } alloy-trie = { version = "0.7", default-features = false } alloy-consensus = { version = "0.11.1", default-features = false } diff --git a/crates/engine/util/src/reorg.rs b/crates/engine/util/src/reorg.rs index 61ad963dfc..e2c385aa72 100644 --- a/crates/engine/util/src/reorg.rs +++ b/crates/engine/util/src/reorg.rs @@ -303,7 +303,7 @@ where .with_bundle_update() .build(); - let mut strategy = evm_config.create_strategy(&mut state, &reorg_target); + let mut strategy = evm_config.strategy_for_block(&mut state, &reorg_target); strategy.apply_pre_execution_changes()?; diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index 0838a59f9f..718becc24f 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -15,13 +15,10 @@ workspace = true reth-execution-types.workspace = true reth-chainspec.workspace = true reth-ethereum-forks.workspace = true -reth-revm.workspace = true +revm.workspace = true reth-evm.workspace = true reth-primitives.workspace = true -# Ethereum -reth-primitives-traits.workspace = true - # Alloy alloy-primitives.workspace = true alloy-eips.workspace = true @@ -32,8 +29,8 @@ alloy-consensus.workspace = true [dev-dependencies] reth-testing-utils.workspace = true reth-evm = { workspace = true, features = ["test-utils"] } -reth-revm = { workspace = true, features = ["test-utils"] } reth-primitives = { workspace = true, features = ["secp256k1"] } +reth-primitives-traits.workspace = true reth-execution-types.workspace = true secp256k1.workspace = true serde_json.workspace = true @@ -43,7 +40,6 @@ alloy-genesis.workspace = true default = ["std"] std = [ "reth-primitives/std", - "reth-revm/std", "alloy-consensus/std", "alloy-eips/std", "alloy-genesis/std", @@ -51,9 +47,11 @@ std = [ "secp256k1/std", "reth-ethereum-forks/std", "serde_json/std", - "reth-primitives-traits/std", "reth-chainspec/std", "alloy-evm/std", "reth-execution-types/std", "reth-evm/std", + "reth-primitives-traits/std", + "alloy-sol-types/std", + "revm/std", ] diff --git a/crates/ethereum/evm/src/config.rs b/crates/ethereum/evm/src/config.rs index 3d0e13faa1..272b0a021c 100644 --- a/crates/ethereum/evm/src/config.rs +++ b/crates/ethereum/evm/src/config.rs @@ -1,7 +1,7 @@ use alloy_consensus::Header; use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_ethereum_forks::EthereumHardfork; -use reth_revm::specification::hardfork::SpecId; +use revm::specification::hardfork::SpecId; /// Map the latest active hardfork at the given header to a revm [`SpecId`]. pub fn revm_spec(chain_spec: &ChainSpec, header: &Header) -> SpecId { diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index e9b4b31f7b..ba16a29cba 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -8,7 +8,7 @@ use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_consensus::{Header, Transaction}; use alloy_eips::{eip4895::Withdrawals, eip6110, eip7685::Requests}; use alloy_evm::FromRecoveredTx; -use alloy_primitives::{Address, B256}; +use alloy_primitives::B256; use reth_chainspec::{ChainSpec, EthereumHardfork, EthereumHardforks}; use reth_evm::{ execute::{ @@ -17,14 +17,16 @@ use reth_evm::{ }, state_change::post_block_balance_increments, system_calls::{OnStateHook, StateChangePostBlockSource, StateChangeSource, SystemCaller}, - ConfigureEvm, Database, Evm, EvmEnv, EvmFactory, TransactionEnv, + Database, Evm, EvmEnv, EvmFactory, EvmFor, InspectorFor, NextBlockEnvAttributes, + TransactionEnv, }; use reth_execution_types::BlockExecutionResult; -use reth_primitives::{EthPrimitives, Receipt, Recovered, SealedBlock, TransactionSigned}; -use reth_primitives_traits::NodePrimitives; -use reth_revm::{ - context_interface::result::ResultAndState, db::State, specification::hardfork::SpecId, - DatabaseCommit, +use reth_primitives::{ + EthPrimitives, Receipt, Recovered, SealedBlock, SealedHeader, TransactionSigned, +}; +use revm::{ + context::result::ExecutionResult, context_interface::result::ResultAndState, database::State, + specification::hardfork::SpecId, DatabaseCommit, }; impl BlockExecutionStrategyFactory for EthEvmConfig @@ -33,57 +35,60 @@ where + Send + Sync + Unpin - + Clone, + + Clone + + 'static, { type Primitives = EthPrimitives; + type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; + type Strategy<'a, DB: Database + 'a, I: InspectorFor<&'a mut State, Self> + 'a> = + EthExecutionStrategy<'a, EvmFor, I>>; - fn create_strategy<'a, DB>( - &'a self, - db: &'a mut State, - block: &'a SealedBlock<::Block>, - ) -> impl BlockExecutionStrategy + 'a - where - DB: Database, - { - let evm = self.evm_for_block(db, block.header()); - EthExecutionStrategy::new(evm, block, &self.chain_spec) - } -} - -/// Input for block execution. -#[derive(Debug, Clone, Copy)] -pub struct EthBlockExecutionInput<'a> { - /// Block number. - pub number: u64, - /// Block timestamp. - pub timestamp: u64, - /// Parent block hash. - pub parent_hash: B256, - /// Block gas limit. - pub gas_limit: u64, - /// Parent beacon block root. - pub parent_beacon_block_root: Option, - /// Block beneficiary. - pub beneficiary: Address, - /// Block ommers - pub ommers: &'a [Header], - /// Block withdrawals. - pub withdrawals: Option<&'a Withdrawals>, -} - -impl<'a> From<&'a SealedBlock> for EthBlockExecutionInput<'a> { - fn from(block: &'a SealedBlock) -> Self { - Self { - number: block.header().number, - timestamp: block.header().timestamp, + fn context_for_block<'a>(&self, block: &'a SealedBlock) -> Self::ExecutionCtx<'a> { + EthBlockExecutionCtx { parent_hash: block.header().parent_hash, - gas_limit: block.header().gas_limit, parent_beacon_block_root: block.header().parent_beacon_block_root, - beneficiary: block.header().beneficiary, ommers: &block.body().ommers, withdrawals: block.body().withdrawals.as_ref(), } } + + fn context_for_next_block<'a>( + &self, + parent: &SealedHeader, + attributes: NextBlockEnvAttributes<'a>, + ) -> Self::ExecutionCtx<'a> { + EthBlockExecutionCtx { + parent_hash: parent.hash(), + parent_beacon_block_root: attributes.parent_beacon_block_root, + ommers: &[], + withdrawals: attributes.withdrawals, + } + } + + fn create_strategy<'a, DB, I>( + &'a self, + evm: EvmFor, I>, + ctx: Self::ExecutionCtx<'a>, + ) -> Self::Strategy<'a, DB, I> + where + DB: Database, + I: InspectorFor<&'a mut State, Self> + 'a, + { + EthExecutionStrategy::new(evm, ctx, &self.chain_spec) + } +} + +/// Context for Ethereum block execution. +#[derive(Debug, Clone, Copy)] +pub struct EthBlockExecutionCtx<'a> { + /// Parent block hash. + pub parent_hash: B256, + /// Parent beacon block root. + pub parent_beacon_block_root: Option, + /// Block ommers + pub ommers: &'a [Header], + /// Block withdrawals. + pub withdrawals: Option<&'a Withdrawals>, } /// Block execution strategy for Ethereum. @@ -92,8 +97,8 @@ pub struct EthExecutionStrategy<'a, Evm> { /// Reference to the [`ChainSpec`]. chain_spec: &'a ChainSpec, - /// Input for block execution. - input: EthBlockExecutionInput<'a>, + /// Context for block execution. + ctx: EthBlockExecutionCtx<'a>, /// The EVM used by strategy. evm: Evm, /// Utility to call system smart contracts. @@ -107,15 +112,11 @@ pub struct EthExecutionStrategy<'a, Evm> { impl<'a, Evm> EthExecutionStrategy<'a, Evm> { /// Creates a new [`EthExecutionStrategy`] - pub fn new( - evm: Evm, - input: impl Into>, - chain_spec: &'a ChainSpec, - ) -> Self { + pub fn new(evm: Evm, ctx: EthBlockExecutionCtx<'a>, chain_spec: &'a ChainSpec) -> Self { Self { evm, chain_spec, - input: input.into(), + ctx, receipts: Vec::new(), gas_used: 0, system_caller: SystemCaller::new(chain_spec), @@ -130,27 +131,28 @@ where { type Error = BlockExecutionError; type Primitives = EthPrimitives; + type Evm = E; fn apply_pre_execution_changes(&mut self) -> Result<(), Self::Error> { // Set state clear flag if the block is after the Spurious Dragon hardfork. let state_clear_flag = - self.chain_spec.is_spurious_dragon_active_at_block(self.input.number); + self.chain_spec.is_spurious_dragon_active_at_block(self.evm.block().number); self.evm.db_mut().set_state_clear_flag(state_clear_flag); + self.system_caller.apply_blockhashes_contract_call(self.ctx.parent_hash, &mut self.evm)?; self.system_caller - .apply_blockhashes_contract_call(self.input.parent_hash, &mut self.evm)?; - self.system_caller - .apply_beacon_root_contract_call(self.input.parent_beacon_block_root, &mut self.evm)?; + .apply_beacon_root_contract_call(self.ctx.parent_beacon_block_root, &mut self.evm)?; Ok(()) } - fn execute_transaction( + fn execute_transaction_with_result_closure( &mut self, tx: Recovered<&TransactionSigned>, + f: impl FnOnce(&ExecutionResult<::HaltReason>), ) -> Result { // The sum of the transaction's gas limit, Tg, and the gas utilized in this block prior, // must be no greater than the block's gasLimit. - let block_available_gas = self.input.gas_limit - self.gas_used; + let block_available_gas = self.evm.block().gas_limit - self.gas_used; if tx.gas_limit() > block_available_gas { return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { transaction_gas_limit: tx.gas_limit(), @@ -169,6 +171,8 @@ where let ResultAndState { result, state } = result_and_state; self.evm.db_mut().commit(state); + f(&result); + let gas_used = result.gas_used(); // append gas used @@ -190,7 +194,8 @@ where fn apply_post_execution_changes( mut self, ) -> Result, Self::Error> { - let requests = if self.chain_spec.is_prague_active_at_timestamp(self.input.timestamp) { + let requests = if self.chain_spec.is_prague_active_at_timestamp(self.evm.block().timestamp) + { // Collect all EIP-6110 deposits let deposit_requests = crate::eip6110::parse_deposits_from_receipts(self.chain_spec, &self.receipts)?; @@ -210,12 +215,13 @@ where let mut balance_increments = post_block_balance_increments( self.chain_spec, self.evm.block(), - self.input.ommers, - self.input.withdrawals, + self.ctx.ommers, + self.ctx.withdrawals, ); // Irregular state change at Ethereum DAO hardfork - if self.chain_spec.fork(EthereumHardfork::Dao).transitions_at_block(self.input.number) { + if self.chain_spec.fork(EthereumHardfork::Dao).transitions_at_block(self.evm.block().number) + { // drain balances from hardcoded addresses. let drained_balance: u128 = self .evm @@ -247,6 +253,10 @@ where fn with_state_hook(&mut self, hook: Option>) { self.system_caller.with_state_hook(hook); } + + fn evm_mut(&mut self) -> &mut Self::Evm { + &mut self.evm + } } /// Helper type with backwards compatible methods to obtain Ethereum executor @@ -281,53 +291,46 @@ mod tests { use reth_chainspec::{ChainSpecBuilder, ForkCondition, MAINNET}; use reth_evm::execute::{BasicBlockExecutorProvider, BlockExecutorProvider, Executor}; use reth_execution_types::BlockExecutionResult; - use reth_primitives::{Account, Block, BlockBody, RecoveredBlock, Transaction}; + use reth_primitives::{Block, BlockBody, RecoveredBlock, Transaction}; use reth_primitives_traits::{crypto::secp256k1::public_key_to_address, Block as _}; - use reth_revm::{ - database::StateProviderDatabase, - db::TransitionState, + use reth_testing_utils::generators::{self, sign_tx_with_key_pair}; + use revm::{ + database::{CacheDB, EmptyDB, TransitionState}, primitives::{address, BLOCKHASH_SERVE_WINDOW}, - state::EvmState, - test_utils::StateProviderTest, + state::{AccountInfo, Bytecode, EvmState}, Database, }; - use reth_testing_utils::generators::{self, sign_tx_with_key_pair}; use secp256k1::{Keypair, Secp256k1}; - use std::{collections::HashMap, sync::mpsc}; + use std::sync::mpsc; - fn create_state_provider_with_beacon_root_contract() -> StateProviderTest { - let mut db = StateProviderTest::default(); + fn create_database_with_beacon_root_contract() -> CacheDB { + let mut db = CacheDB::new(Default::default()); - let beacon_root_contract_account = Account { + let beacon_root_contract_account = AccountInfo { balance: U256::ZERO, - bytecode_hash: Some(keccak256(BEACON_ROOTS_CODE.clone())), + code_hash: keccak256(BEACON_ROOTS_CODE.clone()), nonce: 1, + code: Some(Bytecode::new_raw(BEACON_ROOTS_CODE.clone())), }; - db.insert_account( - BEACON_ROOTS_ADDRESS, - beacon_root_contract_account, - Some(BEACON_ROOTS_CODE.clone()), - HashMap::default(), - ); + db.insert_account_info(BEACON_ROOTS_ADDRESS, beacon_root_contract_account); db } - fn create_state_provider_with_withdrawal_requests_contract() -> StateProviderTest { - let mut db = StateProviderTest::default(); + fn create_database_with_withdrawal_requests_contract() -> CacheDB { + let mut db = CacheDB::new(Default::default()); - let withdrawal_requests_contract_account = Account { + let withdrawal_requests_contract_account = AccountInfo { nonce: 1, balance: U256::ZERO, - bytecode_hash: Some(keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())), + code_hash: keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()), + code: Some(Bytecode::new_raw(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())), }; - db.insert_account( + db.insert_account_info( WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, withdrawal_requests_contract_account, - Some(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()), - HashMap::default(), ); db @@ -342,7 +345,7 @@ mod tests { let mut header = Header { timestamp: 1, number: 1, excess_blob_gas: Some(0), ..Header::default() }; - let db = create_state_provider_with_beacon_root_contract(); + let db = create_database_with_beacon_root_contract(); let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) @@ -353,7 +356,7 @@ mod tests { let provider = executor_provider(chain_spec); - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.executor(db); // attempt to execute a block without parent beacon block root, expect err let err = executor @@ -423,7 +426,7 @@ mod tests { ..Header::default() }; - let db = StateProviderTest::default(); + let db = CacheDB::new(EmptyDB::default()); // DON'T deploy the contract at genesis let chain_spec = Arc::new( @@ -437,7 +440,7 @@ mod tests { // attempt to execute an empty block with parent beacon block root, this should not fail provider - .executor(StateProviderDatabase::new(&db)) + .executor(db) .execute_one(&RecoveredBlock::new_unhashed( Block { header, @@ -455,10 +458,10 @@ mod tests { // 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_state_provider_with_beacon_root_contract(); + let mut db = create_database_with_beacon_root_contract(); // insert an empty SYSTEM_ADDRESS - db.insert_account(SYSTEM_ADDRESS, Account::default(), None, HashMap::default()); + db.insert_account_info(SYSTEM_ADDRESS, Default::default()); let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) @@ -478,7 +481,7 @@ mod tests { ..Header::default() }; - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.executor(db); // attempt to execute an empty block with parent beacon block root, this should not fail executor @@ -501,7 +504,7 @@ mod tests { #[test] fn eip_4788_genesis_call() { - let db = create_state_provider_with_beacon_root_contract(); + let db = create_database_with_beacon_root_contract(); // activate cancun at genesis let chain_spec = Arc::new( @@ -513,7 +516,7 @@ mod tests { let mut header = chain_spec.genesis_header().clone(); let provider = executor_provider(chain_spec); - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.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)); @@ -565,7 +568,7 @@ mod tests { ..Header::default() }; - let db = create_state_provider_with_beacon_root_contract(); + let db = create_database_with_beacon_root_contract(); let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) @@ -577,7 +580,7 @@ mod tests { let provider = executor_provider(chain_spec); // execute header - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.executor(db); // Now execute a block with the fixed header, ensure that it does not fail executor @@ -611,30 +614,26 @@ mod tests { } /// Create a state provider with blockhashes and the EIP-2935 system contract. - fn create_state_provider_with_block_hashes(latest_block: u64) -> StateProviderTest { - let mut db = StateProviderTest::default(); + 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.insert_block_hash(block_number, keccak256(block_number.to_string())); + db.block_hashes.insert(U256::from(block_number), keccak256(block_number.to_string())); } - let blockhashes_contract_account = Account { + let blockhashes_contract_account = AccountInfo { balance: U256::ZERO, - bytecode_hash: Some(keccak256(HISTORY_STORAGE_CODE.clone())), + code_hash: keccak256(HISTORY_STORAGE_CODE.clone()), + code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())), nonce: 1, }; - db.insert_account( - HISTORY_STORAGE_ADDRESS, - blockhashes_contract_account, - Some(HISTORY_STORAGE_CODE.clone()), - HashMap::default(), - ); + db.insert_account_info(HISTORY_STORAGE_ADDRESS, blockhashes_contract_account); db } #[test] fn eip_2935_pre_fork() { - let db = create_state_provider_with_block_hashes(1); + let db = create_database_with_block_hashes(1); let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) @@ -644,7 +643,7 @@ mod tests { ); let provider = executor_provider(chain_spec); - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.executor(db); // construct the header for block one let header = Header { timestamp: 1, number: 1, ..Header::default() }; @@ -673,7 +672,7 @@ mod tests { #[test] fn eip_2935_fork_activation_genesis() { - let db = create_state_provider_with_block_hashes(0); + let db = create_database_with_block_hashes(0); let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) @@ -685,7 +684,7 @@ mod tests { let header = chain_spec.genesis_header().clone(); let provider = executor_provider(chain_spec); - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.executor(db); // attempt to execute genesis block, this should not fail executor @@ -712,7 +711,7 @@ mod tests { #[test] fn eip_2935_fork_activation_within_window_bounds() { let fork_activation_block = (BLOCKHASH_SERVE_WINDOW - 10) as u64; - let db = create_state_provider_with_block_hashes(fork_activation_block); + let db = create_database_with_block_hashes(fork_activation_block); let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) @@ -732,7 +731,7 @@ mod tests { ..Header::default() }; let provider = executor_provider(chain_spec); - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.executor(db); // attempt to execute the fork activation block, this should not fail executor @@ -765,7 +764,7 @@ mod tests { #[test] fn eip_2935_fork_activation_outside_window_bounds() { let fork_activation_block = (BLOCKHASH_SERVE_WINDOW + 256) as u64; - let db = create_state_provider_with_block_hashes(fork_activation_block); + let db = create_database_with_block_hashes(fork_activation_block); let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) @@ -776,7 +775,7 @@ mod tests { ); let provider = executor_provider(chain_spec); - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.executor(db); let header = Header { parent_hash: B256::random(), @@ -805,7 +804,7 @@ mod tests { #[test] fn eip_2935_state_transition_inside_fork() { - let db = create_state_provider_with_block_hashes(2); + let db = create_database_with_block_hashes(2); let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) @@ -819,7 +818,7 @@ mod tests { let header_hash = header.hash_slow(); let provider = executor_provider(chain_spec); - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.executor(db); // attempt to execute the genesis block, this should not fail executor @@ -927,17 +926,15 @@ mod tests { .build(), ); - let mut db = create_state_provider_with_withdrawal_requests_contract(); + let mut db = create_database_with_withdrawal_requests_contract(); let secp = Secp256k1::new(); let sender_key_pair = Keypair::new(&secp, &mut generators::rng()); let sender_address = public_key_to_address(sender_key_pair.public_key()); - db.insert_account( + db.insert_account_info( sender_address, - Account { nonce: 1, balance: U256::from(ETH_TO_WEI), bytecode_hash: None }, - None, - HashMap::default(), + 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 @@ -969,7 +966,7 @@ mod tests { let provider = executor_provider(chain_spec); - let mut executor = provider.executor(StateProviderDatabase::new(&db)); + let mut executor = provider.executor(db); let BlockExecutionResult { receipts, requests, .. } = executor .execute_one( @@ -998,7 +995,7 @@ mod tests { ); // Create a state provider with the withdrawal requests contract pre-deployed - let mut db = create_state_provider_with_withdrawal_requests_contract(); + let mut db = create_database_with_withdrawal_requests_contract(); // Initialize Secp256k1 for key pair generation let secp = Secp256k1::new(); @@ -1008,11 +1005,9 @@ mod tests { 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( + db.insert_account_info( sender_address, - Account { nonce: 1, balance: U256::from(ETH_TO_WEI), bytecode_hash: None }, - None, - HashMap::default(), + AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() }, ); // Define the validator public key and withdrawal amount as fixed bytes @@ -1045,7 +1040,7 @@ mod tests { ); // Create an executor from the state provider - let mut executor = executor_provider(chain_spec).executor(StateProviderDatabase::new(&db)); + let mut executor = executor_provider(chain_spec).executor(db); // Execute the block and capture the result let exec_result = executor.execute_one( @@ -1079,13 +1074,11 @@ mod tests { let withdrawal_recipient = address!("1000000000000000000000000000000000000000"); - let mut db = StateProviderTest::default(); + let mut db = CacheDB::new(EmptyDB::default()); let initial_balance = 100; - db.insert_account( + db.insert_account_info( withdrawal_recipient, - Account { balance: U256::from(initial_balance), nonce: 1, bytecode_hash: None }, - None, - HashMap::default(), + AccountInfo { balance: U256::from(initial_balance), nonce: 1, ..Default::default() }, ); let withdrawal = @@ -1112,7 +1105,7 @@ mod tests { ); let provider = executor_provider(chain_spec); - let executor = provider.executor(StateProviderDatabase::new(&db)); + let executor = provider.executor(db); let (tx, rx) = mpsc::channel(); let tx_clone = tx.clone(); diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 0da5357b03..6993b379dc 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -28,7 +28,7 @@ use reth_evm::{ ConfigureEvm, ConfigureEvmEnv, EvmEnv, EvmFactory, NextBlockEnvAttributes, TransactionEnv, }; use reth_primitives::TransactionSigned; -use reth_revm::{ +use revm::{ context::{BlockEnv, CfgEnv}, context_interface::block::BlobExcessGasAndPrice, specification::hardfork::SpecId, @@ -123,7 +123,7 @@ where fn next_evm_env( &self, parent: &Self::Header, - attributes: NextBlockEnvAttributes, + attributes: NextBlockEnvAttributes<'_>, ) -> Result { // ensure we're not missing any timestamp based hardforks let spec_id = revm_spec_by_timestamp_and_block_number( @@ -204,10 +204,10 @@ mod tests { use alloy_genesis::Genesis; use reth_chainspec::{Chain, ChainSpec}; use reth_evm::{execute::ProviderError, EvmEnv}; - use reth_revm::{ + use revm::{ context::{BlockEnv, CfgEnv}, + database::CacheDB, database_interface::EmptyDBTyped, - db::CacheDB, inspector::NoOpInspector, }; diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index cf66d203bc..3a48612522 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -17,12 +17,12 @@ use reth_basic_payload_builder::{ }; use reth_chainspec::{ChainSpec, ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_errors::{BlockExecutionError, BlockValidationError}; -use reth_ethereum_primitives::{Block, BlockBody, TransactionSigned}; -use reth_evm::{execute::BlockExecutionStrategy, ConfigureEvm, NextBlockEnvAttributes}; -use reth_evm_ethereum::{ - execute::{EthBlockExecutionInput, EthExecutionStrategy}, - EthEvmConfig, +use reth_ethereum_primitives::{Block, BlockBody, EthPrimitives, TransactionSigned}; +use reth_evm::{ + execute::{BlockExecutionStrategy, BlockExecutionStrategyFactory}, + Evm, NextBlockEnvAttributes, }; +use reth_evm_ethereum::EthEvmConfig; use reth_execution_types::{BlockExecutionResult, ExecutionOutcome}; use reth_payload_builder::{EthBuiltPayload, EthPayloadBuilderAttributes}; use reth_payload_builder_primitives::PayloadBuilderError; @@ -81,7 +81,7 @@ impl EthereumPayloadBuilder { // Default implementation of [PayloadBuilder] for unit type impl PayloadBuilder for EthereumPayloadBuilder where - EvmConfig: ConfigureEvm
, + EvmConfig: BlockExecutionStrategyFactory, Client: StateProviderFactory + ChainSpecProvider + Clone, Pool: TransactionPool>, { @@ -136,7 +136,7 @@ pub fn default_ethereum_payload( best_txs: F, ) -> Result, PayloadBuilderError> where - EvmConfig: ConfigureEvm
, + EvmConfig: BlockExecutionStrategyFactory, Client: StateProviderFactory + ChainSpecProvider, Pool: TransactionPool>, F: FnOnce(BestTransactionsAttributes) -> BestTransactionsIter, @@ -154,43 +154,31 @@ where suggested_fee_recipient: attributes.suggested_fee_recipient(), prev_randao: attributes.prev_randao(), gas_limit: builder_config.gas_limit(parent_header.gas_limit), + parent_beacon_block_root: attributes.parent_beacon_block_root(), + withdrawals: Some(attributes.withdrawals()), }; - let evm_env = evm_config - .next_evm_env(&parent_header, next_attributes) + + let mut strategy = evm_config + .strategy_for_next_block(&mut db, &parent_header, next_attributes) .map_err(PayloadBuilderError::other)?; let chain_spec = client.chain_spec(); debug!(target: "payload_builder", id=%attributes.id, parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload"); let mut cumulative_gas_used = 0; - let block_gas_limit: u64 = evm_env.block_env.gas_limit; - let base_fee = evm_env.block_env.basefee; + let block_gas_limit: u64 = strategy.evm_mut().block().gas_limit; + let base_fee = strategy.evm_mut().block().basefee; let mut executed_txs = Vec::new(); let mut best_txs = best_txs(BestTransactionsAttributes::new( base_fee, - evm_env.block_env.blob_gasprice().map(|gasprice| gasprice as u64), + strategy.evm_mut().block().blob_gasprice().map(|gasprice| gasprice as u64), )); let mut total_fees = U256::ZERO; - let block_number = evm_env.block_env.number; - let beneficiary = evm_env.block_env.beneficiary; - - let mut strategy = EthExecutionStrategy::new( - evm_config.evm_with_env(&mut db, evm_env), - EthBlockExecutionInput { - number: parent_header.number + 1, - timestamp: attributes.timestamp(), - parent_hash: parent_header.hash(), - gas_limit: next_attributes.gas_limit, - parent_beacon_block_root: attributes.parent_beacon_block_root, - beneficiary, - ommers: &[], - withdrawals: Some(&attributes.withdrawals), - }, - &chain_spec, - ); + let block_number = strategy.evm_mut().block().number; + let beneficiary = strategy.evm_mut().block().beneficiary; strategy.apply_pre_execution_changes().map_err(|err| { warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index b6dbfbd25a..13c3d0b28f 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -1,8 +1,12 @@ //! Traits for execution. use alloy_consensus::BlockHeader; +use alloy_evm::Evm; // Re-export execution types -use crate::{system_calls::OnStateHook, ConfigureEvmFor, Database}; +use crate::{ + system_calls::OnStateHook, ConfigureEvmFor, Database, EvmFor, InspectorFor, + NextBlockEnvAttributes, +}; use alloc::{boxed::Box, vec::Vec}; use alloy_primitives::{ map::{DefaultHashBuilder, HashMap}, @@ -13,9 +17,15 @@ pub use reth_execution_errors::{ }; use reth_execution_types::BlockExecutionResult; pub use reth_execution_types::{BlockExecutionOutput, ExecutionOutcome}; -use reth_primitives::{NodePrimitives, Receipt, Recovered, RecoveredBlock, SealedBlock}; +use reth_primitives::{ + NodePrimitives, Receipt, Recovered, RecoveredBlock, SealedBlock, SealedHeader, +}; pub use reth_storage_errors::provider::ProviderError; -use revm::state::{Account, AccountStatus, EvmState}; +use revm::{ + context::result::ExecutionResult, + inspector::NoOpInspector, + state::{Account, AccountStatus, EvmState}, +}; use revm_database::{states::bundle_state::BundleRetention, State}; /// A type that knows how to execute a block. It is assumed to operate on a @@ -163,10 +173,23 @@ pub struct ExecuteOutput { } /// Defines the strategy for executing a single block. +/// +/// The current abstraction assumes that block execution consists of the following steps: +/// 1. Apply pre-execution changes. Those might include system calls, irregular state transitions +/// (DAO fork), etc. +/// 2. Apply block transactions to the state. +/// 3. Apply post-execution changes and finalize the state. This might include other system calls, +/// block rewards, etc. +/// +/// The output of [`BlockExecutionStrategy::apply_post_execution_changes`] is a +/// [`BlockExecutionResult`] which contains all relevant information about the block execution. pub trait BlockExecutionStrategy { /// Primitive types used by the strategy. type Primitives: NodePrimitives; + /// EVM used by the strategy. + type Evm: Evm; + /// The error type returned by this strategy's methods. type Error: core::error::Error; @@ -179,6 +202,16 @@ pub trait BlockExecutionStrategy { fn execute_transaction( &mut self, tx: Recovered<&::SignedTx>, + ) -> Result { + self.execute_transaction_with_result_closure(tx, |_| ()) + } + + /// Executes a single transaction and applies execution result to internal state. Invokes the + /// given closure with an internal [`ExecutionResult`] produced by the EVM. + fn execute_transaction_with_result_closure( + &mut self, + tx: Recovered<&::SignedTx>, + f: impl FnOnce(&ExecutionResult<::HaltReason>), ) -> Result; /// Applies any necessary changes after executing the block's transactions. @@ -188,21 +221,89 @@ pub trait BlockExecutionStrategy { /// Sets a hook to be called after each state change during execution. fn with_state_hook(&mut self, hook: Option>); + + /// Exposes mutable reference to EVM. + fn evm_mut(&mut self) -> &mut Self::Evm; } -/// A strategy factory that can create block execution strategies. -pub trait BlockExecutionStrategyFactory: ConfigureEvmFor { +/// A factory that can create block execution strategies. +/// +/// This trait extends [`crate::ConfigureEvm`] and provides a way to construct a +/// [`BlockExecutionStrategy`]. Strategy is expected to derive most of the context for block +/// execution from the EVM (which includes [`revm::context::BlockEnv`]), and any additional context +/// should be contained in configured [`ExecutionCtx`]. +/// +/// Strategy is required to provide a way to obtain [`ExecutionCtx`] from either a complete +/// [`SealedBlock`] (in case of execution of an externally obtained block), or from a parent header +/// along with [`NextBlockEnvAttributes`] (in the case of block building). +/// +/// For more context on the strategy design, see the documentation for [`BlockExecutionStrategy`]. +/// +/// [`ExecutionCtx`]: BlockExecutionStrategyFactory::ExecutionCtx +pub trait BlockExecutionStrategyFactory: ConfigureEvmFor + 'static { /// Primitive types used by the strategy. type Primitives: NodePrimitives; - /// Creates a strategy using the given database. - fn create_strategy<'a, DB>( + /// Strategy this factory produces. + type Strategy<'a, DB: Database + 'a, I: InspectorFor<&'a mut State, Self> + 'a>: BlockExecutionStrategy< + Primitives = Self::Primitives, + Error = BlockExecutionError, + Evm = EvmFor, I>, + >; + + /// Context required for block execution. + /// + /// This is similar to [`alloy_evm::EvmEnv`], but only contains context unrelated to EVM and + /// required for execution of an entire block. + type ExecutionCtx<'a>; + + /// Returns the configured [`BlockExecutionStrategyFactory::ExecutionCtx`] for a given block. + fn context_for_block<'a>( + &self, + block: &'a SealedBlock<::Block>, + ) -> Self::ExecutionCtx<'a>; + + /// Returns the configured [`BlockExecutionStrategyFactory::ExecutionCtx`] for `parent + 1` + /// block. + fn context_for_next_block<'a>( + &self, + parent: &SealedHeader<::BlockHeader>, + attributes: NextBlockEnvAttributes<'a>, + ) -> Self::ExecutionCtx<'a>; + + /// Creates a strategy with given EVM and execution context. + fn create_strategy<'a, DB, I>( + &'a self, + evm: EvmFor, I>, + ctx: Self::ExecutionCtx<'a>, + ) -> Self::Strategy<'a, DB, I> + where + DB: Database, + I: InspectorFor<&'a mut State, Self> + 'a; + + /// Creates a strategy for execution of a given block. + fn strategy_for_block<'a, DB: Database>( &'a self, db: &'a mut State, block: &'a SealedBlock<::Block>, - ) -> impl BlockExecutionStrategy + 'a - where - DB: Database; + ) -> Self::Strategy<'a, DB, NoOpInspector> { + let evm = self.evm_for_block(db, block.header()); + let ctx = self.context_for_block(block); + self.create_strategy(evm, ctx) + } + + /// Creates a strategy for execution of a next block. + fn strategy_for_next_block<'a, DB: Database>( + &'a self, + db: &'a mut State, + parent: &'a SealedHeader<::BlockHeader>, + attributes: NextBlockEnvAttributes<'a>, + ) -> Result, Self::Error> { + let evm_env = self.next_evm_env(parent, attributes)?; + let evm = self.evm_with_env(db, evm_env); + let ctx = self.context_for_next_block(parent, attributes); + Ok(self.create_strategy(evm, ctx)) + } } impl Clone for BasicBlockExecutorProvider @@ -275,7 +376,7 @@ where block: &RecoveredBlock<::Block>, ) -> Result::Receipt>, Self::Error> { - let mut strategy = self.strategy_factory.create_strategy(&mut self.db, block); + let mut strategy = self.strategy_factory.strategy_for_block(&mut self.db, block); strategy.apply_pre_execution_changes()?; for tx in block.transactions_recovered() { @@ -296,7 +397,7 @@ where where H: OnStateHook + 'static, { - let mut strategy = self.strategy_factory.create_strategy(&mut self.db, block); + let mut strategy = self.strategy_factory.strategy_for_block(&mut self.db, block); strategy.with_state_hook(Some(Box::new(state_hook))); strategy.apply_pre_execution_changes()?; diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 11efdf4069..c45ea8f84c 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -17,7 +17,7 @@ extern crate alloc; -use alloy_eips::eip2930::AccessList; +use alloy_eips::{eip2930::AccessList, eip4895::Withdrawals}; pub use alloy_evm::evm::EvmFactory; use alloy_evm::{FromRecoveredTx, IntoTxEnv}; use alloy_primitives::{Address, B256}; @@ -177,7 +177,7 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone { fn next_evm_env( &self, parent: &Self::Header, - attributes: NextBlockEnvAttributes, + attributes: NextBlockEnvAttributes<'_>, ) -> Result, Self::Error>; } @@ -186,7 +186,7 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone { /// [`ConfigureEvmEnv::next_evm_env`] and contains fields that can't be derived from the /// parent header alone (attributes that are determined by the CL.) #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct NextBlockEnvAttributes { +pub struct NextBlockEnvAttributes<'a> { /// The timestamp of the next block. pub timestamp: u64, /// The suggested fee recipient for the next block. @@ -195,6 +195,10 @@ pub struct NextBlockEnvAttributes { pub prev_randao: B256, /// Block gas limit. pub gas_limit: u64, + /// The parent beacon block root. + pub parent_beacon_block_root: Option, + /// Withdrawals + pub withdrawals: Option<&'a Withdrawals>, } /// Abstraction over transaction environment. diff --git a/crates/node/api/src/node.rs b/crates/node/api/src/node.rs index 96991a55c5..26dce7870e 100644 --- a/crates/node/api/src/node.rs +++ b/crates/node/api/src/node.rs @@ -6,7 +6,7 @@ use reth_basic_payload_builder::PayloadBuilder; use reth_consensus::{ConsensusError, FullConsensus}; use reth_db_api::{database_metrics::DatabaseMetrics, Database}; use reth_engine_primitives::{BeaconConsensusEngineEvent, BeaconConsensusEngineHandle}; -use reth_evm::{execute::BlockExecutorProvider, ConfigureEvmFor}; +use reth_evm::execute::{BlockExecutionStrategyFactory, BlockExecutorProvider}; use reth_network_api::FullNetwork; use reth_node_core::node_config::NodeConfig; use reth_node_types::{NodeTypes, NodeTypesWithDBAdapter, NodeTypesWithEngine, TxTy}; @@ -68,7 +68,7 @@ pub trait FullNodeComponents: FullNodeTypes + Clone + 'static { type Pool: TransactionPool>> + Unpin; /// The node's EVM configuration, defining settings for the Ethereum Virtual Machine. - type Evm: ConfigureEvmFor<::Primitives>; + type Evm: BlockExecutionStrategyFactory::Primitives>; /// The type that knows how to execute blocks. type Executor: BlockExecutorProvider::Primitives>; diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 52fd3593c2..9e3d6a374c 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -18,67 +18,67 @@ use reth_evm::{ }, state_change::post_block_balance_increments, system_calls::{OnStateHook, StateChangePostBlockSource, StateChangeSource, SystemCaller}, - ConfigureEvm, Database, Evm, + Database, Evm, EvmFor, InspectorFor, NextBlockEnvAttributes, }; use reth_execution_types::BlockExecutionResult; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{transaction::signed::OpTransaction, DepositReceipt}; -use reth_primitives_traits::{Block, NodePrimitives, SealedBlock, SignedTransaction}; +use reth_primitives_traits::{NodePrimitives, SealedBlock, SealedHeader, SignedTransaction}; use revm::{context::TxEnv, context_interface::result::ResultAndState, DatabaseCommit}; use revm_database::State; -use revm_primitives::{Address, B256}; +use revm_primitives::B256; use tracing::trace; impl BlockExecutionStrategyFactory for OpEvmConfig where - ChainSpec: EthChainSpec + OpHardforks, + ChainSpec: EthChainSpec + OpHardforks + 'static, N: NodePrimitives, revm_optimism::OpTransaction: FromRecoveredTx, { type Primitives = N; + type Strategy<'a, DB: Database + 'a, I: InspectorFor<&'a mut State, Self> + 'a> = + OpExecutionStrategy<'a, EvmFor, I>, N, &'a ChainSpec>; + type ExecutionCtx<'a> = OpBlockExecutionCtx; - fn create_strategy<'a, DB>( - &'a self, - db: &'a mut State, - block: &'a SealedBlock<::Block>, - ) -> impl BlockExecutionStrategy + 'a - where - DB: Database, - { - let evm = self.evm_for_block(db, block.header()); - OpExecutionStrategy::new(evm, block, &self.chain_spec, self.receipt_builder.as_ref()) - } -} - -/// Input for block execution. -#[derive(Debug, Clone, Copy)] -pub struct OpBlockExecutionInput { - /// Block number. - pub number: u64, - /// Block timestamp. - pub timestamp: u64, - /// Parent block hash. - pub parent_hash: B256, - /// Block gas limit. - pub gas_limit: u64, - /// Parent beacon block root. - pub parent_beacon_block_root: Option, - /// Block beneficiary. - pub beneficiary: Address, -} - -impl<'a, B: Block> From<&'a SealedBlock> for OpBlockExecutionInput { - fn from(block: &'a SealedBlock) -> Self { - Self { - number: block.header().number(), - timestamp: block.header().timestamp(), + fn context_for_block<'a>(&self, block: &'a SealedBlock) -> Self::ExecutionCtx<'a> { + OpBlockExecutionCtx { parent_hash: block.header().parent_hash(), - gas_limit: block.header().gas_limit(), parent_beacon_block_root: block.header().parent_beacon_block_root(), - beneficiary: block.header().beneficiary(), } } + + fn context_for_next_block<'a>( + &self, + parent: &SealedHeader, + attributes: NextBlockEnvAttributes<'a>, + ) -> Self::ExecutionCtx<'a> { + OpBlockExecutionCtx { + parent_hash: parent.hash(), + parent_beacon_block_root: attributes.parent_beacon_block_root, + } + } + + fn create_strategy<'a, DB, I>( + &'a self, + evm: EvmFor, I>, + ctx: Self::ExecutionCtx<'a>, + ) -> Self::Strategy<'a, DB, I> + where + DB: Database, + I: reth_evm::InspectorFor<&'a mut State, Self> + 'a, + { + OpExecutionStrategy::new(evm, ctx, self.chain_spec.as_ref(), self.receipt_builder.as_ref()) + } +} + +/// Context for OP block execution. +#[derive(Debug, Clone, Copy)] +pub struct OpBlockExecutionCtx { + /// Parent block hash. + pub parent_hash: B256, + /// Parent beacon block root. + pub parent_beacon_block_root: Option, } /// Block execution strategy for Optimism. @@ -89,8 +89,8 @@ pub struct OpExecutionStrategy<'a, E: Evm, N: NodePrimitives, ChainSpec> { /// Receipt builder. receipt_builder: &'a dyn OpReceiptBuilder, - /// Input for block execution. - input: OpBlockExecutionInput, + /// Context for block execution. + ctx: OpBlockExecutionCtx, /// The EVM used by strategy. evm: E, /// Receipts of executed transactions. @@ -112,20 +112,19 @@ where /// Creates a new [`OpExecutionStrategy`] pub fn new( evm: E, - input: impl Into, + ctx: OpBlockExecutionCtx, chain_spec: ChainSpec, receipt_builder: &'a dyn OpReceiptBuilder, ) -> Self { - let input = input.into(); Self { - is_regolith: chain_spec.is_regolith_active_at_timestamp(input.timestamp), + is_regolith: chain_spec.is_regolith_active_at_timestamp(evm.block().timestamp), evm, system_caller: SystemCaller::new(chain_spec.clone()), chain_spec, receipt_builder, receipts: Vec::new(), gas_used: 0, - input, + ctx, } } } @@ -139,30 +138,39 @@ where { type Primitives = N; type Error = BlockExecutionError; + type Evm = E; fn apply_pre_execution_changes(&mut self) -> Result<(), Self::Error> { // Set state clear flag if the block is after the Spurious Dragon hardfork. let state_clear_flag = - self.chain_spec.is_spurious_dragon_active_at_block(self.input.number); + self.chain_spec.is_spurious_dragon_active_at_block(self.evm.block().number); self.evm.db_mut().set_state_clear_flag(state_clear_flag); self.system_caller - .apply_beacon_root_contract_call(self.input.parent_beacon_block_root, &mut self.evm)?; + .apply_beacon_root_contract_call(self.ctx.parent_beacon_block_root, &mut self.evm)?; // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism // blocks will always have at least a single transaction in them (the L1 info transaction), // so we can safely assume that this will always be triggered upon the transition and that // the above check for empty blocks will never be hit on OP chains. - ensure_create2_deployer(self.chain_spec.clone(), self.input.timestamp, self.evm.db_mut()) - .map_err(|_| OpBlockExecutionError::ForceCreate2DeployerFail)?; + ensure_create2_deployer( + self.chain_spec.clone(), + self.evm.block().timestamp, + self.evm.db_mut(), + ) + .map_err(|_| OpBlockExecutionError::ForceCreate2DeployerFail)?; Ok(()) } - fn execute_transaction(&mut self, tx: Recovered<&N::SignedTx>) -> Result { + fn execute_transaction_with_result_closure( + &mut self, + tx: Recovered<&::SignedTx>, + f: impl FnOnce(&revm::context::result::ExecutionResult<::HaltReason>), + ) -> Result { // The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior, // must be no greater than the block’s gasLimit. - let block_available_gas = self.input.gas_limit - self.gas_used; + let block_available_gas = self.evm.block().gas_limit - self.gas_used; if tx.gas_limit() > block_available_gas && (self.is_regolith || !tx.is_deposit()) { return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { transaction_gas_limit: tx.gas_limit(), @@ -202,6 +210,8 @@ where let ResultAndState { result, state } = result_and_state; self.evm.db_mut().commit(state); + f(&result); + let gas_used = result.gas_used(); // append gas used @@ -232,7 +242,8 @@ where // this is only set for post-Canyon deposit // transactions. deposit_receipt_version: (tx.is_deposit() && - self.chain_spec.is_canyon_active_at_timestamp(self.input.timestamp)) + self.chain_spec + .is_canyon_active_at_timestamp(self.evm.block().timestamp)) .then_some(1), }) } @@ -270,6 +281,10 @@ where fn with_state_hook(&mut self, hook: Option>) { self.system_caller.with_state_hook(hook); } + + fn evm_mut(&mut self) -> &mut Self::Evm { + &mut self.evm + } } /// Helper type with backwards compatible methods to obtain executor providers. diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index a1fa8162c8..c7519a8af6 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -132,7 +132,7 @@ where fn next_evm_env( &self, parent: &Self::Header, - attributes: NextBlockEnvAttributes, + attributes: NextBlockEnvAttributes<'_>, ) -> Result, Self::Error> { // ensure we're not missing any timestamp based hardforks let spec_id = revm_spec_by_timestamp_after_bedrock(&self.chain_spec, attributes.timestamp); diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 45f1895d44..f72a19d005 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -19,12 +19,15 @@ use reth_basic_payload_builder::*; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_evm::{ - execute::{BlockExecutionError, BlockExecutionStrategy, BlockValidationError}, - ConfigureEvm, ConfigureEvmFor, Database, EvmEnv, HaltReasonFor, NextBlockEnvAttributes, + execute::{ + BlockExecutionError, BlockExecutionStrategy, BlockExecutionStrategyFactory, + BlockValidationError, + }, + ConfigureEvm, ConfigureEvmFor, Database, Evm, HaltReasonFor, NextBlockEnvAttributes, }; use reth_execution_types::ExecutionOutcome; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; -use reth_optimism_evm::{OpBlockExecutionInput, OpExecutionStrategy, OpReceiptBuilder}; +use reth_optimism_evm::OpReceiptBuilder; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::transaction::signed::OpTransaction; use reth_optimism_storage::predeploys; @@ -44,7 +47,7 @@ use reth_revm::{ witness::ExecutionWitnessRecord, }; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; -use revm::context_interface::Block; +use revm::context::{Block, BlockEnv}; use std::sync::Arc; use tracing::{debug, trace, warn}; @@ -160,7 +163,7 @@ where Pool: TransactionPool>, Client: StateProviderFactory + ChainSpecProvider, N: OpPayloadPrimitives, - EvmConfig: ConfigureEvmFor, + EvmConfig: BlockExecutionStrategyFactory, { /// Constructs an Optimism payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -178,10 +181,6 @@ where where Txs: PayloadTransactions>, { - let evm_env = self - .evm_env(&args.config.attributes, &args.config.parent_header) - .map_err(PayloadBuilderError::other)?; - let BuildArguments { mut cached_reads, config, cancel, best_payload } = args; let ctx = OpPayloadBuilderCtx { @@ -189,7 +188,6 @@ where da_config: self.config.da_config.clone(), chain_spec: self.client.chain_spec(), config, - evm_env, cancel, best_payload, receipt_builder: self.receipt_builder.clone(), @@ -214,22 +212,6 @@ where .map(|out| out.with_cached_reads(cached_reads)) } - /// Returns the configured [`EvmEnv`] for the targeted payload - /// (that has the `parent` as its parent). - pub fn evm_env( - &self, - attributes: &OpPayloadBuilderAttributes, - parent: &Header, - ) -> Result, EvmConfig::Error> { - let next_attributes = NextBlockEnvAttributes { - timestamp: attributes.timestamp(), - suggested_fee_recipient: attributes.suggested_fee_recipient(), - prev_randao: attributes.prev_randao(), - gas_limit: attributes.gas_limit.unwrap_or(parent.gas_limit), - }; - self.evm_config.next_evm_env(parent, next_attributes) - } - /// Computes the witness for the payload. pub fn payload_witness( &self, @@ -239,15 +221,12 @@ where let attributes = OpPayloadBuilderAttributes::try_new(parent.hash(), attributes, 3) .map_err(PayloadBuilderError::other)?; - let evm_env = self.evm_env(&attributes, &parent).map_err(PayloadBuilderError::other)?; - let config = PayloadConfig { parent_header: Arc::new(parent), attributes }; let ctx: OpPayloadBuilderCtx = OpPayloadBuilderCtx { evm_config: self.evm_config.clone(), da_config: self.config.da_config.clone(), chain_spec: self.client.chain_spec(), config, - evm_env, cancel: Default::default(), best_payload: Default::default(), receipt_builder: self.receipt_builder.clone(), @@ -269,7 +248,7 @@ where Client: StateProviderFactory + ChainSpecProvider + Clone, N: OpPayloadPrimitives, Pool: TransactionPool>, - EvmConfig: ConfigureEvmFor, + EvmConfig: BlockExecutionStrategyFactory, Txs: OpPayloadTransactions, { type Attributes = OpPayloadBuilderAttributes; @@ -348,7 +327,7 @@ impl OpBuilder<'_, Txs> { where N: OpPayloadPrimitives, Txs: PayloadTransactions>, - EvmConfig: ConfigureEvmFor, + EvmConfig: BlockExecutionStrategyFactory, ChainSpec: EthChainSpec + OpHardforks, DB: Database + AsRef

, P: StorageRootProvider, @@ -356,19 +335,23 @@ impl OpBuilder<'_, Txs> { let Self { best } = self; debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); - let mut strategy = OpExecutionStrategy::new( - ctx.evm_config.evm_with_env(&mut *state, ctx.evm_env.clone()), - OpBlockExecutionInput { - number: ctx.evm_env.block_env.number, - timestamp: ctx.evm_env.block_env.timestamp, - parent_hash: ctx.parent().hash(), - gas_limit: ctx.evm_env.block_env.gas_limit, - parent_beacon_block_root: ctx.attributes().parent_beacon_block_root(), - beneficiary: ctx.evm_env.block_env.beneficiary, - }, - &ctx.chain_spec, - ctx.receipt_builder.as_ref(), - ); + let mut strategy = ctx + .evm_config + .strategy_for_next_block( + &mut *state, + ctx.parent(), + NextBlockEnvAttributes { + timestamp: ctx.attributes().timestamp(), + suggested_fee_recipient: ctx.attributes().suggested_fee_recipient(), + prev_randao: ctx.attributes().prev_randao(), + gas_limit: ctx.attributes().gas_limit.unwrap_or(ctx.parent().gas_limit), + parent_beacon_block_root: ctx.attributes().parent_beacon_block_root(), + withdrawals: None, + }, + ) + .map_err(PayloadBuilderError::other)?; + + let block_env = strategy.evm_mut().block().clone(); // 1. apply pre-execution changes strategy.apply_pre_execution_changes().map_err(|err| { @@ -381,7 +364,7 @@ impl OpBuilder<'_, Txs> { // 3. if mem pool transactions are requested we execute them if !ctx.attributes().no_tx_pool { - let best_txs = best(ctx.best_transaction_attributes()); + let best_txs = best(ctx.best_transaction_attributes(strategy.evm_mut().block())); if ctx.execute_best_transactions(&mut info, &mut strategy, best_txs)?.is_some() { return Ok(BuildOutcomeKind::Cancelled) } @@ -411,7 +394,7 @@ impl OpBuilder<'_, Txs> { None }; - let payload = ExecutedPayload { receipts, info, withdrawals_root }; + let payload = ExecutedPayload { receipts, info, withdrawals_root, block_env }; Ok(BuildOutcomeKind::Better { payload }) } @@ -423,21 +406,21 @@ impl OpBuilder<'_, Txs> { ctx: OpPayloadBuilderCtx, ) -> Result>, PayloadBuilderError> where - EvmConfig: ConfigureEvmFor, + EvmConfig: BlockExecutionStrategyFactory, ChainSpec: EthChainSpec + OpHardforks, N: OpPayloadPrimitives, Txs: PayloadTransactions>, DB: Database + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, { - let ExecutedPayload { receipts, info, withdrawals_root } = + let ExecutedPayload { receipts, info, withdrawals_root, block_env } = match self.execute(&mut state, &ctx)? { BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), BuildOutcomeKind::Aborted { fees } => return Ok(BuildOutcomeKind::Aborted { fees }), }; - let block_number = ctx.block_number(); + let block_number = block_env.number; let execution_outcome = ExecutionOutcome::new(state.take_bundle(), vec![receipts], block_number, Vec::new()); let receipts_root = execution_outcome @@ -477,7 +460,7 @@ impl OpBuilder<'_, Txs> { let header = Header { parent_hash: ctx.parent().hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: ctx.evm_env.block_env.beneficiary, + beneficiary: block_env.beneficiary, state_root, transactions_root, receipts_root, @@ -486,9 +469,9 @@ impl OpBuilder<'_, Txs> { timestamp: ctx.attributes().payload_attributes.timestamp, mix_hash: ctx.attributes().payload_attributes.prev_randao, nonce: BEACON_NONCE.into(), - base_fee_per_gas: Some(ctx.base_fee()), + base_fee_per_gas: Some(block_env.basefee), number: ctx.parent().number + 1, - gas_limit: ctx.block_gas_limit(), + gas_limit: block_env.gas_limit, difficulty: U256::ZERO, gas_used: info.cumulative_gas_used, extra_data, @@ -546,7 +529,7 @@ impl OpBuilder<'_, Txs> { ctx: &OpPayloadBuilderCtx, ) -> Result where - EvmConfig: ConfigureEvmFor, + EvmConfig: BlockExecutionStrategyFactory, ChainSpec: EthChainSpec + OpHardforks, N: OpPayloadPrimitives, Txs: PayloadTransactions>, @@ -591,6 +574,8 @@ pub struct ExecutedPayload { pub withdrawals_root: Option, /// The transaction receipts. pub receipts: Vec, + /// The block env used during execution. + pub block_env: BlockEnv, } /// This acts as the container for executed transactions and its byproducts (receipts, gas used) @@ -658,8 +643,6 @@ pub struct OpPayloadBuilderCtx, /// How to build the payload. pub config: PayloadConfig>, - /// Evm Settings - pub evm_env: EvmEnv, /// Marker to check whether the job has been cancelled. pub cancel: CancelOnDrop, /// The currently best payload. @@ -692,26 +675,6 @@ where .then(|| &self.attributes().payload_attributes.withdrawals) } - /// Returns the block gas limit to target. - pub fn block_gas_limit(&self) -> u64 { - self.attributes().gas_limit.unwrap_or(self.evm_env.block_env.gas_limit) - } - - /// Returns the block number for the block. - pub fn block_number(&self) -> u64 { - self.evm_env.block_env.number - } - - /// Returns the current base fee - pub fn base_fee(&self) -> u64 { - self.evm_env.block_env.basefee - } - - /// Returns the current blob gas price. - pub fn get_blob_gasprice(&self) -> Option { - self.evm_env.block_env.blob_gasprice().map(|gasprice| gasprice as u64) - } - /// Returns the blob fields for the header. /// /// This will always return `Some(0)` after ecotone. @@ -744,8 +707,11 @@ where } /// Returns the current fee settings for transactions from the mempool - pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { - BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) + pub fn best_transaction_attributes(&self, block_env: &BlockEnv) -> BestTransactionsAttributes { + BestTransactionsAttributes::new( + block_env.basefee, + block_env.blob_gasprice().map(|p| p as u64), + ) } /// Returns the unique id for this payload job. @@ -855,10 +821,10 @@ where Transaction: PoolTransaction, >, ) -> Result, PayloadBuilderError> { - let block_gas_limit = self.block_gas_limit(); + let block_gas_limit = strategy.evm_mut().block().gas_limit; let block_da_limit = self.da_config.max_da_block_size(); let tx_da_limit = self.da_config.max_da_tx_size(); - let base_fee = self.base_fee(); + let base_fee = strategy.evm_mut().block().basefee; while let Some(tx) = best_txs.next(()) { let tx = tx.into_consensus(); diff --git a/crates/optimism/rpc/src/eth/pending_block.rs b/crates/optimism/rpc/src/eth/pending_block.rs index d0f0a875b6..117fc3ea8c 100644 --- a/crates/optimism/rpc/src/eth/pending_block.rs +++ b/crates/optimism/rpc/src/eth/pending_block.rs @@ -3,20 +3,20 @@ use crate::OpEthApi; use alloy_consensus::{ constants::EMPTY_WITHDRAWALS, proofs::calculate_transaction_root, transaction::Recovered, - Eip658Value, Header, Transaction as _, TxReceipt, EMPTY_OMMER_ROOT_HASH, + Header, Transaction as _, TxReceipt, EMPTY_OMMER_ROOT_HASH, }; use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE, BlockNumberOrTag}; use alloy_primitives::{B256, U256}; -use op_alloy_consensus::{OpDepositReceipt, OpTxType}; use reth_chainspec::{EthChainSpec, EthereumHardforks}; -use reth_evm::{ConfigureEvm, HaltReasonFor}; +use reth_evm::execute::BlockExecutionStrategyFactory; +use reth_node_api::NodePrimitives; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{OpBlock, OpReceipt, OpTransactionSigned}; -use reth_primitives::{logs_bloom, BlockBody, RecoveredBlock}; +use reth_primitives::{logs_bloom, BlockBody, RecoveredBlock, SealedHeader}; use reth_provider::{ - BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderHeader, - ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory, + BlockExecutionResult, BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, + ProviderHeader, ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory, }; use reth_rpc_eth_api::{ helpers::{LoadPendingBlock, SpawnBlocking}, @@ -25,10 +25,7 @@ use reth_rpc_eth_api::{ }; use reth_rpc_eth_types::{EthApiError, PendingBlock}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use revm::{ - context::BlockEnv, - context_interface::{result::ExecutionResult, Block}, -}; +use revm::{context::BlockEnv, context_interface::Block}; impl LoadPendingBlock for OpEthApi where @@ -48,9 +45,12 @@ where > + ChainSpecProvider + StateProviderFactory, Pool: TransactionPool>>, - Evm: ConfigureEvm< - Header = ProviderHeader, - Transaction = ProviderTx, + Evm: BlockExecutionStrategyFactory< + Primitives: NodePrimitives< + SignedTx = ProviderTx, + BlockHeader = ProviderHeader, + Receipt = ProviderReceipt, + >, >, >, { @@ -98,25 +98,25 @@ where fn assemble_block( &self, block_env: &BlockEnv, - parent_hash: B256, + result: &BlockExecutionResult>, + parent: &SealedHeader>, state_root: B256, transactions: Vec>>, - receipts: &[ProviderReceipt], ) -> reth_provider::ProviderBlock { let chain_spec = self.provider().chain_spec(); let timestamp = block_env.timestamp; let transactions_root = calculate_transaction_root(&transactions); let receipts_root = - calculate_receipt_root_no_memo_optimism(receipts, &chain_spec, timestamp); + calculate_receipt_root_no_memo_optimism(&result.receipts, &chain_spec, timestamp); - let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); + let logs_bloom = logs_bloom(result.receipts.iter().flat_map(|r| r.logs())); let is_cancun = chain_spec.is_cancun_active_at_timestamp(timestamp); let is_prague = chain_spec.is_prague_active_at_timestamp(timestamp); let is_shanghai = chain_spec.is_shanghai_active_at_timestamp(timestamp); let header = Header { - parent_hash, + parent_hash: parent.hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, beneficiary: block_env.beneficiary, state_root, @@ -131,7 +131,7 @@ where number: block_env.number, gas_limit: block_env.gas_limit, difficulty: U256::ZERO, - gas_used: receipts.last().map(|r| r.cumulative_gas_used()).unwrap_or_default(), + gas_used: result.gas_used, blob_gas_used: is_cancun.then(|| { transactions.iter().map(|tx| tx.blob_gas_used().unwrap_or_default()).sum::() }), @@ -151,29 +151,4 @@ where }, } } - - fn assemble_receipt( - &self, - tx: &ProviderTx, - result: ExecutionResult>, - cumulative_gas_used: u64, - ) -> reth_provider::ProviderReceipt { - let receipt = alloy_consensus::Receipt { - status: Eip658Value::Eip658(result.is_success()), - cumulative_gas_used, - logs: result.into_logs().into_iter().collect(), - }; - - match tx.tx_type() { - OpTxType::Legacy => OpReceipt::Legacy(receipt), - OpTxType::Eip2930 => OpReceipt::Eip2930(receipt), - OpTxType::Eip1559 => OpReceipt::Eip1559(receipt), - OpTxType::Eip7702 => OpReceipt::Eip7702(receipt), - OpTxType::Deposit => OpReceipt::Deposit(OpDepositReceipt { - inner: receipt, - deposit_nonce: None, - deposit_receipt_version: None, - }), - } - } } diff --git a/crates/optimism/rpc/src/witness.rs b/crates/optimism/rpc/src/witness.rs index 7e2e221dfe..79f25905d0 100644 --- a/crates/optimism/rpc/src/witness.rs +++ b/crates/optimism/rpc/src/witness.rs @@ -5,7 +5,7 @@ use alloy_rpc_types_debug::ExecutionWitness; use jsonrpsee_core::{async_trait, RpcResult}; use op_alloy_rpc_types_engine::OpPayloadAttributes; use reth_chainspec::ChainSpecProvider; -use reth_evm::{ConfigureEvm, ConfigureEvmFor}; +use reth_evm::{execute::BlockExecutionStrategyFactory, ConfigureEvm}; use reth_node_api::NodePrimitives; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_payload_builder::{OpPayloadBuilder, OpPayloadPrimitives}; @@ -69,7 +69,7 @@ where + ChainSpecProvider + Clone + 'static, - EvmConfig: ConfigureEvmFor + 'static, + EvmConfig: BlockExecutionStrategyFactory + 'static, { async fn execute_payload( &self, diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index db405f2a81..9d2698ab1b 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -6,7 +6,7 @@ use crate::{ helpers::estimate::EstimateCall, FromEvmError, FullEthApiTypes, RpcBlock, RpcNodeCore, }; use alloy_consensus::BlockHeader; -use alloy_eips::{eip1559::calc_next_block_base_fee, eip2930::AccessListResult}; +use alloy_eips::eip2930::AccessListResult; use alloy_primitives::{Bytes, B256, U256}; use alloy_rpc_types_eth::{ simulate::{SimBlock, SimulatePayload, SimulatedBlock}, @@ -15,20 +15,23 @@ use alloy_rpc_types_eth::{ BlockId, Bundle, EthCallResponse, StateContext, TransactionInfo, }; use futures::Future; -use reth_chainspec::EthChainSpec; -use reth_errors::ProviderError; +use reth_errors::{ProviderError, RethError}; use reth_evm::{ - ConfigureEvm, ConfigureEvmEnv, Evm, EvmEnv, HaltReasonFor, InspectorFor, SpecFor, - TransactionEnv, + execute::BlockExecutionStrategyFactory, ConfigureEvm, ConfigureEvmEnv, Evm, EvmEnv, + HaltReasonFor, InspectorFor, SpecFor, TransactionEnv, }; use reth_node_api::BlockBody; -use reth_primitives::Recovered; +use reth_primitives::{Recovered, SealedHeader}; use reth_primitives_traits::SignedTransaction; -use reth_provider::{BlockIdReader, ChainSpecProvider, ProviderHeader}; -use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef}; +use reth_provider::{BlockIdReader, ProviderHeader}; +use reth_revm::{ + database::StateProviderDatabase, + db::{CacheDB, State}, + DatabaseRef, +}; use reth_rpc_eth_types::{ cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, - error::{api::FromEvmHalt, ensure_success}, + error::{api::FromEvmHalt, ensure_success, FromEthApiError}, revm_utils::{apply_block_overrides, apply_state_overrides, caller_gas_allowance}, simulate::{self, EthSimulateError}, EthApiError, RevertError, RpcInvalidTransactionError, StateCacheDb, @@ -74,6 +77,8 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA return Err(EthApiError::InvalidParams("too many blocks.".to_string()).into()) } + let block = block.unwrap_or_default(); + let SimulatePayload { block_state_calls, trace_transfers, @@ -85,50 +90,32 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA return Err(EthApiError::InvalidParams(String::from("calls are empty.")).into()) } - // Build cfg and block env, we'll reuse those. - let (mut evm_env, block) = self.evm_env_at(block.unwrap_or_default()).await?; - // Gas cap for entire operation let total_gas_limit = self.call_gas_limit(); let base_block = self.block_with_senders(block).await?.ok_or(EthApiError::HeaderNotFound(block))?; - let mut parent_hash = base_block.hash(); - - // Only enforce base fee if validation is enabled - evm_env.cfg_env.disable_base_fee = !validation; - // Always disable EIP-3607 - evm_env.cfg_env.disable_eip3607 = true; + let mut parent = base_block.sealed_header().clone(); let this = self.clone(); self.spawn_with_state_at_block(block, move |state| { - let mut db = CacheDB::new(StateProviderDatabase::new(state)); + let mut db = + State::builder().with_database(StateProviderDatabase::new(state)).build(); let mut gas_used = 0; let mut blocks: Vec>> = Vec::with_capacity(block_state_calls.len()); - let mut block_state_calls = block_state_calls.into_iter().peekable(); - let chain_spec = RpcNodeCore::provider(&this).chain_spec(); - while let Some(block) = block_state_calls.next() { - // Increase number and timestamp for every new block - evm_env.block_env.number += 1; - evm_env.block_env.timestamp += 1; + for block in block_state_calls { + let mut evm_env = this + .evm_config() + .next_evm_env(&parent, this.next_env_attributes(&parent)?) + .map_err(RethError::other) + .map_err(Self::Error::from_eth_err)?; - if validation { - let base_fee_params = - chain_spec.base_fee_params_at_timestamp(evm_env.block_env.timestamp); - let base_fee = if let Some(latest) = blocks.last() { - let header = &latest.inner.header; - calc_next_block_base_fee( - header.gas_used(), - header.gas_limit(), - header.base_fee_per_gas().unwrap_or_default(), - base_fee_params, - ) - } else { - base_block.next_block_base_fee(base_fee_params).unwrap_or_default() - }; - evm_env.block_env.basefee = base_fee; - } else { + // Always disable EIP-3607 + evm_env.cfg_env.disable_eip3607 = true; + + if !validation { + evm_env.cfg_env.disable_base_fee = !validation; evm_env.block_env.basefee = 0; } @@ -141,6 +128,9 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA apply_state_overrides(state_overrides, &mut db)?; } + let block_env = evm_env.block_env.clone(); + let chain_id = evm_env.cfg_env.chain_id; + if (total_gas_limit - gas_used) < evm_env.block_env.gas_limit { return Err( EthApiError::Other(Box::new(EthSimulateError::GasLimitReached)).into() @@ -152,7 +142,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA let txs_without_gas_limit = calls.iter().filter(|tx| tx.gas.is_none()).count(); - if total_specified_gas > evm_env.block_env.gas_limit { + if total_specified_gas > block_env.gas_limit { return Err(EthApiError::Other(Box::new( EthSimulateError::BlockGasLimitExceeded, )) @@ -160,78 +150,68 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA } if txs_without_gas_limit > 0 { - (evm_env.block_env.gas_limit - total_specified_gas) / + (block_env.gas_limit - total_specified_gas) / txs_without_gas_limit as u64 } else { 0 } }; - let mut calls = calls.into_iter().peekable(); - let mut transactions = Vec::with_capacity(calls.len()); - let mut senders = Vec::with_capacity(calls.len()); - let mut results = Vec::with_capacity(calls.len()); - - while let Some(call) = calls.next() { - // Resolve transaction, populate missing fields and enforce calls - // correctness. - let tx = simulate::resolve_transaction( - call, + let ctx = this + .evm_config() + .context_for_next_block(&parent, this.next_env_attributes(&parent)?); + let (transactions, result, results) = if trace_transfers { + // prepare inspector to capture transfer inside the evm so they are recorded + // and included in logs + let inspector = TransferInspector::new(false).with_logs(true); + let evm = this + .evm_config() + .evm_with_env_and_inspector(&mut db, evm_env, inspector); + let strategy = this.evm_config().create_strategy(evm, ctx); + simulate::execute_transactions( + strategy, + calls, validation, default_gas_limit, - evm_env.cfg_env.chain_id, - &mut db, + chain_id, this.tx_resp_builder(), - )?; + )? + } else { + let evm = this.evm_config().evm_with_env(&mut db, evm_env); + let strategy = this.evm_config().create_strategy(evm, ctx); + simulate::execute_transactions( + strategy, + calls, + validation, + default_gas_limit, + chain_id, + this.tx_resp_builder(), + )? + }; - let tx_env = this.evm_config().tx_env(&tx); + let senders = transactions.iter().map(|tx| tx.signer()).collect(); - let (res, (_, tx_env)) = { - if trace_transfers { - this.transact_with_inspector( - &mut db, - evm_env.clone(), - tx_env, - TransferInspector::new(false) - // capture transfer inside the evm so they are recorded and - // included in the result - .with_logs(true), - )? - } else { - this.transact(&mut db, evm_env.clone(), tx_env.clone())? - } - }; - - if calls.peek().is_some() || block_state_calls.peek().is_some() { - // need to apply the state changes of this call before executing the - // next call - db.commit(res.state); - } - - transactions.push(tx); - senders.push(tx_env.caller()); - results.push(res.result); - } - - let (block, _) = this.assemble_block_and_receipts( - &evm_env.block_env, - parent_hash, + let block = this.assemble_block( + &block_env, + &result, + &parent, // state root calculation is skipped for performance reasons B256::ZERO, transactions, - results.clone(), ); - let block: SimulatedBlock> = - simulate::build_simulated_block( - senders, - results, - return_full_transactions, - this.tx_resp_builder(), - block, - )?; + let block = simulate::build_simulated_block( + senders, + results, + return_full_transactions, + this.tx_resp_builder(), + block, + )?; - parent_hash = block.inner.header.hash; + parent = SealedHeader::new( + block.inner.header.inner.clone(), + block.inner.header.hash, + ); gas_used += block.inner.header.gas_used(); blocks.push(block); diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index 0b1d67479c..09230ffecf 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -9,17 +9,18 @@ use alloy_primitives::B256; use alloy_rpc_types_eth::BlockNumberOrTag; use futures::Future; use reth_chainspec::{EthChainSpec, EthereumHardforks}; -use reth_errors::RethError; +use reth_errors::{BlockExecutionError, BlockValidationError, RethError}; use reth_evm::{ - state_change::post_block_withdrawals_balance_increments, system_calls::SystemCaller, - ConfigureEvm, ConfigureEvmEnv, Evm, EvmEnv, EvmError, HaltReasonFor, InvalidTxError, - NextBlockEnvAttributes, + execute::{BlockExecutionStrategy, BlockExecutionStrategyFactory}, + ConfigureEvmEnv, Evm, NextBlockEnvAttributes, }; -use reth_primitives::{InvalidTransactionError, RecoveredBlock}; +use reth_node_api::NodePrimitives; +use reth_primitives::{InvalidTransactionError, RecoveredBlock, SealedHeader}; use reth_primitives_traits::Receipt; use reth_provider::{ - BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderError, ProviderHeader, - ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory, + BlockExecutionResult, BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, + ProviderError, ProviderHeader, ProviderReceipt, ProviderTx, ReceiptProvider, + StateProviderFactory, }; use reth_revm::{ database::StateProviderDatabase, @@ -30,13 +31,7 @@ use reth_transaction_pool::{ error::InvalidPoolTransactionError, BestTransactionsAttributes, PoolTransaction, TransactionPool, }; -use revm::{ - context::BlockEnv, - context_interface::{ - result::{ExecutionResult, ResultAndState}, - Block, - }, -}; +use revm::{context::BlockEnv, context_interface::Block}; use std::time::{Duration, Instant}; use tokio::sync::Mutex; use tracing::debug; @@ -55,9 +50,12 @@ pub trait LoadPendingBlock: + ChainSpecProvider + StateProviderFactory, Pool: TransactionPool>>, - Evm: ConfigureEvm< - Header = ProviderHeader, - Transaction = ProviderTx, + Evm: BlockExecutionStrategyFactory< + Primitives: NodePrimitives< + BlockHeader = ProviderHeader, + SignedTx = ProviderTx, + Receipt = ProviderReceipt, + >, >, > { @@ -69,7 +67,7 @@ pub trait LoadPendingBlock: &self, ) -> &Mutex, ProviderReceipt>>>; - /// Configures the [`EvmEnv`] for the pending block + /// Configures the [`PendingBlockEnv`] for the pending block /// /// If no pending block is available, this will derive it from the `latest` block #[expect(clippy::type_complexity)] @@ -113,19 +111,26 @@ pub trait LoadPendingBlock: let evm_env = self .evm_config() - .next_evm_env( - &latest, - NextBlockEnvAttributes { - timestamp: latest.timestamp().saturating_add(12), - suggested_fee_recipient: latest.beneficiary(), - prev_randao: B256::random(), - gas_limit: latest.gas_limit(), - }, - ) + .next_evm_env(&latest, self.next_env_attributes(&latest)?) .map_err(RethError::other) .map_err(Self::Error::from_eth_err)?; - Ok(PendingBlockEnv::new(evm_env, PendingBlockEnvOrigin::DerivedFromLatest(latest.hash()))) + Ok(PendingBlockEnv::new(evm_env, PendingBlockEnvOrigin::DerivedFromLatest(latest))) + } + + /// Returns [`NextBlockEnvAttributes`] for building a local pending block. + fn next_env_attributes( + &self, + parent: &SealedHeader>, + ) -> Result, Self::Error> { + Ok(NextBlockEnvAttributes { + timestamp: parent.timestamp().saturating_add(12), + suggested_fee_recipient: parent.beneficiary(), + prev_randao: B256::random(), + gas_limit: parent.gas_limit(), + parent_beacon_block_root: parent.parent_beacon_block_root(), + withdrawals: None, + }) } /// Returns the locally built pending block @@ -146,11 +151,11 @@ pub trait LoadPendingBlock: { async move { let pending = self.pending_block_env_and_cfg()?; - let parent_hash = match pending.origin { + let parent = match pending.origin { PendingBlockEnvOrigin::ActualPending(block, receipts) => { return Ok(Some((block, receipts))); } - PendingBlockEnvOrigin::DerivedFromLatest(parent_hash) => parent_hash, + PendingBlockEnvOrigin::DerivedFromLatest(parent) => parent, }; // we couldn't find the real pending block, so we need to build it ourselves @@ -162,7 +167,7 @@ pub trait LoadPendingBlock: if let Some(pending_block) = lock.as_ref() { // this is guaranteed to be the `latest` header if pending.evm_env.block_env.number == pending_block.block.number() && - parent_hash == pending_block.block.parent_hash() && + parent.hash() == pending_block.block.parent_hash() && now <= pending_block.expires_at { return Ok(Some((pending_block.block.clone(), pending_block.receipts.clone()))); @@ -173,7 +178,7 @@ pub trait LoadPendingBlock: let (sealed_block, receipts) = match self .spawn_blocking_io(move |this| { // we rebuild the block - this.build_block(pending.evm_env, parent_hash) + this.build_block(&parent) }) .await { @@ -195,47 +200,16 @@ pub trait LoadPendingBlock: } } - /// Assembles a receipt for a transaction, based on its [`ExecutionResult`]. - fn assemble_receipt( - &self, - tx: &ProviderTx, - result: ExecutionResult>, - cumulative_gas_used: u64, - ) -> ProviderReceipt; - /// Assembles a pending block. fn assemble_block( &self, block_env: &BlockEnv, - parent_hash: B256, + result: &BlockExecutionResult>, + parent: &SealedHeader>, state_root: B256, transactions: Vec>>, - receipts: &[ProviderReceipt], ) -> ProviderBlock; - /// Helper to invoke both [`Self::assemble_block`] and [`Self::assemble_receipt`]. - fn assemble_block_and_receipts( - &self, - block_env: &BlockEnv, - parent_hash: B256, - state_root: B256, - transactions: Vec>>, - results: Vec>>, - ) -> (ProviderBlock, Vec>) { - let mut cumulative_gas_used = 0; - let mut receipts = Vec::with_capacity(results.len()); - - for (tx, outcome) in transactions.iter().zip(results) { - cumulative_gas_used += outcome.gas_used(); - receipts.push(self.assemble_receipt(tx, outcome, cumulative_gas_used)); - } - - let block = - self.assemble_block(block_env, parent_hash, state_root, transactions, &receipts); - - (block, receipts) - } - /// Builds a pending block using the configured provider and pool. /// /// If the origin is the actual pending block, the block is built with withdrawals. @@ -245,8 +219,7 @@ pub trait LoadPendingBlock: #[expect(clippy::type_complexity)] fn build_block( &self, - evm_env: EvmEnv<::Spec>, - parent_hash: B256, + parent: &SealedHeader>, ) -> Result< (RecoveredBlock>, Vec>), Self::Error, @@ -256,34 +229,32 @@ pub trait LoadPendingBlock: { let state_provider = self .provider() - .history_by_block_hash(parent_hash) + .history_by_block_hash(parent.hash()) .map_err(Self::Error::from_eth_err)?; let state = StateProviderDatabase::new(state_provider); let mut db = State::builder().with_database(state).with_bundle_update().build(); + let mut strategy = self + .evm_config() + .strategy_for_next_block(&mut db, parent, self.next_env_attributes(parent)?) + .map_err(RethError::other) + .map_err(Self::Error::from_eth_err)?; + + strategy.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?; + + let block_env = strategy.evm_mut().block().clone(); + let mut cumulative_gas_used = 0; let mut sum_blob_gas_used = 0; - let block_gas_limit: u64 = evm_env.block_env.gas_limit; - let base_fee = evm_env.block_env.basefee; + let block_gas_limit: u64 = block_env.gas_limit; let mut executed_txs = Vec::new(); let mut best_txs = self.pool().best_transactions_with_attributes(BestTransactionsAttributes::new( - base_fee, - evm_env.block_env.blob_gasprice().map(|gasprice| gasprice as u64), + block_env.basefee, + block_env.blob_gasprice().map(|gasprice| gasprice as u64), )); - let chain_spec = self.provider().chain_spec(); - - let mut system_caller = SystemCaller::new(chain_spec.clone()); - let mut evm = self.evm_config().evm_with_env(&mut db, evm_env.clone()); - - system_caller - .apply_blockhashes_contract_call(parent_hash, &mut evm) - .map_err(|err| EthApiError::Internal(err.into()))?; - - let mut results = Vec::new(); - while let Some(pool_tx) = best_txs.next() { // ensure we still have capacity for this transaction if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit { @@ -335,29 +306,28 @@ pub trait LoadPendingBlock: } } - let tx_env = self.evm_config().tx_env(&tx); - - let ResultAndState { result, state: _ } = match evm.transact_commit(tx_env) { - Ok(res) => res, - Err(err) => { - if let Some(err) = err.as_invalid_tx_err() { - if err.is_nonce_too_low() { - // if the nonce is too low, we can skip this transaction - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - best_txs.mark_invalid( - &pool_tx, - InvalidPoolTransactionError::Consensus( - InvalidTransactionError::TxTypeNotSupported, - ), - ); - } - continue + let gas_used = match strategy.execute_transaction(tx.as_recovered_ref()) { + Ok(gas_used) => gas_used, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + if error.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + best_txs.mark_invalid( + &pool_tx, + InvalidPoolTransactionError::Consensus( + InvalidTransactionError::TxTypeNotSupported, + ), + ); } - // this is an error that we should treat as fatal for this attempt - return Err(Self::Error::from_evm_err(err)); + continue } + // this is an error that we should treat as fatal for this attempt + Err(err) => return Err(Self::Error::from_eth_err(err)), }; // add to the total blob gas used if the transaction successfully executed @@ -370,28 +340,14 @@ pub trait LoadPendingBlock: } } - let gas_used = result.gas_used(); - // add gas used by the transaction to cumulative gas used, before creating the receipt cumulative_gas_used += gas_used; // append transaction to the list of executed transactions executed_txs.push(tx); - results.push(result); } - // executes the withdrawals and commits them to the Database and BundleState. - let balance_increments = post_block_withdrawals_balance_increments( - chain_spec.as_ref(), - evm_env.block_env.timestamp, - &[], - ); - - // release db - drop(evm); - - // increment account balances for withdrawals - db.increment_balances(balance_increments).map_err(Self::Error::from_eth_err)?; + let result = strategy.apply_post_execution_changes().map_err(Self::Error::from_eth_err)?; // merge all transitions into bundle state. db.merge_transitions(BundleRetention::PlainState); @@ -404,14 +360,8 @@ pub trait LoadPendingBlock: let senders = executed_txs.iter().map(|tx| tx.signer()).collect(); - let (block, receipts) = self.assemble_block_and_receipts( - &evm_env.block_env, - parent_hash, - state_root, - executed_txs, - results, - ); + let block = self.assemble_block(&block_env, &result, parent, state_root, executed_txs); - Ok((RecoveredBlock::new_unhashed(block, senders), receipts)) + Ok((RecoveredBlock::new_unhashed(block, senders), result.receipts)) } } diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index da1e34af1a..60420ecdc4 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -8,7 +8,7 @@ use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError use alloy_sol_types::{ContractError, RevertReason}; pub use api::{AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError}; use core::time::Duration; -use reth_errors::RethError; +use reth_errors::{BlockExecutionError, RethError}; use reth_primitives_traits::transaction::signed::RecoveryError; use reth_rpc_server_types::result::{ block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code, @@ -255,6 +255,12 @@ impl From for EthApiError { } } +impl From for EthApiError { + fn from(error: BlockExecutionError) -> Self { + Self::Internal(error.into()) + } +} + impl From for EthApiError { fn from(error: reth_errors::ProviderError) -> Self { use reth_errors::ProviderError; diff --git a/crates/rpc/rpc-eth-types/src/pending_block.rs b/crates/rpc/rpc-eth-types/src/pending_block.rs index 08a0f8f3c7..246bc6c49e 100644 --- a/crates/rpc/rpc-eth-types/src/pending_block.rs +++ b/crates/rpc/rpc-eth-types/src/pending_block.rs @@ -9,7 +9,7 @@ use alloy_eips::{BlockId, BlockNumberOrTag}; use alloy_primitives::B256; use derive_more::Constructor; use reth_evm::EvmEnv; -use reth_primitives::{Receipt, RecoveredBlock}; +use reth_primitives::{Receipt, RecoveredBlock, SealedHeader}; use reth_primitives_traits::Block; /// Configured [`EvmEnv`] for a pending block. @@ -32,7 +32,7 @@ pub enum PendingBlockEnvOrigin { /// - the timestamp /// - the block number /// - fees - DerivedFromLatest(B256), + DerivedFromLatest(SealedHeader), } impl PendingBlockEnvOrigin { @@ -56,7 +56,7 @@ impl PendingBlockEnvOrigin { pub fn state_block_id(&self) -> BlockId { match self { Self::ActualPending(_, _) => BlockNumberOrTag::Pending.into(), - Self::DerivedFromLatest(hash) => BlockId::Hash((*hash).into()), + Self::DerivedFromLatest(latest) => BlockId::Hash(latest.hash().into()), } } @@ -68,7 +68,7 @@ impl PendingBlockEnvOrigin { pub fn build_target_hash(&self) -> B256 { match self { Self::ActualPending(block, _) => block.header().parent_hash(), - Self::DerivedFromLatest(hash) => *hash, + Self::DerivedFromLatest(latest) => latest.hash(), } } } diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index d9a6ba06f5..d54525dc9f 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -7,7 +7,9 @@ use alloy_rpc_types_eth::{ Block, BlockTransactionsKind, Header, }; use jsonrpsee_types::ErrorObject; -use reth_primitives::{Recovered, RecoveredBlock}; +use reth_evm::{execute::BlockExecutionStrategy, Evm}; +use reth_execution_types::BlockExecutionResult; +use reth_primitives::{NodePrimitives, Recovered, RecoveredBlock}; use reth_primitives_traits::{block::BlockTx, BlockBody as _, SignedTransaction}; use reth_rpc_server_types::result::rpc_err; use reth_rpc_types_compat::{block::from_block, TransactionCompat}; @@ -48,6 +50,60 @@ impl ToRpcError for EthSimulateError { } } +/// Converts all [`TransactionRequest`]s into [`Recovered`] transactions and applies them to the +/// given [`BlockExecutionStrategy`]. +/// +/// Returns all executed transactions and the result of the execution. +#[expect(clippy::type_complexity)] +pub fn execute_transactions( + mut strategy: S, + calls: Vec, + validation: bool, + default_gas_limit: u64, + chain_id: u64, + tx_resp_builder: &T, +) -> Result< + ( + Vec>, + BlockExecutionResult, + Vec::HaltReason>>, + ), + EthApiError, +> +where + N: NodePrimitives, + S: BlockExecutionStrategy, + EthApiError: From + From<<::DB as Database>::Error>, + S::Evm: Evm>>, + T: TransactionCompat, +{ + strategy.apply_pre_execution_changes()?; + + let mut transactions = Vec::with_capacity(calls.len()); + let mut results = Vec::with_capacity(calls.len()); + for call in calls { + // Resolve transaction, populate missing fields and enforce calls + // correctness. + let tx = resolve_transaction( + call, + validation, + default_gas_limit, + chain_id, + strategy.evm_mut().db_mut(), + tx_resp_builder, + )?; + + strategy.execute_transaction_with_result_closure(tx.as_recovered_ref(), |result| { + results.push(result.clone()) + })?; + transactions.push(tx); + } + + let result = strategy.apply_post_execution_changes()?; + + Ok((transactions, result, results)) +} + /// Goes over the list of [`TransactionRequest`]s and populates missing fields trying to resolve /// them into primitive transactions. /// diff --git a/crates/rpc/rpc/src/eth/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs index 92d26b95ac..52b25e3b2d 100644 --- a/crates/rpc/rpc/src/eth/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -7,12 +7,13 @@ use alloy_consensus::{ use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_primitives::U256; use reth_chainspec::{EthChainSpec, EthereumHardforks}; -use reth_evm::{ConfigureEvm, HaltReasonFor}; -use reth_primitives::{logs_bloom, BlockBody, Receipt}; +use reth_evm::execute::BlockExecutionStrategyFactory; +use reth_node_api::NodePrimitives; +use reth_primitives::{logs_bloom, BlockBody, Receipt, SealedHeader}; use reth_primitives_traits::proofs::calculate_transaction_root; use reth_provider::{ - BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderReceipt, ProviderTx, - StateProviderFactory, + BlockExecutionResult, BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, + ProviderHeader, ProviderReceipt, ProviderTx, StateProviderFactory, }; use reth_rpc_eth_api::{ helpers::{LoadPendingBlock, SpawnBlocking}, @@ -21,10 +22,7 @@ use reth_rpc_eth_api::{ }; use reth_rpc_eth_types::PendingBlock; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use revm::{ - context::BlockEnv, - context_interface::{result::ExecutionResult, Block}, -}; +use revm::{context::BlockEnv, context_interface::Block}; use revm_primitives::B256; use crate::EthApi; @@ -46,7 +44,13 @@ where Pool: TransactionPool< Transaction: PoolTransaction>, >, - Evm: ConfigureEvm

>, + Evm: BlockExecutionStrategyFactory< + Primitives: NodePrimitives< + BlockHeader = Header, + SignedTx = ProviderTx, + Receipt = ProviderReceipt, + >, + >, >, Provider: BlockReader, { @@ -62,17 +66,17 @@ where fn assemble_block( &self, block_env: &BlockEnv, - parent_hash: revm_primitives::B256, + result: &BlockExecutionResult>, + parent: &SealedHeader>, state_root: revm_primitives::B256, transactions: Vec>>, - receipts: &[ProviderReceipt], ) -> reth_provider::ProviderBlock { let chain_spec = self.provider().chain_spec(); let transactions_root = calculate_transaction_root(&transactions); - let receipts_root = Receipt::calculate_receipt_root_no_memo(receipts); + let receipts_root = Receipt::calculate_receipt_root_no_memo(&result.receipts); - let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| &r.logs)); + let logs_bloom = logs_bloom(result.receipts.iter().flat_map(|r| &r.logs)); let timestamp = block_env.timestamp; let is_shanghai = chain_spec.is_shanghai_active_at_timestamp(timestamp); @@ -80,7 +84,7 @@ where let is_prague = chain_spec.is_prague_active_at_timestamp(timestamp); let header = Header { - parent_hash, + parent_hash: parent.hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, beneficiary: block_env.beneficiary, state_root, @@ -95,7 +99,7 @@ where number: block_env.number, gas_limit: block_env.gas_limit, difficulty: U256::ZERO, - gas_used: receipts.last().map(|r| r.cumulative_gas_used).unwrap_or_default(), + gas_used: result.gas_used, blob_gas_used: is_cancun.then(|| { transactions.iter().map(|tx| tx.blob_gas_used().unwrap_or_default()).sum::() }), @@ -115,18 +119,4 @@ where }, } } - - fn assemble_receipt( - &self, - tx: &ProviderTx, - result: ExecutionResult>, - cumulative_gas_used: u64, - ) -> reth_provider::ProviderReceipt { - Receipt { - tx_type: tx.tx_type(), - success: result.is_success(), - cumulative_gas_used, - logs: result.into_logs().into_iter().collect(), - } - } } diff --git a/examples/custom-beacon-withdrawals/src/main.rs b/examples/custom-beacon-withdrawals/src/main.rs index 5b63f43d92..12e46ab391 100644 --- a/examples/custom-beacon-withdrawals/src/main.rs +++ b/examples/custom-beacon-withdrawals/src/main.rs @@ -3,8 +3,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use alloy_consensus::BlockHeader; -use alloy_eips::eip4895::Withdrawal; +use alloy_eips::eip4895::{Withdrawal, Withdrawals}; use alloy_sol_macro::sol; use alloy_sol_types::SolCall; use reth::{ @@ -13,6 +12,7 @@ use reth::{ cli::Cli, providers::BlockExecutionResult, revm::{ + context::result::ExecutionResult, db::State, primitives::{address, Address}, DatabaseCommit, @@ -24,11 +24,13 @@ use reth_evm::{ BlockExecutionError, BlockExecutionStrategy, BlockExecutionStrategyFactory, InternalBlockExecutionError, }, - ConfigureEvmEnv, Database, Evm, EvmEnv, NextBlockEnvAttributes, + ConfigureEvmEnv, Database, Evm, EvmEnv, EvmFor, InspectorFor, NextBlockEnvAttributes, }; use reth_evm_ethereum::EthEvmConfig; use reth_node_ethereum::{node::EthereumAddOns, BasicBlockExecutorProvider, EthereumNode}; -use reth_primitives::{Block, EthPrimitives, Receipt, Recovered, SealedBlock, TransactionSigned}; +use reth_primitives::{ + EthPrimitives, Receipt, Recovered, SealedBlock, SealedHeader, TransactionSigned, +}; use std::fmt::Display; pub const SYSTEM_ADDRESS: Address = address!("fffffffffffffffffffffffffffffffffffffffe"); @@ -111,19 +113,42 @@ impl ConfigureEvm for CustomEvmConfig { } } +pub struct CustomExecutionCtx<'a> { + withdrawals: Option<&'a Withdrawals>, +} + impl BlockExecutionStrategyFactory for CustomEvmConfig { type Primitives = EthPrimitives; + type ExecutionCtx<'a> = CustomExecutionCtx<'a>; + type Strategy<'a, DB: Database + 'a, I: InspectorFor<&'a mut State, Self> + 'a> = + CustomExecutorStrategy<'a, EvmFor, I>>; - fn create_strategy<'a, DB>( + fn context_for_block<'a>(&self, block: &'a SealedBlock) -> Self::ExecutionCtx<'a> { + CustomExecutionCtx { withdrawals: block.body().withdrawals.as_ref() } + } + + fn context_for_next_block<'a>( + &self, + _parent: &SealedHeader, + attributes: NextBlockEnvAttributes<'a>, + ) -> Self::ExecutionCtx<'a> { + CustomExecutionCtx { withdrawals: attributes.withdrawals } + } + + fn create_strategy<'a, DB, I>( &'a self, - db: &'a mut State, - block: &'a SealedBlock, - ) -> impl BlockExecutionStrategy + 'a + evm: EvmFor, I>, + ctx: Self::ExecutionCtx<'a>, + ) -> Self::Strategy<'a, DB, I> where DB: Database, + I: InspectorFor<&'a mut State, Self> + 'a, { - let evm = self.evm_for_block(db, block.header()); - CustomExecutorStrategy { evm, chain_spec: self.inner.chain_spec(), block } + CustomExecutorStrategy { + evm, + chain_spec: self.inner.chain_spec(), + withdrawals: ctx.withdrawals, + } } } @@ -132,8 +157,8 @@ pub struct CustomExecutorStrategy<'a, Evm> { chain_spec: &'a ChainSpec, /// EVM used for execution. evm: Evm, - /// Block being executed. - block: &'a SealedBlock, + /// Block withdrawals. + withdrawals: Option<&'a Withdrawals>, } impl<'db, DB, E> BlockExecutionStrategy for CustomExecutorStrategy<'_, E> @@ -143,19 +168,21 @@ where { type Primitives = EthPrimitives; type Error = BlockExecutionError; + type Evm = E; fn apply_pre_execution_changes(&mut self) -> Result<(), Self::Error> { // Set state clear flag if the block is after the Spurious Dragon hardfork. let state_clear_flag = - self.chain_spec.is_spurious_dragon_active_at_block(self.block.number()); + self.chain_spec.is_spurious_dragon_active_at_block(self.evm.block().number); self.evm.db_mut().set_state_clear_flag(state_clear_flag); Ok(()) } - fn execute_transaction( + fn execute_transaction_with_result_closure( &mut self, _tx: Recovered<&TransactionSigned>, + _f: impl FnOnce(&ExecutionResult<::HaltReason>), ) -> Result { Ok(0) } @@ -163,7 +190,7 @@ where fn apply_post_execution_changes( mut self, ) -> Result, Self::Error> { - if let Some(withdrawals) = self.block.body().withdrawals.as_ref() { + if let Some(withdrawals) = self.withdrawals { apply_withdrawals_contract_call(withdrawals, &mut self.evm)?; } @@ -171,6 +198,10 @@ where } fn with_state_hook(&mut self, _hook: Option>) {} + + fn evm_mut(&mut self) -> &mut Self::Evm { + &mut self.evm + } } sol!(