From 72210736ad18b4db26a27508a6d1d9c7e8553800 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 20 Feb 2025 19:06:19 +0400 Subject: [PATCH] feat: re-use `BlockExecutionStrategy` in payload building (#14609) --- Cargo.lock | 4 - crates/consensus/common/src/calc.rs | 4 +- crates/ethereum/evm/Cargo.toml | 3 - crates/ethereum/evm/src/execute.rs | 103 +++++-- crates/ethereum/payload/src/lib.rs | 213 +++++--------- crates/evm/execution-errors/src/lib.rs | 2 +- crates/evm/src/aliases.rs | 6 + crates/evm/src/execute.rs | 8 +- crates/evm/src/lib.rs | 33 +-- crates/evm/src/state_change.rs | 40 ++- crates/optimism/evm/src/execute.rs | 115 ++++++-- crates/optimism/payload/src/builder.rs | 263 +++++------------- crates/payload/basic/Cargo.toml | 5 - crates/payload/basic/src/lib.rs | 41 +-- .../custom-beacon-withdrawals/src/main.rs | 4 +- 15 files changed, 352 insertions(+), 492 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1848988d..d994a356f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6720,8 +6720,6 @@ dependencies = [ "futures-core", "futures-util", "metrics", - "reth-chainspec", - "reth-evm", "reth-metrics", "reth-payload-builder", "reth-payload-builder-primitives", @@ -6731,7 +6729,6 @@ dependencies = [ "reth-provider", "reth-revm", "reth-tasks", - "revm", "tokio", "tracing", ] @@ -7776,7 +7773,6 @@ dependencies = [ "alloy-genesis", "alloy-primitives", "alloy-sol-types", - "derive_more 2.0.1", "reth-chainspec", "reth-ethereum-forks", "reth-evm", diff --git a/crates/consensus/common/src/calc.rs b/crates/consensus/common/src/calc.rs index 2f3ad6560..7680a568a 100644 --- a/crates/consensus/common/src/calc.rs +++ b/crates/consensus/common/src/calc.rs @@ -21,8 +21,8 @@ use reth_chainspec::EthereumHardforks; /// - Definition: [Yellow Paper][yp] (page 15, 11.3) /// /// [yp]: https://ethereum.github.io/yellowpaper/paper.pdf -pub fn base_block_reward( - chain_spec: &ChainSpec, +pub fn base_block_reward( + chain_spec: impl EthereumHardforks, block_number: BlockNumber, ) -> Option { if chain_spec.is_paris_active_at_block(block_number).is_some_and(|active| active) { diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index 2926f18d3..0838a59f9 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -29,8 +29,6 @@ alloy-evm.workspace = true alloy-sol-types.workspace = true alloy-consensus.workspace = true -derive_more.workspace = true - [dev-dependencies] reth-testing-utils.workspace = true reth-evm = { workspace = true, features = ["test-utils"] } @@ -58,5 +56,4 @@ std = [ "alloy-evm/std", "reth-execution-types/std", "reth-evm/std", - "derive_more/std", ] diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 761459da7..ccac63899 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -5,8 +5,9 @@ use crate::{ EthEvmConfig, }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; -use alloy_consensus::{BlockHeader, Transaction}; -use alloy_eips::{eip6110, eip7685::Requests}; +use alloy_consensus::{Header, Transaction}; +use alloy_eips::{eip4895::Withdrawals, eip6110, eip7685::Requests}; +use alloy_primitives::{Address, B256}; use reth_chainspec::{ChainSpec, EthereumHardfork, EthereumHardforks, MAINNET}; use reth_evm::{ execute::{ @@ -75,43 +76,83 @@ where DB: Database, { let evm = self.evm_config.evm_for_block(db, block.header()); - EthExecutionStrategy::new(evm, block.sealed_block(), self) + EthExecutionStrategy::new(evm, block.sealed_block(), &self.chain_spec, &self.evm_config) + } +} + +/// 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, + 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(), + } } } /// Block execution strategy for Ethereum. -#[derive(Debug, derive_more::Deref)] +#[derive(Debug)] pub struct EthExecutionStrategy<'a, Evm, EvmConfig> { - /// Reference to the parent factory providing access to [`ChainSpec`]. - #[deref] - factory: &'a EthExecutionStrategyFactory, + /// Reference to the [`ChainSpec`]. + chain_spec: &'a ChainSpec, + /// How to configure the EVM. + evm_config: EvmConfig, - /// Block being executed. - block: &'a SealedBlock, + /// Input for block execution. + input: EthBlockExecutionInput<'a>, /// The EVM used by strategy. evm: Evm, + /// Utility to call system smart contracts. + system_caller: SystemCaller<&'a ChainSpec>, + /// Receipts of executed transactions. receipts: Vec, /// Total gas used by transactions in this block. gas_used: u64, - /// Utility to call system smart contracts. - system_caller: SystemCaller<&'a ChainSpec>, } impl<'a, Evm, EvmConfig> EthExecutionStrategy<'a, Evm, EvmConfig> { /// Creates a new [`EthExecutionStrategy`] pub fn new( evm: Evm, - block: &'a SealedBlock, - factory: &'a EthExecutionStrategyFactory, + input: impl Into>, + chain_spec: &'a ChainSpec, + evm_config: EvmConfig, ) -> Self { Self { evm, - factory, - block, + chain_spec, + evm_config, + input: input.into(), receipts: Vec::new(), gas_used: 0, - system_caller: SystemCaller::new(&factory.chain_spec), + system_caller: SystemCaller::new(chain_spec), } } } @@ -128,9 +169,12 @@ where 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.input.number); self.evm.db_mut().set_state_clear_flag(state_clear_flag); - self.system_caller.apply_pre_execution_changes(self.block.header(), &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)?; Ok(()) } @@ -138,10 +182,10 @@ where fn execute_transaction( &mut self, tx: Recovered<&TransactionSigned>, - ) -> Result<(), Self::Error> { + ) -> 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.block.gas_limit() - self.gas_used; + let block_available_gas = self.input.gas_limit - self.gas_used; if tx.gas_limit() > block_available_gas { return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { transaction_gas_limit: tx.gas_limit(), @@ -161,8 +205,10 @@ where let ResultAndState { result, state } = result_and_state; self.evm.db_mut().commit(state); + let gas_used = result.gas_used(); + // append gas used - self.gas_used += result.gas_used(); + self.gas_used += gas_used; // Push transaction changeset and calculate header bloom filter for receipt. self.receipts.push(Receipt { @@ -174,16 +220,16 @@ where logs: result.into_logs(), }); - Ok(()) + Ok(gas_used) } fn apply_post_execution_changes( mut self, ) -> Result, Self::Error> { - let requests = if self.chain_spec.is_prague_active_at_timestamp(self.block.timestamp) { + let requests = if self.chain_spec.is_prague_active_at_timestamp(self.input.timestamp) { // Collect all EIP-6110 deposits let deposit_requests = - crate::eip6110::parse_deposits_from_receipts(&self.chain_spec, &self.receipts)?; + crate::eip6110::parse_deposits_from_receipts(self.chain_spec, &self.receipts)?; let mut requests = Requests::default(); @@ -197,10 +243,15 @@ where Requests::default() }; - let mut balance_increments = post_block_balance_increments(&self.chain_spec, self.block); + let mut balance_increments = post_block_balance_increments( + self.chain_spec, + self.evm.block(), + self.input.ommers, + self.input.withdrawals, + ); // Irregular state change at Ethereum DAO hardfork - if self.chain_spec.fork(EthereumHardfork::Dao).transitions_at_block(self.block.number()) { + if self.chain_spec.fork(EthereumHardfork::Dao).transitions_at_block(self.input.number) { // drain balances from hardcoded addresses. let drained_balance: u128 = self .evm diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index 60b022ba6..b93fd8d47 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -10,21 +10,20 @@ #![allow(clippy::useless_let_if_seq)] use alloy_consensus::{BlockHeader, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH}; -use alloy_eips::{eip4844::DATA_GAS_PER_BLOB, eip6110, eip7685::Requests, merge::BEACON_NONCE}; +use alloy_eips::{eip4844::DATA_GAS_PER_BLOB, merge::BEACON_NONCE}; use alloy_primitives::U256; use reth_basic_payload_builder::{ - commit_withdrawals, is_better_payload, BuildArguments, BuildOutcome, PayloadBuilder, - PayloadConfig, + is_better_payload, BuildArguments, BuildOutcome, PayloadBuilder, PayloadConfig, }; use reth_chainspec::{ChainSpec, ChainSpecProvider, EthChainSpec, EthereumHardforks}; -use reth_errors::RethError; -use reth_ethereum_primitives::{Block, BlockBody, Receipt, TransactionSigned}; -use reth_evm::{ - system_calls::SystemCaller, ConfigureEvm, Evm, EvmEnv, EvmError, InvalidTxError, - NextBlockEnvAttributes, +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_evm_ethereum::{eip6110::parse_deposits_from_receipts, EthEvmConfig}; -use reth_execution_types::ExecutionOutcome; +use reth_execution_types::{BlockExecutionResult, ExecutionOutcome}; use reth_payload_builder::{EthBuiltPayload, EthPayloadBuilderAttributes}; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; @@ -41,10 +40,7 @@ use reth_transaction_pool::{ error::InvalidPoolTransactionError, BestTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool, ValidPoolTransaction, }; -use revm::{ - context_interface::{result::ResultAndState, Block as _}, - DatabaseCommit, -}; +use revm::context_interface::Block as _; use std::sync::Arc; use tracing::{debug, trace, warn}; @@ -82,27 +78,6 @@ impl EthereumPayloadBuilder { } } -impl EthereumPayloadBuilder -where - EvmConfig: ConfigureEvm
, -{ - /// Returns the configured [`EvmEnv`] for the targeted payload - /// (that has the `parent` as its parent). - fn evm_env( - &self, - config: &PayloadConfig, - parent: &Header, - ) -> Result, EvmConfig::Error> { - let next_attributes = NextBlockEnvAttributes { - timestamp: config.attributes.timestamp(), - suggested_fee_recipient: config.attributes.suggested_fee_recipient(), - prev_randao: config.attributes.prev_randao(), - gas_limit: self.builder_config.gas_limit(parent.gas_limit), - }; - self.evm_config.next_evm_env(parent, next_attributes) - } -} - // Default implementation of [PayloadBuilder] for unit type impl PayloadBuilder for EthereumPayloadBuilder where @@ -117,17 +92,12 @@ where &self, args: BuildArguments, ) -> Result, PayloadBuilderError> { - let evm_env = self - .evm_env(&args.config, &args.config.parent_header) - .map_err(PayloadBuilderError::other)?; - default_ethereum_payload( self.evm_config.clone(), self.client.clone(), self.pool.clone(), self.builder_config.clone(), args, - evm_env, |attributes| self.pool.best_transactions_with_attributes(attributes), ) } @@ -138,17 +108,12 @@ where ) -> Result { let args = BuildArguments::new(Default::default(), config, Default::default(), None); - let evm_env = self - .evm_env(&args.config, &args.config.parent_header) - .map_err(PayloadBuilderError::other)?; - default_ethereum_payload( self.evm_config.clone(), self.client.clone(), self.pool.clone(), self.builder_config.clone(), args, - evm_env, |attributes| self.pool.best_transactions_with_attributes(attributes), )? .into_payload() @@ -168,7 +133,6 @@ pub fn default_ethereum_payload( pool: Pool, builder_config: EthereumBuilderConfig, args: BuildArguments, - evm_env: EvmEnv, best_txs: F, ) -> Result, PayloadBuilderError> where @@ -178,13 +142,24 @@ where F: FnOnce(BestTransactionsAttributes) -> BestTransactionsIter, { let BuildArguments { mut cached_reads, config, cancel, best_payload } = args; + let PayloadConfig { parent_header, attributes } = config; - let chain_spec = client.chain_spec(); - let state_provider = client.state_by_block_hash(config.parent_header.hash())?; + let state_provider = client.state_by_block_hash(parent_header.hash())?; let state = StateProviderDatabase::new(state_provider); let mut db = State::builder().with_database(cached_reads.as_db_mut(state)).with_bundle_update().build(); - let PayloadConfig { parent_header, attributes } = config; + + let next_attributes = NextBlockEnvAttributes { + timestamp: attributes.timestamp(), + suggested_fee_recipient: attributes.suggested_fee_recipient(), + prev_randao: attributes.prev_randao(), + gas_limit: builder_config.gas_limit(parent_header.gas_limit), + }; + let evm_env = evm_config + .next_evm_env(&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; @@ -202,32 +177,27 @@ where let block_number = evm_env.block_env.number; let beneficiary = evm_env.block_env.beneficiary; - let mut evm = evm_config.evm_with_env(&mut db, evm_env); - let mut system_caller = SystemCaller::new(chain_spec.clone()); + 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, + &evm_config, + ); - // apply eip-4788 pre block contract call - system_caller - .apply_beacon_root_contract_call(attributes.parent_beacon_block_root, &mut evm) - .map_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_header.hash(), - %err, - "failed to apply beacon root contract call for payload" - ); - PayloadBuilderError::Internal(err.into()) - })?; - - // apply eip-2935 blockhashes update - system_caller.apply_blockhashes_contract_call( - parent_header.hash(), - &mut evm, - ) - .map_err(|err| { - warn!(target: "payload_builder", parent_hash=%parent_header.hash(), %err, "failed to update parent header blockhashes for payload"); + strategy.apply_pre_execution_changes().map_err(|err| { + warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); PayloadBuilderError::Internal(err.into()) })?; - let mut receipts = Vec::new(); let mut block_blob_count = 0; let blob_params = chain_spec.blob_params_at_timestamp(attributes.timestamp); let max_blob_count = @@ -278,37 +248,31 @@ where } } - // Configure the environment for the tx. - let tx_env = evm_config.tx_env(&tx); - - let ResultAndState { result, state } = match evm.transact(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 - trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and 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 + trace!(target: "payload_builder", %error, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %error, ?tx, "skipping invalid transaction and 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(PayloadBuilderError::evm(err)) + continue } + // this is an error that we should treat as fatal for this attempt + Err(err) => return Err(PayloadBuilderError::evm(err)), }; - // commit changes - evm.db_mut().commit(state); - // add to the total blob gas used if the transaction successfully executed if let Some(blob_tx) = tx.as_eip4844() { block_blob_count += blob_tx.blob_versioned_hashes.len() as u64; @@ -319,23 +283,11 @@ where } } - 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; - - // Push transaction changeset and calculate header bloom filter for receipt. - receipts.push(Receipt { - tx_type: tx.tx_type(), - success: result.is_success(), - cumulative_gas_used, - logs: result.into_logs().into_iter().collect(), - }); - // update add to total fees let miner_fee = tx.effective_tip_per_gas(base_fee).expect("fee is always valid; execution succeeded"); total_fees += U256::from(miner_fee) * U256::from(gas_used); + cumulative_gas_used += gas_used; // append transaction to the block body executed_txs.push(tx.into_tx()); @@ -344,39 +296,20 @@ where // check if we have a better block if !is_better_payload(best_payload.as_ref(), total_fees) { // Release db - drop(evm); - + drop(strategy); // can skip building the block return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads }) } - // calculate the requests and the requests root - let requests = if chain_spec.is_prague_active_at_timestamp(attributes.timestamp) { - let deposit_requests = parse_deposits_from_receipts(&chain_spec, receipts.iter()) - .map_err(|err| PayloadBuilderError::Internal(RethError::Execution(err.into())))?; + let BlockExecutionResult { receipts, requests, gas_used } = strategy + .apply_post_execution_changes() + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; - let mut requests = Requests::default(); - - if !deposit_requests.is_empty() { - requests.push_request_with_type(eip6110::DEPOSIT_REQUEST_TYPE, deposit_requests); - } - - requests.extend( - system_caller - .apply_post_execution_changes(&mut evm) - .map_err(|err| PayloadBuilderError::Internal(err.into()))?, - ); - - Some(requests) - } else { - None - }; - - // Release db - drop(evm); - - let withdrawals_root = - commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, &attributes.withdrawals)?; + let requests = + chain_spec.is_prague_active_at_timestamp(attributes.timestamp).then_some(requests); + let withdrawals_root = chain_spec + .is_shanghai_active_at_timestamp(attributes.timestamp) + .then_some(proofs::calculate_withdrawals_root(&attributes.withdrawals)); // merge all transitions into bundle state, this would apply the withdrawal balance changes // and 4788 contract call @@ -449,7 +382,7 @@ where number: parent_header.number + 1, gas_limit: block_gas_limit, difficulty: U256::ZERO, - gas_used: cumulative_gas_used, + gas_used, extra_data: builder_config.extra_data, parent_beacon_block_root: attributes.parent_beacon_block_root, blob_gas_used, diff --git a/crates/evm/execution-errors/src/lib.rs b/crates/evm/execution-errors/src/lib.rs index f28891acc..cb26d3bbb 100644 --- a/crates/evm/execution-errors/src/lib.rs +++ b/crates/evm/execution-errors/src/lib.rs @@ -143,7 +143,7 @@ impl BlockExecutionError { matches!(self, Self::Validation(BlockValidationError::StateRoot(_))) } - /// Handles an EVM error occured when executing a transaction. + /// Handles an EVM error occurred when executing a transaction. /// /// If an error matches [`EvmError::InvalidTransaction`], it will be wrapped into /// [`BlockValidationError::InvalidTx`], otherwise into [`InternalBlockExecutionError::EVM`]. diff --git a/crates/evm/src/aliases.rs b/crates/evm/src/aliases.rs index aae9faf4b..6cb225cb4 100644 --- a/crates/evm/src/aliases.rs +++ b/crates/evm/src/aliases.rs @@ -2,6 +2,7 @@ use crate::{ConfigureEvm, ConfigureEvmEnv}; use alloy_evm::{EvmEnv, EvmFactory}; use reth_primitives_traits::NodePrimitives; +use revm::inspector::NoOpInspector; /// This is a type alias to make type bounds simpler when we have a [`NodePrimitives`] and need a /// [`ConfigureEvmEnv`] whose associated types match the [`NodePrimitives`] associated types. @@ -43,3 +44,8 @@ pub type HaltReasonFor = <::EvmFactory as EvmFactory< /// Helper to access [`ConfigureEvmEnv::Spec`] for a given [`ConfigureEvmEnv`]. pub type SpecFor = ::Spec; + +/// Helper to access [`EvmFactory::Evm`] for a given [`ConfigureEvm`]. +pub type EvmFor = <::EvmFactory as EvmFactory< + EvmEnv<::Spec>, +>>::Evm; diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index f0f59a9be..1709a9dcf 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -174,10 +174,12 @@ pub trait BlockExecutionStrategy { fn apply_pre_execution_changes(&mut self) -> Result<(), Self::Error>; /// Executes a single transaction and applies execution result to internal state. + /// + /// Returns the gas used by the transaction. fn execute_transaction( &mut self, tx: Recovered<&::SignedTx>, - ) -> Result<(), Self::Error>; + ) -> Result; /// Applies any necessary changes after executing the block's transactions. fn apply_post_execution_changes( @@ -449,8 +451,8 @@ mod tests { fn execute_transaction( &mut self, _tx: Recovered<&TransactionSigned>, - ) -> Result<(), Self::Error> { - Ok(()) + ) -> Result { + Ok(0) } fn apply_post_execution_changes( diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 8f3a4792b..1f3331c6d 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -24,10 +24,7 @@ pub use alloy_evm::evm::EvmFactory; use alloy_primitives::{Address, B256}; use core::fmt::Debug; use reth_primitives_traits::{BlockHeader, SignedTransaction}; -use revm::{ - context::TxEnv, - inspector::{Inspector, NoOpInspector}, -}; +use revm::{context::TxEnv, inspector::Inspector}; pub mod batch; pub mod either; @@ -76,11 +73,7 @@ pub trait ConfigureEvm: ConfigureEvmEnv { /// including the spec id and transaction environment. /// /// This will preserve any handler modifications - fn evm_with_env( - &self, - db: DB, - evm_env: EvmEnv, - ) -> >>::Evm { + fn evm_with_env(&self, db: DB, evm_env: EvmEnv) -> EvmFor { self.evm_factory().create_evm(db, evm_env) } @@ -91,11 +84,7 @@ pub trait ConfigureEvm: ConfigureEvmEnv { /// # Caution /// /// This does not initialize the tx environment. - fn evm_for_block( - &self, - db: DB, - header: &Self::Header, - ) -> >>::Evm { + fn evm_for_block(&self, db: DB, header: &Self::Header) -> EvmFor { let evm_env = self.evm_env(header); self.evm_with_env(db, evm_env) } @@ -111,7 +100,7 @@ pub trait ConfigureEvm: ConfigureEvmEnv { db: DB, evm_env: EvmEnv, inspector: I, - ) -> >>::Evm + ) -> EvmFor where DB: Database, I: InspectorFor, @@ -131,19 +120,11 @@ where (*self).evm_factory() } - fn evm_for_block( - &self, - db: DB, - header: &Self::Header, - ) -> >>::Evm { + fn evm_for_block(&self, db: DB, header: &Self::Header) -> EvmFor { (*self).evm_for_block(db, header) } - fn evm_with_env( - &self, - db: DB, - evm_env: EvmEnv, - ) -> >>::Evm { + fn evm_with_env(&self, db: DB, evm_env: EvmEnv) -> EvmFor { (*self).evm_with_env(db, evm_env) } @@ -152,7 +133,7 @@ where db: DB, evm_env: EvmEnv, inspector: I, - ) -> >>::Evm + ) -> EvmFor where DB: Database, I: InspectorFor, diff --git a/crates/evm/src/state_change.rs b/crates/evm/src/state_change.rs index a6f38f626..8fb4f06b7 100644 --- a/crates/evm/src/state_change.rs +++ b/crates/evm/src/state_change.rs @@ -1,50 +1,46 @@ //! State changes that are not related to transactions. use alloy_consensus::BlockHeader; -use alloy_eips::eip4895::Withdrawal; +use alloy_eips::eip4895::{Withdrawal, Withdrawals}; use alloy_primitives::{map::HashMap, Address}; use reth_chainspec::EthereumHardforks; use reth_consensus_common::calc; -use reth_primitives::SealedBlock; -use reth_primitives_traits::BlockBody; +use revm::context::BlockEnv; /// Collect all balance changes at the end of the block. /// /// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular /// state changes (DAO fork). #[inline] -pub fn post_block_balance_increments( - chain_spec: &ChainSpec, - block: &SealedBlock, +pub fn post_block_balance_increments( + chain_spec: impl EthereumHardforks, + block_env: &BlockEnv, + ommers: &[H], + withdrawals: Option<&Withdrawals>, ) -> HashMap where - ChainSpec: EthereumHardforks, - Block: reth_primitives_traits::Block, + H: BlockHeader, { let mut balance_increments = HashMap::default(); // Add block rewards if they are enabled. - if let Some(base_block_reward) = calc::base_block_reward(chain_spec, block.header().number()) { + if let Some(base_block_reward) = calc::base_block_reward(&chain_spec, block_env.number) { // Ommer rewards - if let Some(ommers) = block.body().ommers() { - for ommer in ommers { - *balance_increments.entry(ommer.beneficiary()).or_default() += - calc::ommer_reward(base_block_reward, block.header().number(), ommer.number()); - } + for ommer in ommers { + *balance_increments.entry(ommer.beneficiary()).or_default() += + calc::ommer_reward(base_block_reward, block_env.number, ommer.number()); } // Full block reward - *balance_increments.entry(block.header().beneficiary()).or_default() += calc::block_reward( - base_block_reward, - block.body().ommers().map(|s| s.len()).unwrap_or(0), - ); + *balance_increments.entry(block_env.beneficiary).or_default() += + calc::block_reward(base_block_reward, ommers.len()); } // process withdrawals insert_post_block_withdrawals_balance_increments( chain_spec, - block.header().timestamp(), - block.body().withdrawals().as_ref().map(|w| w.as_slice()), + block_env.timestamp, + withdrawals.map(|w| w.as_slice()), &mut balance_increments, ); @@ -77,8 +73,8 @@ pub fn post_block_withdrawals_balance_increments( /// /// Zero-valued withdrawals are filtered out. #[inline] -pub fn insert_post_block_withdrawals_balance_increments( - chain_spec: &ChainSpec, +pub fn insert_post_block_withdrawals_balance_increments( + chain_spec: impl EthereumHardforks, block_timestamp: u64, withdrawals: Option<&[Withdrawal]>, balance_increments: &mut HashMap, diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 95ad9993f..fb6aa7945 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -6,7 +6,7 @@ use crate::{ }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_consensus::{ - transaction::Recovered, BlockHeader, Eip658Value, Receipt, Transaction as _, TxReceipt, + transaction::Recovered, BlockHeader, Eip658Value, Header, Receipt, Transaction as _, TxReceipt, }; use op_alloy_consensus::OpDepositReceipt; use reth_evm::{ @@ -22,9 +22,12 @@ use reth_execution_types::BlockExecutionResult; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{transaction::signed::OpTransaction, DepositReceipt, OpPrimitives}; -use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock, SignedTransaction}; +use reth_primitives_traits::{ + Block, NodePrimitives, RecoveredBlock, SealedBlock, SignedTransaction, +}; use revm::{context_interface::result::ResultAndState, DatabaseCommit}; use revm_database::State; +use revm_primitives::{Address, B256}; use tracing::trace; /// Factory for [`OpExecutionStrategy`]. @@ -89,19 +92,59 @@ where DB: Database, { let evm = self.evm_config.evm_for_block(db, block.header()); - OpExecutionStrategy::new(evm, block.sealed_block(), self) + OpExecutionStrategy::new( + evm, + block.sealed_block(), + &self.chain_spec, + &self.evm_config, + 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(), + 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(), + } } } /// Block execution strategy for Optimism. -#[derive(Debug, derive_more::Deref)] +#[derive(Debug)] pub struct OpExecutionStrategy<'a, Evm, N: NodePrimitives, ChainSpec, EvmConfig: ConfigureEvm> { - /// Reference to the parent factory. - #[deref] - factory: &'a OpExecutionStrategyFactory, + /// Chainspec. + chain_spec: ChainSpec, + /// How to configure the EVM. + evm_config: EvmConfig, + /// Receipt builder. + receipt_builder: + &'a dyn OpReceiptBuilder, Receipt = N::Receipt>, - /// Block being executed. - block: &'a SealedBlock, + /// Input for block execution. + input: OpBlockExecutionInput, /// The EVM used by strategy. evm: Evm, /// Receipts of executed transactions. @@ -111,7 +154,7 @@ pub struct OpExecutionStrategy<'a, Evm, N: NodePrimitives, ChainSpec, EvmConfig: /// Whether Regolith hardfork is active. is_regolith: bool, /// Utility to call system smart contracts. - system_caller: SystemCaller<&'a ChainSpec>, + system_caller: SystemCaller, } impl<'a, Evm, N, ChainSpec, EvmConfig> OpExecutionStrategy<'a, Evm, N, ChainSpec, EvmConfig> @@ -123,17 +166,26 @@ where /// Creates a new [`OpExecutionStrategy`] pub fn new( evm: Evm, - block: &'a SealedBlock, - factory: &'a OpExecutionStrategyFactory, + input: impl Into, + chain_spec: ChainSpec, + evm_config: EvmConfig, + receipt_builder: &'a dyn OpReceiptBuilder< + N::SignedTx, + HaltReasonFor, + Receipt = N::Receipt, + >, ) -> Self { + let input = input.into(); Self { + is_regolith: chain_spec.is_regolith_active_at_timestamp(input.timestamp), evm, - factory, - block, + system_caller: SystemCaller::new(chain_spec.clone()), + chain_spec, + evm_config, + receipt_builder, receipts: Vec::new(), gas_used: 0, - is_regolith: factory.chain_spec.is_regolith_active_at_timestamp(block.timestamp()), - system_caller: SystemCaller::new(&factory.chain_spec), + input, } } } @@ -153,28 +205,26 @@ where 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.input.number); self.evm.db_mut().set_state_clear_flag(state_clear_flag); - self.system_caller.apply_beacon_root_contract_call( - self.block.parent_beacon_block_root(), - &mut self.evm, - )?; + self.system_caller + .apply_beacon_root_contract_call(self.input.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.block.timestamp(), self.evm.db_mut()) + ensure_create2_deployer(self.chain_spec.clone(), self.input.timestamp, self.evm.db_mut()) .map_err(|_| OpBlockExecutionError::ForceCreate2DeployerFail)?; Ok(()) } - fn execute_transaction(&mut self, tx: Recovered<&N::SignedTx>) -> Result<(), Self::Error> { + fn execute_transaction(&mut self, tx: Recovered<&N::SignedTx>) -> 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.block.gas_limit() - self.gas_used; + let block_available_gas = self.input.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(), @@ -215,8 +265,10 @@ where let ResultAndState { result, state } = result_and_state; self.evm.db_mut().commit(state); + let gas_used = result.gas_used(); + // append gas used - self.gas_used += result.gas_used(); + self.gas_used += gas_used; self.receipts.push( match self.receipt_builder.build_receipt(ReceiptBuilderCtx { @@ -243,22 +295,25 @@ 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.block.timestamp())) + self.chain_spec.is_canyon_active_at_timestamp(self.input.timestamp)) .then_some(1), }) } }, ); - Ok(()) + Ok(gas_used) } fn apply_post_execution_changes( mut self, ) -> Result, Self::Error> { - let balance_increments = - post_block_balance_increments(&self.chain_spec.clone(), self.block); + let balance_increments = post_block_balance_increments::
( + &self.chain_spec.clone(), + self.evm.block(), + &[], + None, + ); // increment balances self.evm .db_mut() diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index d4cdc6c86..f66e9a4b7 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -7,26 +7,24 @@ use crate::{ OpPayloadPrimitives, }; use alloy_consensus::{ - constants::EMPTY_WITHDRAWALS, Eip658Value, Header, Transaction, Typed2718, - EMPTY_OMMER_ROOT_HASH, + constants::EMPTY_WITHDRAWALS, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH, }; use alloy_eips::{eip4895::Withdrawals, merge::BEACON_NONCE}; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rlp::Encodable; use alloy_rpc_types_debug::ExecutionWitness; use alloy_rpc_types_engine::PayloadId; -use op_alloy_consensus::OpDepositReceipt; use op_alloy_rpc_types_engine::OpPayloadAttributes; use reth_basic_payload_builder::*; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_evm::{ - system_calls::SystemCaller, ConfigureEvm, ConfigureEvmFor, Database, Evm, EvmEnv, EvmError, - HaltReasonFor, InvalidTxError, NextBlockEnvAttributes, + execute::{BlockExecutionError, BlockExecutionStrategy, BlockValidationError}, + ConfigureEvm, ConfigureEvmFor, Database, EvmEnv, HaltReasonFor, NextBlockEnvAttributes, }; use reth_execution_types::ExecutionOutcome; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; -use reth_optimism_evm::{OpReceiptBuilder, ReceiptBuilderCtx}; +use reth_optimism_evm::{OpBlockExecutionInput, OpExecutionStrategy, OpReceiptBuilder}; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::transaction::signed::OpTransaction; use reth_optimism_storage::predeploys; @@ -38,8 +36,8 @@ use reth_primitives::{ }; use reth_primitives_traits::{block::Block as _, proofs, RecoveredBlock}; use reth_provider::{ - HashedPostStateProvider, ProviderError, StateProofProvider, StateProviderFactory, - StateRootProvider, StorageRootProvider, + BlockExecutionResult, HashedPostStateProvider, ProviderError, StateProofProvider, + StateProviderFactory, StateRootProvider, StorageRootProvider, }; use reth_revm::{ cancelled::CancelOnDrop, @@ -48,14 +46,8 @@ use reth_revm::{ witness::ExecutionWitnessRecord, }; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; -use revm::{ - context_interface::{ - result::{ExecutionResult, ResultAndState}, - Block, - }, - Database as _, DatabaseCommit, -}; -use std::{fmt::Display, sync::Arc}; +use revm::context_interface::Block; +use std::sync::Arc; use tracing::{debug, trace, warn}; /// Optimism's payload builder @@ -366,21 +358,34 @@ 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 evm = ctx.evm_config.evm_with_env(&mut *state, ctx.evm_env.clone()); + 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.evm_config, + ctx.receipt_builder.as_ref(), + ); - // 1. apply eip-4788 pre block contract call - ctx.apply_pre_beacon_root_contract_call(&mut evm)?; + // 1. apply pre-execution changes + strategy.apply_pre_execution_changes().map_err(|err| { + warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); + PayloadBuilderError::Internal(err.into()) + })?; - // 2. ensure create2deployer is force deployed - ctx.ensure_create2_deployer(evm.db_mut())?; + // 2. execute sequencer transactions + let mut info = ctx.execute_sequencer_transactions(&mut strategy)?; - // 3. execute sequencer transactions - let mut info = ctx.execute_sequencer_transactions(&mut evm)?; - - // 4. if mem pool transactions are requested we execute them + // 3. if mem pool transactions are requested we execute them if !ctx.attributes().no_tx_pool { let best_txs = best(ctx.best_transaction_attributes()); - if ctx.execute_best_transactions(&mut info, &mut evm, best_txs)?.is_some() { + if ctx.execute_best_transactions(&mut info, &mut strategy, best_txs)?.is_some() { return Ok(BuildOutcomeKind::Cancelled) } @@ -391,7 +396,9 @@ impl OpBuilder<'_, Txs> { } } - drop(evm); + let BlockExecutionResult { receipts, .. } = strategy + .apply_post_execution_changes() + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; // merge all transitions into bundle state, this would apply the withdrawal balance changes // and 4788 contract call @@ -407,7 +414,7 @@ impl OpBuilder<'_, Txs> { None }; - let payload = ExecutedPayload { info, withdrawals_root }; + let payload = ExecutedPayload { receipts, info, withdrawals_root }; Ok(BuildOutcomeKind::Better { payload }) } @@ -426,19 +433,16 @@ impl OpBuilder<'_, Txs> { DB: Database + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, { - let ExecutedPayload { info, withdrawals_root } = 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 ExecutedPayload { receipts, info, withdrawals_root } = + 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 execution_outcome = ExecutionOutcome::new( - state.take_bundle(), - vec![info.receipts], - block_number, - Vec::new(), - ); + let execution_outcome = + ExecutionOutcome::new(state.take_bundle(), vec![receipts], block_number, Vec::new()); let receipts_root = execution_outcome .generic_receipts_root_slow(block_number, |receipts| { calculate_receipt_root_no_memo_optimism( @@ -588,6 +592,8 @@ pub struct ExecutedPayload { pub info: ExecutionInfo, /// Withdrawal hash. pub withdrawals_root: Option, + /// The transaction receipts. + pub receipts: Vec, } /// This acts as the container for executed transactions and its byproducts (receipts, gas used) @@ -597,8 +603,6 @@ pub struct ExecutionInfo { pub executed_transactions: Vec, /// The recovered senders for the executed transactions. pub executed_senders: Vec

, - /// The transaction receipts - pub receipts: Vec, /// All gas used so far pub cumulative_gas_used: u64, /// Estimated DA size @@ -613,7 +617,6 @@ impl ExecutionInfo { Self { executed_transactions: Vec::with_capacity(capacity), executed_senders: Vec::with_capacity(capacity), - receipts: Vec::with_capacity(capacity), cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO, @@ -787,26 +790,6 @@ where pub fn is_better_payload(&self, total_fees: U256) -> bool { is_better_payload(self.best_payload.as_ref(), total_fees) } - - /// 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. - pub fn ensure_create2_deployer(&self, db: &mut State) -> Result<(), PayloadBuilderError> - where - DB: Database, - DB::Error: Display, - { - reth_optimism_evm::ensure_create2_deployer( - self.chain_spec.clone(), - self.attributes().payload_attributes.timestamp, - db, - ) - .map_err(|err| { - warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); - PayloadBuilderError::other(OpPayloadBuilderError::ForceCreate2DeployerFail) - }) - } } impl OpPayloadBuilderCtx @@ -815,73 +798,10 @@ where ChainSpec: EthChainSpec + OpHardforks, N: OpPayloadPrimitives, { - /// apply eip-4788 pre block contract call - pub fn apply_pre_beacon_root_contract_call( - &self, - evm: &mut impl Evm, - ) -> Result<(), PayloadBuilderError> { - SystemCaller::new(&self.chain_spec) - .apply_beacon_root_contract_call( - self.attributes().payload_attributes.parent_beacon_block_root, - evm, - ) - .map_err(|err| { - warn!(target: "payload_builder", - parent_header=%self.parent().hash(), - %err, - "failed to apply beacon root contract call for payload" - ); - PayloadBuilderError::Internal(err.into()) - })?; - - Ok(()) - } - - /// Constructs a receipt for the given transaction. - fn build_receipt( - &self, - info: &ExecutionInfo, - result: ExecutionResult>, - deposit_nonce: Option, - tx: &N::SignedTx, - ) -> N::Receipt { - match self.receipt_builder.build_receipt(ReceiptBuilderCtx { - tx, - result, - cumulative_gas_used: info.cumulative_gas_used, - }) { - Ok(receipt) => receipt, - Err(ctx) => { - let receipt = alloy_consensus::Receipt { - // Success flag was added in `EIP-658: Embedding transaction status code - // in receipts`. - status: Eip658Value::Eip658(ctx.result.is_success()), - cumulative_gas_used: ctx.cumulative_gas_used, - logs: ctx.result.into_logs(), - }; - - self.receipt_builder.build_deposit_receipt(OpDepositReceipt { - inner: receipt, - deposit_nonce, - // The deposit receipt version was introduced in Canyon to indicate an - // update to how receipt hashes should be computed - // when set. The state transition process ensures - // this is only set for post-Canyon deposit - // transactions. - deposit_receipt_version: self.is_canyon_active().then_some(1), - }) - } - } - } - /// Executes all sequencer transactions that are included in the payload attributes. pub fn execute_sequencer_transactions( &self, - evm: &mut impl Evm< - DB: Database + DatabaseCommit, - Tx = EvmConfig::TxEnv, - HaltReason = HaltReasonFor, - >, + strategy: &mut impl BlockExecutionStrategy, ) -> Result, PayloadBuilderError> { let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); @@ -901,51 +821,24 @@ where PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) })?; - // Cache the depositor account prior to the state transition for the deposit nonce. - // - // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces - // were not introduced in Bedrock. In addition, regular transactions don't have deposit - // nonces, so we don't need to touch the DB for those. - let depositor_nonce = (self.is_regolith_active() && sequencer_tx.is_deposit()) - .then(|| { - evm.db_mut() - .basic(sequencer_tx.signer()) - .map(|acc| acc.unwrap_or_default().nonce) - }) - .transpose() - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( - sequencer_tx.signer(), - )) - })?; - - let tx_env = self.evm_config.tx_env(&sequencer_tx); - - let ResultAndState { result, state: _ } = match evm.transact_commit(tx_env) { - Ok(res) => res, + let gas_used = match strategy.execute_transaction(sequencer_tx.as_recovered_ref()) { + Ok(gas_used) => gas_used, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + trace!(target: "payload_builder", %error, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue + } Err(err) => { - if err.is_invalid_tx_err() { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue - } // this is an error that we should treat as fatal for this attempt return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) } }; - let gas_used = result.gas_used(); - // add gas used by the transaction to cumulative gas used, before creating the receipt info.cumulative_gas_used += gas_used; - // Push transaction changeset and calculate header bloom filter for receipt. - info.receipts.push(self.build_receipt( - &info, - result, - depositor_nonce, - sequencer_tx.tx(), - )); - // append sender and transaction to the respective lists info.executed_senders.push(sequencer_tx.signer()); info.executed_transactions.push(sequencer_tx.into_tx()); @@ -960,11 +853,7 @@ where pub fn execute_best_transactions( &self, info: &mut ExecutionInfo, - evm: &mut impl Evm< - DB: DatabaseCommit, - Tx = EvmConfig::TxEnv, - HaltReason = HaltReasonFor, - >, + strategy: &mut impl BlockExecutionStrategy, mut best_txs: impl PayloadTransactions< Transaction: PoolTransaction, >, @@ -995,40 +884,34 @@ where return Ok(Some(())) } - // Configure the environment for the tx. - 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 - trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - } - - 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 + trace!(target: "payload_builder", %error, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %error, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); } + continue + } + Err(err) => { // this is an error that we should treat as fatal for this attempt return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) } }; - let gas_used = result.gas_used(); - // add gas used by the transaction to cumulative gas used, before creating the // receipt info.cumulative_gas_used += gas_used; info.cumulative_da_bytes_used += tx.length() as u64; - // Push transaction changeset and calculate header bloom filter for receipt. - info.receipts.push(self.build_receipt(info, result, None, &tx)); - // update add to total fees let miner_fee = tx .effective_tip_per_gas(base_fee) diff --git a/crates/payload/basic/Cargo.toml b/crates/payload/basic/Cargo.toml index 7c975ef7d..d872ae8dd 100644 --- a/crates/payload/basic/Cargo.toml +++ b/crates/payload/basic/Cargo.toml @@ -13,7 +13,6 @@ workspace = true [dependencies] # reth -reth-chainspec.workspace = true reth-primitives.workspace = true reth-primitives-traits.workspace = true reth-provider.workspace = true @@ -21,12 +20,8 @@ reth-payload-builder.workspace = true reth-payload-builder-primitives.workspace = true reth-payload-primitives.workspace = true reth-tasks.workspace = true -reth-evm.workspace = true reth-revm.workspace = true -# revm -revm.workspace = true - # ethereum alloy-primitives.workspace = true alloy-consensus.workspace = true diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index 8bae47f60..973d18c42 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -9,22 +9,18 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use crate::metrics::PayloadBuilderMetrics; -use alloy_consensus::constants::EMPTY_WITHDRAWALS; -use alloy_eips::{eip4895::Withdrawals, merge::SLOT_DURATION}; +use alloy_eips::merge::SLOT_DURATION; use alloy_primitives::{B256, U256}; use futures_core::ready; use futures_util::FutureExt; -use reth_chainspec::EthereumHardforks; -use reth_evm::state_change::post_block_withdrawals_balance_increments; use reth_payload_builder::{KeepPayloadJobAlive, PayloadId, PayloadJob, PayloadJobGenerator}; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes, PayloadKind}; use reth_primitives::{NodePrimitives, SealedHeader}; -use reth_primitives_traits::{proofs, HeaderTy}; +use reth_primitives_traits::HeaderTy; use reth_provider::{BlockReaderIdExt, CanonStateNotification, StateProviderFactory}; -use reth_revm::{cached::CachedReads, cancelled::CancelOnDrop, db::State}; +use reth_revm::{cached::CachedReads, cancelled::CancelOnDrop}; use reth_tasks::TaskSpawner; -use revm::Database; use std::{ fmt, future::Future, @@ -881,37 +877,6 @@ impl Default for MissingPayloadBehaviour { } } -/// Executes the withdrawals and commits them to the _runtime_ Database and `BundleState`. -/// -/// Returns the withdrawals root. -/// -/// Returns `None` values pre shanghai -pub fn commit_withdrawals( - db: &mut State, - chain_spec: &ChainSpec, - timestamp: u64, - withdrawals: &Withdrawals, -) -> Result, DB::Error> -where - DB: Database, - ChainSpec: EthereumHardforks, -{ - if !chain_spec.is_shanghai_active_at_timestamp(timestamp) { - return Ok(None) - } - - if withdrawals.is_empty() { - return Ok(Some(EMPTY_WITHDRAWALS)) - } - - let balance_increments = - post_block_withdrawals_balance_increments(chain_spec, timestamp, withdrawals); - - db.increment_balances(balance_increments)?; - - Ok(Some(proofs::calculate_withdrawals_root(withdrawals))) -} - /// Checks if the new payload is better than the current best. /// /// This compares the total fees of the blocks, higher is better. diff --git a/examples/custom-beacon-withdrawals/src/main.rs b/examples/custom-beacon-withdrawals/src/main.rs index 1c41377e2..c6dbe9e8e 100644 --- a/examples/custom-beacon-withdrawals/src/main.rs +++ b/examples/custom-beacon-withdrawals/src/main.rs @@ -136,8 +136,8 @@ where fn execute_transaction( &mut self, _tx: Recovered<&TransactionSigned>, - ) -> Result<(), Self::Error> { - Ok(()) + ) -> Result { + Ok(0) } fn apply_post_execution_changes(