From 2eab45869fb554699791b066c8beb72f5413bf16 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Tue, 4 Mar 2025 21:20:02 +0400 Subject: [PATCH] feat: `BlockAssember` + `BlockBuilder` (#14808) --- Cargo.lock | 9 +- Cargo.toml | 4 +- book/sources/Cargo.toml | 4 +- crates/engine/util/src/reorg.rs | 100 +--- crates/ethereum/evm/src/build.rs | 118 +++++ crates/ethereum/evm/src/execute.rs | 21 +- crates/ethereum/evm/src/lib.rs | 14 +- crates/ethereum/payload/Cargo.toml | 1 - crates/ethereum/payload/src/lib.rs | 162 ++----- crates/evm/Cargo.toml | 5 + crates/evm/src/aliases.rs | 15 +- crates/evm/src/execute.rs | 257 +++++++++- crates/evm/src/lib.rs | 10 +- .../consensus/src/validation/isthmus.rs | 2 +- crates/optimism/evm/src/build.rs | 125 +++++ crates/optimism/evm/src/execute.rs | 41 +- crates/optimism/evm/src/lib.rs | 5 + crates/optimism/node/src/node.rs | 24 +- crates/optimism/payload/Cargo.toml | 1 - crates/optimism/payload/src/builder.rs | 455 +++++------------- crates/optimism/rpc/Cargo.toml | 1 - crates/optimism/rpc/src/eth/pending_block.rs | 77 +-- crates/optimism/rpc/src/witness.rs | 24 +- crates/rpc/rpc-eth-api/src/helpers/call.rs | 24 +- .../rpc-eth-api/src/helpers/pending_block.rs | 62 +-- crates/rpc/rpc-eth-types/src/simulate.rs | 53 +- .../rpc/rpc/src/eth/helpers/pending_block.rs | 75 +-- .../custom-beacon-withdrawals/src/main.rs | 77 ++- examples/custom-evm/src/main.rs | 3 +- examples/stateful-precompile/src/main.rs | 3 +- 30 files changed, 857 insertions(+), 915 deletions(-) create mode 100644 crates/ethereum/evm/src/build.rs create mode 100644 crates/optimism/evm/src/build.rs diff --git a/Cargo.lock b/Cargo.lock index fb676a435b..ed9681dae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,7 +261,7 @@ dependencies = [ [[package]] name = "alloy-evm" version = "0.1.0" -source = "git+https://github.com/alloy-rs/evm?rev=04fa394#04fa3947c5694c2d15956bb18a31834b95e0e975" +source = "git+https://github.com/alloy-rs/evm?rev=68735f2#68735f245cf331ffebfc6bc867406cddf56ae0d4" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -369,7 +369,7 @@ dependencies = [ [[package]] name = "alloy-op-evm" version = "0.1.0" -source = "git+https://github.com/alloy-rs/evm?rev=04fa394#04fa3947c5694c2d15956bb18a31834b95e0e975" +source = "git+https://github.com/alloy-rs/evm?rev=68735f2#68735f245cf331ffebfc6bc867406cddf56ae0d4" dependencies = [ "alloy-evm", "alloy-primitives", @@ -7693,7 +7693,6 @@ dependencies = [ "reth-ethereum-primitives", "reth-evm", "reth-evm-ethereum", - "reth-execution-types", "reth-payload-builder", "reth-payload-builder-primitives", "reth-payload-primitives", @@ -7769,7 +7768,9 @@ dependencies = [ "reth-metrics", "reth-primitives", "reth-primitives-traits", + "reth-storage-api", "reth-storage-errors", + "reth-trie-common", "revm", "revm-database", "revm-optimism", @@ -8733,7 +8734,6 @@ dependencies = [ "reth-chainspec", "reth-evm", "reth-execution-types", - "reth-optimism-consensus", "reth-optimism-evm", "reth-optimism-forks", "reth-optimism-primitives", @@ -8812,7 +8812,6 @@ dependencies = [ "reth-node-api", "reth-node-builder", "reth-optimism-chainspec", - "reth-optimism-consensus", "reth-optimism-evm", "reth-optimism-forks", "reth-optimism-payload-builder", diff --git a/Cargo.toml b/Cargo.toml index be7cb3217b..38318df345 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -675,8 +675,8 @@ visibility = "0.1.1" walkdir = "2.3.3" [patch.crates-io] -alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "04fa394" } -alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "04fa394" } +alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "68735f2" } +alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "68735f2" } revm = { git = "https://github.com/bluealloy/revm", rev = "a8a9893b" } revm-bytecode = { git = "https://github.com/bluealloy/revm", rev = "a8a9893b" } diff --git a/book/sources/Cargo.toml b/book/sources/Cargo.toml index 860ec688c5..fbca069662 100644 --- a/book/sources/Cargo.toml +++ b/book/sources/Cargo.toml @@ -13,8 +13,8 @@ reth-tracing = { path = "../../crates/tracing" } reth-node-api = { path = "../../crates/node/api" } [patch.crates-io] -alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "04fa394" } -alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "04fa394" } +alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "68735f2" } +alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "68735f2" } revm = { git = "https://github.com/bluealloy/revm", rev = "a8b9b1e" } revm-bytecode = { git = "https://github.com/bluealloy/revm", rev = "a8b9b1e" } diff --git a/crates/engine/util/src/reorg.rs b/crates/engine/util/src/reorg.rs index e2c385aa72..fa6692663f 100644 --- a/crates/engine/util/src/reorg.rs +++ b/crates/engine/util/src/reorg.rs @@ -1,6 +1,6 @@ //! Stream wrapper that simulates reorgs. -use alloy_consensus::{BlockHeader, Header, TxReceipt}; +use alloy_consensus::BlockHeader; use alloy_rpc_types_engine::{ForkchoiceState, PayloadStatus}; use futures::{stream::FuturesUnordered, Stream, StreamExt, TryFutureExt}; use itertools::Either; @@ -10,18 +10,12 @@ use reth_engine_primitives::{ OnForkChoiceUpdated, PayloadValidator, }; use reth_errors::{BlockExecutionError, BlockValidationError, RethError, RethResult}; -use reth_evm::execute::{BlockExecutionStrategy, BlockExecutionStrategyFactory}; +use reth_evm::execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutionStrategyFactory}; use reth_payload_primitives::{BuiltPayload, EngineApiMessageVersion}; -use reth_primitives::{BlockBody, NodePrimitives, SealedBlock}; -use reth_primitives_traits::{block::Block as _, proofs, BlockBody as _, SignedTransaction}; -use reth_provider::{ - BlockExecutionResult, BlockReader, ChainSpecProvider, ExecutionOutcome, ProviderError, - StateProviderFactory, -}; -use reth_revm::{ - database::StateProviderDatabase, - db::{states::bundle_state::BundleRetention, State}, -}; +use reth_primitives::{NodePrimitives, SealedBlock}; +use reth_primitives_traits::{block::Block as _, BlockBody as _, SignedTransaction}; +use reth_provider::{BlockReader, ChainSpecProvider, ProviderError, StateProviderFactory}; +use reth_revm::{database::StateProviderDatabase, db::State}; use std::{ collections::VecDeque, future::Future, @@ -267,8 +261,6 @@ where Evm: BlockExecutionStrategyFactory, Validator: PayloadValidator, { - let chain_spec = provider.chain_spec(); - // Ensure next payload is valid. let next_block = payload_validator.ensure_well_formed_payload(next_payload).map_err(RethError::msg)?; @@ -291,7 +283,7 @@ where } }; let reorg_target_parent = provider - .block_by_hash(reorg_target.header().parent_hash)? + .sealed_header_by_hash(reorg_target.header().parent_hash)? .ok_or_else(|| ProviderError::HeaderNotFound(reorg_target.header().parent_hash.into()))?; debug!(target: "engine::stream::reorg", number = reorg_target.header().number, hash = %previous_hash, "Selected reorg target"); @@ -303,13 +295,13 @@ where .with_bundle_update() .build(); - let mut strategy = evm_config.strategy_for_block(&mut state, &reorg_target); + let ctx = evm_config.context_for_block(&reorg_target); + let evm = evm_config.evm_for_block(&mut state, &reorg_target); + let mut builder = evm_config.create_block_builder(evm, &reorg_target_parent, ctx); - strategy.apply_pre_execution_changes()?; + builder.apply_pre_execution_changes()?; let mut cumulative_gas_used = 0; - let mut sum_blob_gas_used = 0; - let mut transactions = Vec::new(); for tx in candidate_transactions { // ensure we still have capacity for this transaction if cumulative_gas_used + tx.gas_limit() > reorg_target.gas_limit { @@ -318,7 +310,7 @@ where let tx_recovered = tx.try_clone_into_recovered().map_err(|_| ProviderError::SenderRecoveryError)?; - let gas_used = match strategy.execute_transaction(tx_recovered.as_recovered_ref()) { + let gas_used = match builder.execute_transaction(tx_recovered) { Ok(gas_used) => gas_used, Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { hash, @@ -331,74 +323,10 @@ where Err(error) => return Err(RethError::Execution(error)), }; - if tx.is_eip4844() { - sum_blob_gas_used += tx.blob_gas_used().unwrap_or_default(); - } - cumulative_gas_used += gas_used; - - // append transaction to the list of executed transactions - transactions.push(tx); } - let BlockExecutionResult { receipts, .. } = strategy.apply_post_execution_changes()?; + let BlockBuilderOutcome { block, .. } = builder.finish(&state_provider)?; - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call - state.merge_transitions(BundleRetention::PlainState); - - let outcome = ExecutionOutcome::new( - state.take_bundle(), - vec![receipts], - reorg_target.number, - Default::default(), - ); - let hashed_state = state_provider.hashed_post_state(outcome.state()); - - let (blob_gas_used, excess_blob_gas) = - if let Some(blob_params) = chain_spec.blob_params_at_timestamp(reorg_target.timestamp) { - ( - Some(sum_blob_gas_used), - reorg_target_parent.header().next_block_excess_blob_gas(blob_params), - ) - } else { - (None, None) - }; - - let (header, body) = reorg_target.split_header_body(); - - let reorg_block = N::Block::new( - Header { - // Set same fields as the reorg target - parent_hash: header.parent_hash, - ommers_hash: header.ommers_hash, - beneficiary: header.beneficiary, - difficulty: header.difficulty, - number: header.number, - gas_limit: header.gas_limit, - timestamp: header.timestamp, - extra_data: header.extra_data, - mix_hash: header.mix_hash, - nonce: header.nonce, - base_fee_per_gas: header.base_fee_per_gas, - parent_beacon_block_root: header.parent_beacon_block_root, - withdrawals_root: header.withdrawals_root, - - // Compute or add new fields - transactions_root: proofs::calculate_transaction_root(&transactions), - receipts_root: proofs::calculate_receipt_root( - &outcome.receipts()[0].iter().map(|r| r.with_bloom_ref()).collect::>(), - ), - logs_bloom: outcome.block_logs_bloom(header.number).unwrap(), - gas_used: cumulative_gas_used, - blob_gas_used, - excess_blob_gas, - state_root: state_provider.state_root(hashed_state)?, - requests_hash: None, // TODO(prague) - }, - BlockBody { transactions, ommers: body.ommers, withdrawals: body.withdrawals }, - ) - .seal_slow(); - - Ok(reorg_block) + Ok(block.into_sealed_block()) } diff --git a/crates/ethereum/evm/src/build.rs b/crates/ethereum/evm/src/build.rs new file mode 100644 index 0000000000..d34917077c --- /dev/null +++ b/crates/ethereum/evm/src/build.rs @@ -0,0 +1,118 @@ +use crate::execute::EthBlockExecutionCtx; +use alloc::sync::Arc; +use alloy_consensus::{ + proofs, Block, BlockBody, BlockHeader, Header, Transaction, TxReceipt, EMPTY_OMMER_ROOT_HASH, +}; +use alloy_eips::merge::BEACON_NONCE; +use alloy_primitives::Bytes; +use reth_chainspec::{EthChainSpec, EthereumHardforks}; +use reth_evm::execute::{ + BlockAssembler, BlockAssemblerInput, BlockExecutionError, BlockExecutionStrategyFactory, +}; +use reth_execution_types::BlockExecutionResult; +use reth_primitives::{logs_bloom, EthPrimitives, Receipt, TransactionSigned}; + +/// Block builder for Ethereum. +#[derive(Debug, Clone)] +pub struct EthBlockAssembler { + /// The chainspec. + pub chain_spec: Arc, + /// Extra data to use for the blocks. + pub extra_data: Bytes, +} + +impl EthBlockAssembler { + /// Creates a new [`EthBlockAssembler`]. + pub fn new(chain_spec: Arc) -> Self { + Self { chain_spec, extra_data: Default::default() } + } +} + +impl BlockAssembler for EthBlockAssembler +where + Evm: for<'a> BlockExecutionStrategyFactory< + Primitives = EthPrimitives, + ExecutionCtx<'a> = EthBlockExecutionCtx<'a>, + >, + ChainSpec: EthChainSpec + EthereumHardforks, +{ + fn assemble_block( + &self, + input: BlockAssemblerInput<'_, '_, Evm>, + ) -> Result, BlockExecutionError> { + let BlockAssemblerInput { + evm_env, + execution_ctx: ctx, + parent, + transactions, + output: BlockExecutionResult { receipts, requests, gas_used }, + state_root, + .. + } = input; + + let timestamp = evm_env.block_env.timestamp; + + let transactions_root = proofs::calculate_transaction_root(&transactions); + let receipts_root = Receipt::calculate_receipt_root_no_memo(receipts); + let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); + + let withdrawals = self + .chain_spec + .is_shanghai_active_at_timestamp(timestamp) + .then(|| ctx.withdrawals.map(|w| w.into_owned()).unwrap_or_default()); + + let withdrawals_root = + withdrawals.as_deref().map(|w| proofs::calculate_withdrawals_root(w)); + let requests_hash = self + .chain_spec + .is_prague_active_at_timestamp(timestamp) + .then(|| requests.requests_hash()); + + let mut excess_blob_gas = None; + let mut blob_gas_used = None; + + // only determine cancun fields when active + if self.chain_spec.is_cancun_active_at_timestamp(timestamp) { + blob_gas_used = + Some(transactions.iter().map(|tx| tx.blob_gas_used().unwrap_or_default()).sum()); + excess_blob_gas = if self.chain_spec.is_cancun_active_at_timestamp(parent.timestamp) { + parent.maybe_next_block_excess_blob_gas( + self.chain_spec.blob_params_at_timestamp(timestamp), + ) + } else { + // for the first post-fork block, both parent.blob_gas_used and + // parent.excess_blob_gas are evaluated as 0 + Some(alloy_eips::eip7840::BlobParams::cancun().next_block_excess_blob_gas(0, 0)) + }; + } + + let header = Header { + parent_hash: ctx.parent_hash, + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: evm_env.block_env.beneficiary, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp, + mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), + nonce: BEACON_NONCE.into(), + base_fee_per_gas: Some(evm_env.block_env.basefee), + number: evm_env.block_env.number, + gas_limit: evm_env.block_env.gas_limit, + difficulty: evm_env.block_env.difficulty, + gas_used: *gas_used, + extra_data: self.extra_data.clone(), + parent_beacon_block_root: ctx.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_hash, + }; + + Ok(Block { + header, + body: BlockBody { transactions, ommers: Default::default(), withdrawals }, + }) + } +} diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index aa15b60d11..6d132632e6 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -2,7 +2,7 @@ use crate::{ dao_fork::{DAO_HARDFORK_ACCOUNTS, DAO_HARDFORK_BENEFICIARY}, - EthEvmConfig, + EthBlockAssembler, EthEvmConfig, }; use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec::Vec}; use alloy_consensus::{Header, Transaction}; @@ -17,7 +17,7 @@ use reth_evm::{ }, state_change::post_block_balance_increments, system_calls::{OnStateHook, StateChangePostBlockSource, StateChangeSource, SystemCaller}, - Database, Evm, EvmEnv, EvmFactory, EvmFor, InspectorFor, TransactionEnv, + Database, Evm, EvmFactory, EvmFor, InspectorFor, TransactionEnv, }; use reth_execution_types::BlockExecutionResult; use reth_primitives::{ @@ -30,7 +30,7 @@ use revm::{ impl BlockExecutionStrategyFactory for EthEvmConfig where - EvmF: EvmFactory, Tx: TransactionEnv + FromRecoveredTx> + EvmF: EvmFactory, Spec = SpecId> + Send + Sync + Unpin @@ -38,9 +38,14 @@ where + '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>>; + type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; + type BlockAssembler = EthBlockAssembler; + + fn block_assembler(&self) -> &Self::BlockAssembler { + &self.block_assembler + } fn context_for_block<'a>(&self, block: &'a SealedBlock) -> Self::ExecutionCtx<'a> { EthBlockExecutionCtx { @@ -97,7 +102,7 @@ pub struct EthExecutionStrategy<'a, Evm> { chain_spec: &'a ChainSpec, /// Context for block execution. - ctx: EthBlockExecutionCtx<'a>, + pub ctx: EthBlockExecutionCtx<'a>, /// The EVM used by strategy. evm: Evm, /// Utility to call system smart contracts. @@ -190,9 +195,7 @@ where Ok(gas_used) } - fn apply_post_execution_changes( - mut self, - ) -> Result, Self::Error> { + fn finish(mut self) -> Result<(Self::Evm, BlockExecutionResult), Self::Error> { let requests = if self.chain_spec.is_prague_active_at_timestamp(self.evm.block().timestamp) { // Collect all EIP-6110 deposits @@ -246,7 +249,7 @@ where ); let gas_used = self.receipts.last().map(|r| r.cumulative_gas_used).unwrap_or_default(); - Ok(BlockExecutionResult { receipts: self.receipts, requests, gas_used }) + Ok((self.evm, BlockExecutionResult { receipts: self.receipts, requests, gas_used })) } fn with_state_hook(&mut self, hook: Option>) { diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index f9541b8cf1..3e6964d8fe 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -41,6 +41,9 @@ use reth_ethereum_forks::EthereumHardfork; pub mod execute; +mod build; +pub use build::EthBlockAssembler; + /// Ethereum DAO hardfork state change data. pub mod dao_fork; @@ -52,6 +55,7 @@ pub mod eip6110; pub struct EthEvmConfig { chain_spec: Arc, evm_factory: EvmFactory, + block_assembler: EthBlockAssembler, } impl EthEvmConfig { @@ -74,7 +78,11 @@ impl EthEvmConfig { impl EthEvmConfig { /// Creates a new Ethereum EVM configuration with the given chain spec and EVM factory. pub fn new_with_evm_factory(chain_spec: Arc, evm_factory: EvmFactory) -> Self { - Self { chain_spec, evm_factory } + Self { + block_assembler: EthBlockAssembler::new(chain_spec.clone()), + chain_spec, + evm_factory, + } } /// Returns the chain spec associated with this configuration. @@ -85,7 +93,7 @@ impl EthEvmConfig { impl ConfigureEvmEnv for EthEvmConfig where - EvmF: EvmFactory, Tx: TransactionEnv + FromRecoveredTx> + EvmF: EvmFactory, Spec = SpecId> + Send + Sync + Unpin @@ -185,7 +193,7 @@ where impl ConfigureEvm for EthEvmConfig where - EvmF: EvmFactory, Tx: TransactionEnv + FromRecoveredTx> + EvmF: EvmFactory, Spec = SpecId> + Send + Sync + Unpin diff --git a/crates/ethereum/payload/Cargo.toml b/crates/ethereum/payload/Cargo.toml index 198db952c4..7367139e56 100644 --- a/crates/ethereum/payload/Cargo.toml +++ b/crates/ethereum/payload/Cargo.toml @@ -21,7 +21,6 @@ reth-payload-builder.workspace = true reth-storage-api.workspace = true reth-payload-builder-primitives.workspace = true reth-payload-primitives.workspace = true -reth-execution-types.workspace = true reth-basic-payload-builder.workspace = true reth-evm.workspace = true reth-evm-ethereum.workspace = true diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index 2920f58dca..83d90342e9 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -12,32 +12,24 @@ pub mod validator; pub use validator::EthereumExecutionPayloadValidator; -use alloy_consensus::{BlockHeader, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH}; -use alloy_eips::{eip4844::DATA_GAS_PER_BLOB, merge::BEACON_NONCE}; +use alloy_consensus::{Transaction, Typed2718}; use alloy_primitives::U256; use reth_basic_payload_builder::{ is_better_payload, BuildArguments, BuildOutcome, PayloadBuilder, PayloadConfig, }; use reth_chainspec::{ChainSpec, ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_errors::{BlockExecutionError, BlockValidationError}; -use reth_ethereum_primitives::{Block, BlockBody, EthPrimitives, TransactionSigned}; +use reth_ethereum_primitives::{EthPrimitives, TransactionSigned}; use reth_evm::{ - execute::{BlockExecutionStrategy, BlockExecutionStrategyFactory}, + execute::{BlockBuilder, BlockBuilderOutcome, 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; use reth_payload_primitives::PayloadBuilderAttributes; -use reth_primitives_traits::{ - proofs::{self}, - Block as _, SignedTransaction, -}; -use reth_revm::{ - database::StateProviderDatabase, - db::{states::bundle_state::BundleRetention, State}, -}; +use reth_primitives_traits::SignedTransaction; +use reth_revm::{database::StateProviderDatabase, db::State}; use reth_storage_api::StateProviderFactory; use reth_transaction_pool::{ error::InvalidPoolTransactionError, BestTransactions, BestTransactionsAttributes, @@ -154,42 +146,39 @@ where let PayloadConfig { parent_header, attributes } = config; let state_provider = client.state_by_block_hash(parent_header.hash())?; - let state = StateProviderDatabase::new(state_provider); + let state = StateProviderDatabase::new(&state_provider); let mut db = State::builder().with_database(cached_reads.as_db_mut(state)).with_bundle_update().build(); - 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), - parent_beacon_block_root: attributes.parent_beacon_block_root(), - withdrawals: Some(attributes.withdrawals().clone()), - }; - - let mut strategy = evm_config - .strategy_for_next_block(&mut db, &parent_header, next_attributes) + let mut builder = evm_config + .builder_for_next_block( + &mut db, + &parent_header, + 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), + parent_beacon_block_root: attributes.parent_beacon_block_root(), + withdrawals: Some(attributes.withdrawals().clone()), + }, + ) .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 = strategy.evm_mut().block().gas_limit; - let base_fee = strategy.evm_mut().block().basefee; - - let mut executed_txs = Vec::new(); + let block_gas_limit: u64 = builder.evm_mut().block().gas_limit; + let base_fee = builder.evm_mut().block().basefee; let mut best_txs = best_txs(BestTransactionsAttributes::new( base_fee, - strategy.evm_mut().block().blob_gasprice().map(|gasprice| gasprice as u64), + builder.evm_mut().block().blob_gasprice().map(|gasprice| gasprice as u64), )); let mut total_fees = U256::ZERO; - let block_number = strategy.evm_mut().block().number; - let beneficiary = strategy.evm_mut().block().beneficiary; - - strategy.apply_pre_execution_changes().map_err(|err| { + builder.apply_pre_execution_changes().map_err(|err| { warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); PayloadBuilderError::Internal(err.into()) })?; @@ -244,7 +233,7 @@ where } } - let gas_used = match strategy.execute_transaction(tx.as_recovered_ref()) { + let gas_used = match builder.execute_transaction(tx.clone()) { Ok(gas_used) => gas_used, Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { error, .. @@ -284,119 +273,42 @@ where 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()); } // check if we have a better block if !is_better_payload(best_payload.as_ref(), total_fees) { // Release db - drop(strategy); + drop(builder); // can skip building the block return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads }) } - let BlockExecutionResult { receipts, requests, gas_used } = strategy - .apply_post_execution_changes() - .map_err(|err| PayloadBuilderError::Internal(err.into()))?; + let BlockBuilderOutcome { execution_result, block, .. } = + builder.finish(&state_provider).map_err(|err| PayloadBuilderError::Internal(err.into()))?; - 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 - db.merge_transitions(BundleRetention::Reverts); - - let requests_hash = requests.as_ref().map(|requests| requests.requests_hash()); - let execution_outcome = ExecutionOutcome::new( - db.take_bundle(), - vec![receipts], - block_number, - vec![requests.clone().unwrap_or_default()], - ); - let receipts_root = - execution_outcome.ethereum_receipts_root(block_number).expect("Number is in range"); - let logs_bloom = execution_outcome.block_logs_bloom(block_number).expect("Number is in range"); - - // calculate the state root - let hashed_state = db.database.db.hashed_post_state(execution_outcome.state()); - let (state_root, _) = { - db.database.inner().state_root_with_updates(hashed_state).inspect_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_header.hash(), - %err, - "failed to calculate state root for payload" - ); - })? - }; - - // create the block header - let transactions_root = proofs::calculate_transaction_root(&executed_txs); + let requests = chain_spec + .is_prague_active_at_timestamp(attributes.timestamp) + .then_some(execution_result.requests); // initialize empty blob sidecars at first. If cancun is active then this will let mut blob_sidecars = Vec::new(); - let mut excess_blob_gas = None; - let mut blob_gas_used = None; // only determine cancun fields when active if chain_spec.is_cancun_active_at_timestamp(attributes.timestamp) { // grab the blob sidecars from the executed txs blob_sidecars = pool .get_all_blobs_exact( - executed_txs.iter().filter(|tx| tx.is_eip4844()).map(|tx| *tx.tx_hash()).collect(), + block + .body() + .transactions() + .filter(|tx| tx.is_eip4844()) + .map(|tx| *tx.tx_hash()) + .collect(), ) .map_err(PayloadBuilderError::other)?; - - excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_header.timestamp) { - parent_header.maybe_next_block_excess_blob_gas(blob_params) - } else { - // for the first post-fork block, both parent.blob_gas_used and - // parent.excess_blob_gas are evaluated as 0 - Some(alloy_eips::eip7840::BlobParams::cancun().next_block_excess_blob_gas(0, 0)) - }; - - blob_gas_used = Some(block_blob_count * DATA_GAS_PER_BLOB); } - let header = Header { - parent_hash: parent_header.hash(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary, - state_root, - transactions_root, - receipts_root, - withdrawals_root, - logs_bloom, - timestamp: attributes.timestamp, - mix_hash: attributes.prev_randao, - nonce: BEACON_NONCE.into(), - base_fee_per_gas: Some(base_fee), - number: parent_header.number + 1, - gas_limit: block_gas_limit, - difficulty: U256::ZERO, - gas_used, - extra_data: builder_config.extra_data, - parent_beacon_block_root: attributes.parent_beacon_block_root, - blob_gas_used, - excess_blob_gas, - requests_hash, - }; - - let withdrawals = chain_spec - .is_shanghai_active_at_timestamp(attributes.timestamp) - .then(|| attributes.withdrawals.clone()); - - // seal the block - let block = Block { - header, - body: BlockBody { transactions: executed_txs, ommers: vec![], withdrawals }, - }; - - let sealed_block = Arc::new(block.seal_slow()); + let sealed_block = Arc::new(block.sealed_block().clone()); debug!(target: "payload_builder", id=%attributes.id, sealed_block_header = ?sealed_block.sealed_header(), "sealed built block"); let mut payload = EthBuiltPayload::new(attributes.id, sealed_block, total_fees, requests); diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index 7156b9c4c2..82eb605020 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -19,7 +19,9 @@ reth-execution-types.workspace = true reth-metrics = { workspace = true, optional = true } reth-primitives.workspace = true reth-primitives-traits.workspace = true +reth-storage-api.workspace = true reth-storage-errors.workspace = true +reth-trie-common.workspace = true revm.workspace = true revm-database.workspace = true @@ -65,6 +67,8 @@ std = [ "reth-storage-errors/std", "futures-util/std", "derive_more/std", + "reth-storage-api/std", + "reth-trie-common/std", ] metrics = [ "std", @@ -78,6 +82,7 @@ test-utils = [ "reth-primitives/test-utils", "reth-primitives-traits/test-utils", "revm/test-utils", + "reth-trie-common/test-utils", ] op = [ "revm-optimism", diff --git a/crates/evm/src/aliases.rs b/crates/evm/src/aliases.rs index 6cb225cb40..db53095031 100644 --- a/crates/evm/src/aliases.rs +++ b/crates/evm/src/aliases.rs @@ -1,6 +1,6 @@ //! Helper aliases when working with [`NodePrimitives`] and the traits in this crate. use crate::{ConfigureEvm, ConfigureEvmEnv}; -use alloy_evm::{EvmEnv, EvmFactory}; +use alloy_evm::EvmFactory; use reth_primitives_traits::NodePrimitives; use revm::inspector::NoOpInspector; @@ -33,19 +33,14 @@ where } /// Helper to access [`EvmFactory::Error`] for a given [`ConfigureEvm`]. -pub type EvmErrorFor = <::EvmFactory as EvmFactory< - EvmEnv<::Spec>, ->>::Error; +pub type EvmErrorFor = <::EvmFactory as EvmFactory>::Error; /// Helper to access [`EvmFactory::HaltReason`] for a given [`ConfigureEvm`]. -pub type HaltReasonFor = <::EvmFactory as EvmFactory< - EvmEnv<::Spec>, ->>::HaltReason; +pub type HaltReasonFor = <::EvmFactory as EvmFactory>::HaltReason; /// 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; +pub type EvmFor = + <::EvmFactory as EvmFactory>::Evm; diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index 4785856215..8ebef52fc6 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -1,13 +1,17 @@ //! Traits for execution. use alloy_consensus::BlockHeader; -use alloy_evm::Evm; +use alloy_evm::{Evm, EvmEnv}; +use reth_storage_api::StateProvider; +use reth_trie_common::{updates::TrieUpdates, HashedPostState}; // Re-export execution types -use crate::{system_calls::OnStateHook, ConfigureEvmFor, Database, EvmFor, InspectorFor}; +use crate::{ + system_calls::OnStateHook, ConfigureEvmFor, Database, EvmFor, HaltReasonFor, InspectorFor, +}; use alloc::{boxed::Box, vec::Vec}; use alloy_primitives::{ map::{DefaultHashBuilder, HashMap}, - Address, + Address, B256, }; pub use reth_execution_errors::{ BlockExecutionError, BlockValidationError, InternalBlockExecutionError, @@ -15,15 +19,16 @@ 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, SealedHeader, + HeaderTy, NodePrimitives, Receipt, Recovered, RecoveredBlock, SealedBlock, SealedHeader, }; +use reth_primitives_traits::{BlockTy, ReceiptTy, TxTy}; pub use reth_storage_errors::provider::ProviderError; use revm::{ context::result::ExecutionResult, inspector::NoOpInspector, state::{Account, AccountStatus, EvmState}, }; -use revm_database::{states::bundle_state::BundleRetention, State}; +use revm_database::{states::bundle_state::BundleRetention, BundleState, State}; /// A type that knows how to execute a block. It is assumed to operate on a /// [`crate::Evm`] internally and use [`State`] as database. @@ -214,13 +219,24 @@ pub trait BlockExecutionStrategy { /// Applies any necessary changes after executing the block's transactions. fn apply_post_execution_changes( self, - ) -> Result::Receipt>, Self::Error>; + ) -> Result::Receipt>, Self::Error> + where + Self: Sized, + { + self.finish().map(|(_, result)| result) + } /// 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; + + /// Completes execution and returns the underlying EVM along with execution result. + #[expect(clippy::type_complexity)] + fn finish( + self, + ) -> Result<(Self::Evm, BlockExecutionResult>), Self::Error>; } /// A factory that can create block execution strategies. @@ -236,6 +252,11 @@ pub trait BlockExecutionStrategy { /// /// For more context on the strategy design, see the documentation for [`BlockExecutionStrategy`]. /// +/// Additionally, trait implementations are expected to define a [`BlockAssembler`] type that is +/// used to assemble blocks. Assember combined with strategy are used to create a [`BlockBuilder`]. +/// [`BlockBuilder`] exposes a simple API for building blocks and can be consumed by payload +/// builder. +/// /// [`ExecutionCtx`]: BlockExecutionStrategyFactory::ExecutionCtx pub trait BlockExecutionStrategyFactory: ConfigureEvmFor + 'static { /// Primitive types used by the strategy. @@ -252,7 +273,13 @@ pub trait BlockExecutionStrategyFactory: ConfigureEvmFor + 'st /// /// 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>; + type ExecutionCtx<'a>: Clone; + + /// A type that knows how to build a block. + type BlockAssembler: BlockAssembler; + + /// Provides reference to configured [`BlockAssembler`]. + fn block_assembler(&self) -> &Self::BlockAssembler; /// Returns the configured [`BlockExecutionStrategyFactory::ExecutionCtx`] for a given block. fn context_for_block<'a>( @@ -289,17 +316,225 @@ pub trait BlockExecutionStrategyFactory: ConfigureEvmFor + 'st self.create_strategy(evm, ctx) } - /// Creates a strategy for execution of a next block. - fn strategy_for_next_block<'a, DB: Database>( + /// Creates a [`BlockBuilder`]. Should be used when building a new block. + /// + /// Block builder wraps an inner [`BlockExecutionStrategy`] and has a similar interface. Builder + /// collects all of the executed transactions, and once [`BlockBuilder::finish`] is called, it + /// invokes the configured [`BlockAssembler`] to create a block. + fn create_block_builder<'a, DB, I>( + &'a self, + evm: EvmFor, I>, + parent: &'a SealedHeader>, + ctx: Self::ExecutionCtx<'a>, + ) -> impl BlockBuilder> + where + DB: Database, + I: InspectorFor<&'a mut State, Self> + 'a, + { + BasicBlockBuilder { + strategy: self.create_strategy(evm, ctx.clone()), + ctx, + assembler: self.block_assembler(), + parent, + transactions: Vec::new(), + } + } + + /// Creates a [`BlockBuilder`] for building of a new block. This is a helper to invoke + /// [`BlockExecutionStrategyFactory::create_block_builder`]. + fn builder_for_next_block<'a, DB: Database>( &'a self, db: &'a mut State, parent: &'a SealedHeader<::BlockHeader>, attributes: Self::NextBlockEnvCtx, - ) -> Result, Self::Error> { + ) -> 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)) + Ok(self.create_block_builder(evm, parent, ctx)) + } +} + +/// Input for block building. Consumed by [`BlockAssembler`]. +#[derive(derive_more::Debug)] +#[non_exhaustive] +pub struct BlockAssemblerInput<'a, 'b, Evm: BlockExecutionStrategyFactory> { + /// Configuration of EVM used when executing the block. + /// + /// Contains context relevant to EVM such as [`revm::context::BlockEnv`]. + pub evm_env: EvmEnv, + /// [`BlockExecutionStrategyFactory::ExecutionCtx`] used to execute the block. + pub execution_ctx: Evm::ExecutionCtx<'a>, + /// Parent block header. + pub parent: &'a SealedHeader>, + /// Transactions that were executed in this block. + pub transactions: Vec>, + /// Output of block execution. + pub output: &'b BlockExecutionResult>, + /// [`BundleState`] after the block execution. + pub bundle_state: &'a BundleState, + /// Provider with access to state. + #[debug(skip)] + pub state_provider: &'b dyn StateProvider, + /// State root for this block. + pub state_root: B256, +} + +/// A type that knows how to assemble a block. +#[auto_impl::auto_impl(&, Arc)] +pub trait BlockAssembler { + /// Builds a block. see [`BlockAssemblerInput`] documentation for more details. + fn assemble_block( + &self, + input: BlockAssemblerInput<'_, '_, Evm>, + ) -> Result, BlockExecutionError>; +} + +/// Output of block building. +#[derive(Debug, Clone)] +pub struct BlockBuilderOutcome { + /// Result of block execution. + pub execution_result: BlockExecutionResult, + /// Hashed state after execution. + pub hashed_state: HashedPostState, + /// Trie updates collected during state root calculation. + pub trie_updates: TrieUpdates, + /// The built block. + pub block: RecoveredBlock, +} + +/// A type that knows how to execute and build a block. +/// +/// It wraps an inner [`BlockExecutionStrategy`] and provides a way to execute transactions and +/// construct a block. +/// +/// This is a helper to erase `BasicBlockBuilder` type. +pub trait BlockBuilder { + /// The primitive types used by the inner [`BlockExecutionStrategy`]. + type Primitives: NodePrimitives; + /// Inner [`BlockExecutionStrategy`]. + type Strategy: BlockExecutionStrategy< + Primitives = Self::Primitives, + Error = BlockExecutionError, + >; + + /// Invokes [`BlockExecutionStrategy::apply_pre_execution_changes`]. + fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError>; + + /// Invokes [`BlockExecutionStrategy::execute_transaction_with_result_closure`] and saves the + /// transaction in internal state. + fn execute_transaction_with_result_closure( + &mut self, + tx: Recovered>, + f: impl FnOnce( + &ExecutionResult<<::Evm as Evm>::HaltReason>, + ), + ) -> Result; + + /// Invokes [`BlockExecutionStrategy::execute_transaction`] and saves the transaction in + /// internal state. + fn execute_transaction( + &mut self, + tx: Recovered>, + ) -> Result { + self.execute_transaction_with_result_closure(tx, |_| ()) + } + + /// Completes the block building process and returns the [`BlockBuilderOutcome`]. + fn finish( + self, + state_provider: impl StateProvider, + ) -> Result, BlockExecutionError>; + + /// Provides mutable access to the inner [`BlockExecutionStrategy`]. + fn strategy_mut(&mut self) -> &mut Self::Strategy; + + /// Helper to access inner [`BlockExecutionStrategy::Evm`]. + fn evm_mut(&mut self) -> &mut ::Evm { + self.strategy_mut().evm_mut() + } + + /// Consumes the type and returns the underlying [`BlockExecutionStrategy`]. + fn into_strategy(self) -> Self::Strategy; +} + +struct BasicBlockBuilder<'a, Evm, DB, I, Builder> +where + Evm: BlockExecutionStrategyFactory, + DB: Database + 'a, + I: InspectorFor<&'a mut State, Evm> + 'a, +{ + strategy: Evm::Strategy<'a, DB, I>, + transactions: Vec>>, + ctx: Evm::ExecutionCtx<'a>, + parent: &'a SealedHeader>, + assembler: Builder, +} + +impl<'a, Evm, DB, I, Builder> BlockBuilder for BasicBlockBuilder<'a, Evm, DB, I, Builder> +where + Evm: BlockExecutionStrategyFactory, + DB: Database + 'a, + I: InspectorFor<&'a mut State, Evm> + 'a, + Builder: BlockAssembler, +{ + type Primitives = Evm::Primitives; + type Strategy = Evm::Strategy<'a, DB, I>; + + fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { + self.strategy.apply_pre_execution_changes() + } + + fn execute_transaction_with_result_closure( + &mut self, + tx: Recovered>, + f: impl FnOnce(&ExecutionResult>), + ) -> Result { + let gas_used = + self.strategy.execute_transaction_with_result_closure(tx.as_recovered_ref(), f)?; + self.transactions.push(tx); + Ok(gas_used) + } + + fn finish( + self, + state: impl StateProvider, + ) -> Result, BlockExecutionError> { + let (evm, result) = self.strategy.finish()?; + let (db, evm_env) = evm.finish(); + + // merge all transitions into bundle state + db.merge_transitions(BundleRetention::Reverts); + + // calculate the state root + let hashed_state = state.hashed_post_state(&db.bundle_state); + let (state_root, trie_updates) = state.state_root_with_updates(hashed_state.clone())?; + + let (transactions, senders) = + self.transactions.into_iter().map(|tx| tx.into_parts()).unzip(); + + let block = self.assembler.assemble_block(BlockAssemblerInput { + evm_env, + execution_ctx: self.ctx, + parent: self.parent, + transactions, + output: &result, + bundle_state: &db.bundle_state, + state_provider: &state, + state_root, + })?; + + let block = RecoveredBlock::new_unhashed(block, senders); + + Ok(BlockBuilderOutcome { execution_result: result, hashed_state, trie_updates, block }) + } + + fn strategy_mut(&mut self) -> &mut Self::Strategy { + &mut self.strategy + } + + fn into_strategy(self) -> Self::Strategy { + self.strategy } } diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 6775b7951e..7f2a0c9ec4 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -19,7 +19,7 @@ extern crate alloc; use alloy_eips::{eip2930::AccessList, eip4895::Withdrawals}; pub use alloy_evm::evm::EvmFactory; -use alloy_evm::{FromRecoveredTx, IntoTxEnv}; +use alloy_evm::IntoTxEnv; use alloy_primitives::{Address, B256}; use core::fmt::Debug; use reth_primitives_traits::{BlockHeader, SignedTransaction}; @@ -41,28 +41,28 @@ pub mod system_calls; /// test helpers for mocking executor pub mod test_utils; -pub use alloy_evm::{Database, Evm, EvmEnv, EvmError, InvalidTxError}; +pub use alloy_evm::{Database, Evm, EvmEnv, EvmError, FromRecoveredTx, InvalidTxError}; /// Alias for `EvmEnv<::Spec>` pub type EvmEnvFor = EvmEnv<::Spec>; /// Helper trait to bound [`Inspector`] for a [`ConfigureEvm`]. pub trait InspectorFor: - Inspector<>>::Context> + Inspector<::Context> { } impl InspectorFor for T where DB: Database, Evm: ConfigureEvm, - T: Inspector<>>::Context>, + T: Inspector<::Context>, { } /// Trait for configuring the EVM for executing full blocks. pub trait ConfigureEvm: ConfigureEvmEnv { /// The EVM factory. - type EvmFactory: EvmFactory, Tx = Self::TxEnv>; + type EvmFactory: EvmFactory; /// Provides a reference to [`EvmFactory`] implementation. fn evm_factory(&self) -> &Self::EvmFactory; diff --git a/crates/optimism/consensus/src/validation/isthmus.rs b/crates/optimism/consensus/src/validation/isthmus.rs index f688b80619..744c3b1b65 100644 --- a/crates/optimism/consensus/src/validation/isthmus.rs +++ b/crates/optimism/consensus/src/validation/isthmus.rs @@ -92,7 +92,7 @@ where /// `L2ToL1MessagePasser.sol` predeploy post block execution. /// /// Takes pre-hashed storage updates of `L2ToL1MessagePasser.sol` predeploy, resulting from -/// execution of block, if any. Otherwise takes empty [`reth_trie_common::HashedStorage::default`]. +/// execution of block, if any. Otherwise takes empty [`HashedStorage::default`]. /// /// See . pub fn verify_withdrawals_root_prehashed( diff --git a/crates/optimism/evm/src/build.rs b/crates/optimism/evm/src/build.rs new file mode 100644 index 0000000000..22497242f3 --- /dev/null +++ b/crates/optimism/evm/src/build.rs @@ -0,0 +1,125 @@ +use crate::{OpBlockExecutionCtx, OpNextBlockEnvAttributes}; +use alloc::sync::Arc; +use alloy_consensus::{ + constants::EMPTY_WITHDRAWALS, proofs, BlockBody, Header, TxReceipt, EMPTY_OMMER_ROOT_HASH, +}; +use alloy_eips::merge::BEACON_NONCE; +use alloy_primitives::logs_bloom; +use reth_evm::execute::{BlockAssembler, BlockAssemblerInput, BlockExecutionStrategyFactory}; +use reth_execution_errors::BlockExecutionError; +use reth_execution_types::BlockExecutionResult; +use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; +use reth_optimism_forks::OpHardforks; +use reth_optimism_primitives::DepositReceipt; +use reth_primitives_traits::{Block, BlockTy, NodePrimitives, SignedTransaction}; + +/// Block builder for Optimism. +#[derive(Debug)] +pub struct OpBlockAssembler { + chain_spec: Arc, +} + +impl OpBlockAssembler { + /// Creates a new [`OpBlockAssembler`]. + pub fn new(chain_spec: Arc) -> Self { + Self { chain_spec } + } +} + +impl Clone for OpBlockAssembler { + fn clone(&self) -> Self { + Self { chain_spec: self.chain_spec.clone() } + } +} + +impl BlockAssembler for OpBlockAssembler +where + ChainSpec: OpHardforks, + T: SignedTransaction, + Evm: for<'a> BlockExecutionStrategyFactory< + Primitives: NodePrimitives< + Receipt: DepositReceipt, + BlockHeader = Header, + BlockBody = alloy_consensus::BlockBody, + SignedTx = T, + >, + NextBlockEnvCtx = OpNextBlockEnvAttributes, + ExecutionCtx<'a> = OpBlockExecutionCtx, + >, +{ + fn assemble_block( + &self, + input: BlockAssemblerInput<'_, '_, Evm>, + ) -> Result, BlockExecutionError> { + let BlockAssemblerInput { + evm_env, + execution_ctx: ctx, + transactions, + output: BlockExecutionResult { receipts, gas_used, .. }, + bundle_state, + state_root, + state_provider, + .. + } = input; + + let timestamp = evm_env.block_env.timestamp; + + let transactions_root = proofs::calculate_transaction_root(&transactions); + let receipts_root = + calculate_receipt_root_no_memo_optimism(receipts, &self.chain_spec, timestamp); + let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); + + let withdrawals_root = if self.chain_spec.is_isthmus_active_at_timestamp(timestamp) { + // withdrawals root field in block header is used for storage root of L2 predeploy + // `l2tol1-message-passer` + Some(isthmus::withdrawals_root(bundle_state, state_provider)?) + } else if self.chain_spec.is_canyon_active_at_timestamp(timestamp) { + Some(EMPTY_WITHDRAWALS) + } else { + None + }; + + let (excess_blob_gas, blob_gas_used) = + if self.chain_spec.is_ecotone_active_at_timestamp(timestamp) { + (Some(0), Some(0)) + } else { + (None, None) + }; + + let header = Header { + parent_hash: ctx.parent_hash, + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: evm_env.block_env.beneficiary, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp, + mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), + nonce: BEACON_NONCE.into(), + base_fee_per_gas: Some(evm_env.block_env.basefee), + number: evm_env.block_env.number, + gas_limit: evm_env.block_env.gas_limit, + difficulty: evm_env.block_env.difficulty, + gas_used: *gas_used, + extra_data: ctx.extra_data, + parent_beacon_block_root: ctx.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_hash: None, + }; + + Ok(BlockTy::::new( + header, + BlockBody { + transactions, + ommers: Default::default(), + withdrawals: self + .chain_spec + .is_canyon_active_at_timestamp(timestamp) + .then(Default::default), + }, + )) + } +} diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 4cd4dace09..eacad685a3 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -1,14 +1,15 @@ //! Optimism block execution strategy. use crate::{ - l1::ensure_create2_deployer, BasicOpReceiptBuilder, OpBlockExecutionError, OpEvmConfig, - OpReceiptBuilder, ReceiptBuilderCtx, + l1::ensure_create2_deployer, BasicOpReceiptBuilder, OpBlockAssembler, OpBlockExecutionError, + OpEvmConfig, OpReceiptBuilder, ReceiptBuilderCtx, }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_consensus::{ transaction::Recovered, BlockHeader, Eip658Value, Header, Receipt, Transaction as _, TxReceipt, }; use alloy_evm::FromRecoveredTx; +use alloy_primitives::Bytes; use op_alloy_consensus::OpDepositReceipt; use reth_chainspec::EthChainSpec; use reth_evm::{ @@ -30,21 +31,33 @@ use revm_database::State; use revm_primitives::B256; use tracing::trace; -impl BlockExecutionStrategyFactory for OpEvmConfig +impl BlockExecutionStrategyFactory for OpEvmConfig where ChainSpec: EthChainSpec + OpHardforks + 'static, - N: NodePrimitives, - revm_optimism::OpTransaction: FromRecoveredTx, + T: SignedTransaction + OpTransaction, + N: NodePrimitives< + Receipt: DepositReceipt, + SignedTx = T, + BlockHeader = Header, + BlockBody = alloy_consensus::BlockBody, + >, + 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; + type BlockAssembler = OpBlockAssembler; + + fn block_assembler(&self) -> &Self::BlockAssembler { + &self.block_assember + } fn context_for_block<'a>(&self, block: &'a SealedBlock) -> Self::ExecutionCtx<'a> { OpBlockExecutionCtx { parent_hash: block.header().parent_hash(), parent_beacon_block_root: block.header().parent_beacon_block_root(), + extra_data: block.header().extra_data().clone(), } } @@ -56,6 +69,7 @@ where OpBlockExecutionCtx { parent_hash: parent.hash(), parent_beacon_block_root: attributes.parent_beacon_block_root, + extra_data: attributes.extra_data, } } @@ -73,12 +87,14 @@ where } /// Context for OP block execution. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct OpBlockExecutionCtx { /// Parent block hash. pub parent_hash: B256, /// Parent beacon block root. pub parent_beacon_block_root: Option, + /// The block's extra data. + pub extra_data: Bytes, } /// Block execution strategy for Optimism. @@ -253,9 +269,7 @@ where Ok(gas_used) } - fn apply_post_execution_changes( - mut self, - ) -> Result, Self::Error> { + fn finish(mut self) -> Result<(Self::Evm, BlockExecutionResult), Self::Error> { let balance_increments = post_block_balance_increments::
( &self.chain_spec.clone(), self.evm.block(), @@ -275,7 +289,14 @@ where ); let gas_used = self.receipts.last().map(|r| r.cumulative_gas_used()).unwrap_or_default(); - Ok(BlockExecutionResult { receipts: self.receipts, requests: Default::default(), gas_used }) + Ok(( + self.evm, + BlockExecutionResult { + receipts: self.receipts, + requests: Default::default(), + gas_used, + }, + )) } fn with_state_hook(&mut self, hook: Option>) { diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 4527e5930e..e48669e949 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -39,6 +39,8 @@ pub mod l1; pub use l1::*; mod receipts; pub use receipts::*; +mod build; +pub use build::OpBlockAssembler; mod error; pub use error::OpBlockExecutionError; @@ -49,6 +51,7 @@ pub struct OpEvmConfig, evm_factory: OpEvmFactory, receipt_builder: Arc>, + block_assember: OpBlockAssembler, } impl Clone for OpEvmConfig { @@ -57,6 +60,7 @@ impl Clone for OpEvmConfig { chain_spec: self.chain_spec.clone(), evm_factory: OpEvmFactory::default(), receipt_builder: self.receipt_builder.clone(), + block_assember: self.block_assember.clone(), } } } @@ -75,6 +79,7 @@ impl OpEvmConfig { receipt_builder: impl OpReceiptBuilder, ) -> Self { Self { + block_assember: OpBlockAssembler::new(chain_spec.clone()), chain_spec, evm_factory: OpEvmFactory::default(), receipt_builder: Arc::new(receipt_builder), diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index 28fba470d5..58849c5d77 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -29,7 +29,7 @@ use reth_node_builder::{ }; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::OpBeaconConsensus; -use reth_optimism_evm::{BasicOpReceiptBuilder, OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_forks::OpHardforks; use reth_optimism_payload_builder::{ builder::OpPayloadTransactions, @@ -299,7 +299,6 @@ where ctx.node.pool().clone(), ctx.node.provider().clone(), ctx.node.evm_config().clone(), - BasicOpReceiptBuilder::default(), ); // install additional OP specific rpc methods let debug_ext = OpDebugWitnessApi::new( @@ -642,21 +641,12 @@ impl OpPayloadBuilder { /// A helper method to initialize [`reth_optimism_payload_builder::OpPayloadBuilder`] with the /// given EVM config. - #[expect(clippy::type_complexity)] pub fn build( self, evm_config: Evm, ctx: &BuilderContext, pool: Pool, - ) -> eyre::Result< - reth_optimism_payload_builder::OpPayloadBuilder< - Pool, - Node::Provider, - Evm, - PrimitivesTy, - Txs, - >, - > + ) -> eyre::Result> where Node: FullNodeTypes< Types: NodeTypesWithEngine< @@ -675,7 +665,6 @@ impl OpPayloadBuilder { pool, ctx.provider().clone(), evm_config, - BasicOpReceiptBuilder::default(), OpBuilderConfig { da_config: self.da_config.clone() }, ) .with_transactions(self.best_transactions.clone()) @@ -698,13 +687,8 @@ where + 'static, Txs: OpPayloadTransactions, { - type PayloadBuilder = reth_optimism_payload_builder::OpPayloadBuilder< - Pool, - Node::Provider, - OpEvmConfig, - PrimitivesTy, - Txs, - >; + type PayloadBuilder = + reth_optimism_payload_builder::OpPayloadBuilder; async fn build_payload_builder( self, diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index 9abe191c30..36b4c0d02c 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -30,7 +30,6 @@ reth-chain-state.workspace = true reth-payload-validator.workspace = true # op-reth -reth-optimism-consensus.workspace = true reth-optimism-evm.workspace = true reth-optimism-forks.workspace = true reth-optimism-primitives = { workspace = true, features = ["reth-codec"] } diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 28bd40c3d1..4567eaed94 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -6,43 +6,33 @@ use crate::{ payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, OpPayloadPrimitives, }; -use alloy_consensus::{ - 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_consensus::{Transaction, Typed2718}; +use alloy_primitives::{Bytes, B256, U256}; use alloy_rlp::Encodable; use alloy_rpc_types_debug::ExecutionWitness; use alloy_rpc_types_engine::PayloadId; 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_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_evm::{ execute::{ - BlockExecutionError, BlockExecutionStrategy, BlockExecutionStrategyFactory, - BlockValidationError, + BlockBuilder, BlockBuilderOutcome, BlockExecutionError, BlockExecutionStrategy, + BlockExecutionStrategyFactory, BlockValidationError, }, - ConfigureEvm, ConfigureEvmFor, Database, Evm, HaltReasonFor, + Database, Evm, }; use reth_execution_types::ExecutionOutcome; -use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; -use reth_optimism_evm::{OpNextBlockEnvAttributes, OpReceiptBuilder}; +use reth_optimism_evm::OpNextBlockEnvAttributes; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::transaction::signed::OpTransaction; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; -use reth_primitives::{transaction::SignedTransaction, BlockBody, NodePrimitives, SealedHeader}; -use reth_primitives_traits::{block::Block as _, proofs, RecoveredBlock}; -use reth_provider::{ - BlockExecutionResult, HashedPostStateProvider, ProviderError, StateProofProvider, - StateProviderFactory, StateRootProvider, StorageRootProvider, -}; +use reth_primitives::{transaction::SignedTransaction, NodePrimitives, SealedHeader, TxTy}; +use reth_provider::{ProviderError, StateProvider, StateProviderFactory}; use reth_revm::{ - cancelled::CancelOnDrop, - database::StateProviderDatabase, - db::{states::bundle_state::BundleRetention, State}, + cancelled::CancelOnDrop, database::StateProviderDatabase, db::State, witness::ExecutionWitnessRecord, }; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; @@ -52,12 +42,12 @@ use tracing::{debug, trace, warn}; /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct OpPayloadBuilder { +pub struct OpPayloadBuilder { /// The rollup's compute pending block configuration option. // TODO(clabby): Implement this feature. pub compute_pending_block: bool, /// The type responsible for creating the evm. - pub evm_config: EvmConfig, + pub evm_config: Evm, /// Transaction pool. pub pool: Pool, /// Node client. @@ -67,49 +57,27 @@ pub struct OpPayloadBuilder, Receipt = N::Receipt>>, } -impl OpPayloadBuilder -where - EvmConfig: ConfigureEvm, - N: NodePrimitives, -{ +impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. /// /// Configures the builder with the default settings. - pub fn new( - pool: Pool, - client: Client, - evm_config: EvmConfig, - receipt_builder: impl OpReceiptBuilder< - N::SignedTx, - HaltReasonFor, - Receipt = N::Receipt, - >, - ) -> Self { - Self::with_builder_config(pool, client, evm_config, receipt_builder, Default::default()) + pub fn new(pool: Pool, client: Client, evm_config: Evm) -> Self { + Self::with_builder_config(pool, client, evm_config, Default::default()) } /// Configures the builder with the given [`OpBuilderConfig`]. pub fn with_builder_config( pool: Pool, client: Client, - evm_config: EvmConfig, - receipt_builder: impl OpReceiptBuilder< - N::SignedTx, - HaltReasonFor, - Receipt = N::Receipt, - >, + evm_config: Evm, config: OpBuilderConfig, ) -> Self { Self { pool, client, compute_pending_block: true, - receipt_builder: Arc::new(receipt_builder), evm_config, config, best_transactions: (), @@ -117,9 +85,7 @@ where } } -impl - OpPayloadBuilder -{ +impl OpPayloadBuilder { /// Sets the rollup's compute pending block configuration option. pub const fn set_compute_pending_block(mut self, compute_pending_block: bool) -> Self { self.compute_pending_block = compute_pending_block; @@ -131,10 +97,8 @@ impl pub fn with_transactions( self, best_transactions: T, - ) -> OpPayloadBuilder { - let Self { - pool, client, compute_pending_block, evm_config, config, receipt_builder, .. - } = self; + ) -> OpPayloadBuilder { + let Self { pool, client, compute_pending_block, evm_config, config, .. } = self; OpPayloadBuilder { pool, client, @@ -142,7 +106,6 @@ impl evm_config, best_transactions, config, - receipt_builder, } } @@ -157,13 +120,12 @@ impl } } -impl OpPayloadBuilder +impl OpPayloadBuilder where Pool: TransactionPool>, Client: StateProviderFactory + ChainSpecProvider, N: OpPayloadPrimitives, - EvmConfig: - BlockExecutionStrategyFactory, + Evm: 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 @@ -190,24 +152,18 @@ where config, cancel, best_payload, - receipt_builder: self.receipt_builder.clone(), }; let builder = OpBuilder::new(best); let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let state = StateProviderDatabase::new(state_provider); + let state = StateProviderDatabase::new(&state_provider); if ctx.attributes().no_tx_pool { - let db = State::builder().with_database(state).with_bundle_update().build(); - builder.build(db, ctx) + builder.build(state, &state_provider, ctx) } else { // sequencer mode we can reuse cachedreads from previous runs - let db = State::builder() - .with_database(cached_reads.as_db_mut(state)) - .with_bundle_update() - .build(); - builder.build(db, ctx) + builder.build(cached_reads.as_db_mut(state), &state_provider, ctx) } .map(|out| out.with_cached_reads(cached_reads)) } @@ -222,34 +178,29 @@ where .map_err(PayloadBuilderError::other)?; let config = PayloadConfig { parent_header: Arc::new(parent), attributes }; - let ctx: OpPayloadBuilderCtx = OpPayloadBuilderCtx { + let ctx = OpPayloadBuilderCtx { evm_config: self.evm_config.clone(), da_config: self.config.da_config.clone(), chain_spec: self.client.chain_spec(), config, cancel: Default::default(), best_payload: Default::default(), - receipt_builder: self.receipt_builder.clone(), }; let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let state = StateProviderDatabase::new(state_provider); - let mut state = State::builder().with_database(state).with_bundle_update().build(); let builder = OpBuilder::new(|_| NoopPayloadTransactions::::default()); - builder.witness(&mut state, &ctx) + builder.witness(state_provider, &ctx) } } /// Implementation of the [`PayloadBuilder`] trait for [`OpPayloadBuilder`]. -impl PayloadBuilder - for OpPayloadBuilder +impl PayloadBuilder for OpPayloadBuilder where Client: StateProviderFactory + ChainSpecProvider + Clone, N: OpPayloadPrimitives, Pool: TransactionPool>, - EvmConfig: - BlockExecutionStrategyFactory, + Evm: BlockExecutionStrategyFactory, Txs: OpPayloadTransactions, { type Attributes = OpPayloadBuilderAttributes; @@ -319,57 +270,42 @@ impl<'a, Txs> OpBuilder<'a, Txs> { } impl OpBuilder<'_, Txs> { - /// Executes the payload and returns the outcome. - pub fn execute( + /// Builds the payload on top of the state. + pub fn build( self, - state: &mut State, - ctx: &OpPayloadBuilderCtx, - ) -> Result>, PayloadBuilderError> + db: impl Database, + state_provider: impl StateProvider, + ctx: OpPayloadBuilderCtx, + ) -> Result>, PayloadBuilderError> where - N: OpPayloadPrimitives, - Txs: PayloadTransactions>, EvmConfig: BlockExecutionStrategyFactory< Primitives = N, NextBlockEnvCtx = OpNextBlockEnvAttributes, >, ChainSpec: EthChainSpec + OpHardforks, - DB: Database + AsRef

, - P: StorageRootProvider, + N: OpPayloadPrimitives, + Txs: PayloadTransactions>, { 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 = ctx - .evm_config - .strategy_for_next_block( - &mut *state, - ctx.parent(), - OpNextBlockEnvAttributes { - 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(), - extra_data: ctx.extra_data()?, - }, - ) - .map_err(PayloadBuilderError::other)?; + let mut db = State::builder().with_database(db).with_bundle_update().build(); - let block_env = strategy.evm_mut().block().clone(); + let mut builder = ctx.block_builder(&mut db)?; // 1. apply pre-execution changes - strategy.apply_pre_execution_changes().map_err(|err| { + builder.apply_pre_execution_changes().map_err(|err| { warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); PayloadBuilderError::Internal(err.into()) })?; // 2. execute sequencer transactions - let mut info = ctx.execute_sequencer_transactions(&mut strategy)?; + let mut info = ctx.execute_sequencer_transactions(&mut builder)?; // 3. if mem pool transactions are requested we execute them if !ctx.attributes().no_tx_pool { - 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() { + let best_txs = best(ctx.best_transaction_attributes(builder.evm_mut().block())); + if ctx.execute_best_transactions(&mut info, &mut builder, best_txs)?.is_some() { return Ok(BuildOutcomeKind::Cancelled) } @@ -380,138 +316,28 @@ impl OpBuilder<'_, Txs> { } } - let BlockExecutionResult { receipts, .. } = strategy - .apply_post_execution_changes() + let BlockBuilderOutcome { execution_result, hashed_state, trie_updates, block } = builder + .finish(state_provider) .map_err(|err| PayloadBuilderError::Internal(err.into()))?; - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call - state.merge_transitions(BundleRetention::Reverts); - - let withdrawals_root = if ctx.is_isthmus_active() { - // withdrawals root field in block header is used for storage root of L2 predeploy - // `l2tol1-message-passer` - Some(isthmus::withdrawals_root(&state.bundle_state, state.database.as_ref())?) - } else if ctx.is_canyon_active() { - Some(EMPTY_WITHDRAWALS) - } else { - None - }; - - let payload = ExecutedPayload { receipts, info, withdrawals_root, block_env }; - - Ok(BuildOutcomeKind::Better { payload }) - } - - /// Builds the payload on top of the state. - pub fn build( - self, - mut state: State, - ctx: OpPayloadBuilderCtx, - ) -> Result>, PayloadBuilderError> - where - EvmConfig: BlockExecutionStrategyFactory< - Primitives = N, - NextBlockEnvCtx = OpNextBlockEnvAttributes, - >, - ChainSpec: EthChainSpec + OpHardforks, - N: OpPayloadPrimitives, - Txs: PayloadTransactions>, - DB: Database + AsRef

, - P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, - { - 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 = block_env.number; - 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( - receipts, - &ctx.chain_spec, - ctx.attributes().timestamp(), - ) - }) - .expect("Number is in range"); - let logs_bloom = - execution_outcome.block_logs_bloom(block_number).expect("Number is in range"); - - // // calculate the state root - let state_provider = state.database.as_ref(); - let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); - let (state_root, trie_output) = { - state_provider.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { - warn!(target: "payload_builder", - parent_header=%ctx.parent().hash(), - %err, - "failed to calculate state root for payload" - ); - })? - }; - - // create the block header - let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); - - // OP doesn't support blobs/EIP-4844. - // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions - // Need [Some] or [None] based on hardfork to match block hash. - let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(); - let extra_data = ctx.extra_data()?; - - let header = Header { - parent_hash: ctx.parent().hash(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: block_env.beneficiary, - state_root, - transactions_root, - receipts_root, - withdrawals_root, - logs_bloom, - timestamp: ctx.attributes().payload_attributes.timestamp, - mix_hash: ctx.attributes().payload_attributes.prev_randao, - nonce: BEACON_NONCE.into(), - base_fee_per_gas: Some(block_env.basefee), - number: ctx.parent().number + 1, - gas_limit: block_env.gas_limit, - difficulty: U256::ZERO, - gas_used: info.cumulative_gas_used, - extra_data, - parent_beacon_block_root: ctx.attributes().payload_attributes.parent_beacon_block_root, - blob_gas_used, - excess_blob_gas, - requests_hash: None, - }; - - // seal the block - let block = N::Block::new( - header, - BlockBody { - transactions: info.executed_transactions, - ommers: vec![], - withdrawals: ctx.withdrawals().cloned(), - }, - ); - - let sealed_block = Arc::new(block.seal_slow()); + let sealed_block = Arc::new(block.sealed_block().clone()); debug!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header(), "sealed built block"); + let execution_outcome = ExecutionOutcome::new( + db.take_bundle(), + vec![execution_result.receipts], + block.number, + Vec::new(), + ); + // create the executed block data let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { block: ExecutedBlock { - recovered_block: Arc::new(RecoveredBlock::new_sealed( - sealed_block.as_ref().clone(), - info.executed_senders, - )), + recovered_block: Arc::new(block), execution_output: Arc::new(execution_outcome), hashed_state: Arc::new(hashed_state), }, - trie: Arc::new(trie_output), + trie: Arc::new(trie_updates), }; let no_tx_pool = ctx.attributes().no_tx_pool; @@ -530,26 +356,33 @@ impl OpBuilder<'_, Txs> { } /// Builds the payload and returns its [`ExecutionWitness`] based on the state after execution. - pub fn witness( + pub fn witness( self, - state: &mut State, - ctx: &OpPayloadBuilderCtx, + state_provider: impl StateProvider, + ctx: &OpPayloadBuilderCtx, ) -> Result where - EvmConfig: BlockExecutionStrategyFactory< + Evm: BlockExecutionStrategyFactory< Primitives = N, NextBlockEnvCtx = OpNextBlockEnvAttributes, >, ChainSpec: EthChainSpec + OpHardforks, N: OpPayloadPrimitives, Txs: PayloadTransactions>, - DB: Database + AsRef

, - P: StateProofProvider + StorageRootProvider, { - let _ = self.execute(state, ctx)?; + let mut db = State::builder() + .with_database(StateProviderDatabase::new(&state_provider)) + .with_bundle_update() + .build(); + let mut builder = ctx.block_builder(&mut db)?; + + builder.apply_pre_execution_changes().map_err(PayloadBuilderError::evm)?; + ctx.execute_sequencer_transactions(&mut builder)?; + builder.into_strategy().apply_post_execution_changes().map_err(PayloadBuilderError::evm)?; + let ExecutionWitnessRecord { hashed_state, codes, keys } = - ExecutionWitnessRecord::from_executed_state(state); - let state = state.database.as_ref().witness(Default::default(), hashed_state)?; + ExecutionWitnessRecord::from_executed_state(&db); + let state = state_provider.witness(Default::default(), hashed_state)?; Ok(ExecutionWitness { state: state.into_iter().collect(), codes, keys }) } } @@ -579,7 +412,7 @@ impl OpPayloadTransactions for () { #[derive(Debug)] pub struct ExecutedPayload { /// Tracked execution info - pub info: ExecutionInfo, + pub info: ExecutionInfo, /// Withdrawal hash. pub withdrawals_root: Option, /// The transaction receipts. @@ -590,11 +423,7 @@ pub struct ExecutedPayload { /// This acts as the container for executed transactions and its byproducts (receipts, gas used) #[derive(Default, Debug)] -pub struct ExecutionInfo { - /// All executed transactions (unrecovered). - pub executed_transactions: Vec, - /// The recovered senders for the executed transactions. - pub executed_senders: Vec

, +pub struct ExecutionInfo { /// All gas used so far pub cumulative_gas_used: u64, /// Estimated DA size @@ -603,16 +432,10 @@ pub struct ExecutionInfo { pub total_fees: U256, } -impl ExecutionInfo { +impl ExecutionInfo { /// Create a new instance with allocated slots. - pub fn with_capacity(capacity: usize) -> Self { - Self { - executed_transactions: Vec::with_capacity(capacity), - executed_senders: Vec::with_capacity(capacity), - cumulative_gas_used: 0, - cumulative_da_bytes_used: 0, - total_fees: U256::ZERO, - } + pub fn new() -> Self { + Self { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO } } /// Returns true if the transaction would exceed the block limits: @@ -623,7 +446,7 @@ impl ExecutionInfo { /// maximum allowed DA limit per block. pub fn is_tx_over_limits( &self, - tx: &N::SignedTx, + tx: &(impl Encodable + Transaction), block_gas_limit: u64, tx_data_limit: Option, block_data_limit: Option, @@ -643,30 +466,29 @@ impl ExecutionInfo { } /// Container type that holds all necessities to build a new payload. -#[derive(Debug)] -pub struct OpPayloadBuilderCtx { +#[derive(derive_more::Debug)] +pub struct OpPayloadBuilderCtx { /// The type that knows how to perform system calls and configure the evm. - pub evm_config: EvmConfig, + pub evm_config: Evm, /// The DA config for the payload builder pub da_config: OpDAConfig, /// The chainspec pub chain_spec: Arc, /// How to build the payload. - pub config: PayloadConfig>, + pub config: PayloadConfig>>, /// Marker to check whether the job has been cancelled. pub cancel: CancelOnDrop, /// The currently best payload. - pub best_payload: Option>, - /// Receipt builder. - pub receipt_builder: - Arc, Receipt = N::Receipt>>, + pub best_payload: Option>, } -impl OpPayloadBuilderCtx +impl OpPayloadBuilderCtx where - EvmConfig: ConfigureEvm, + Evm: BlockExecutionStrategyFactory< + Primitives: OpPayloadPrimitives, + NextBlockEnvCtx = OpNextBlockEnvAttributes, + >, ChainSpec: EthChainSpec + OpHardforks, - N: NodePrimitives, { /// Returns the parent block the payload will be build on. pub fn parent(&self) -> &SealedHeader { @@ -674,31 +496,10 @@ where } /// Returns the builder attributes. - pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { + pub const fn attributes(&self) -> &OpPayloadBuilderAttributes> { &self.config.attributes } - /// Returns the withdrawals if shanghai is active. - pub fn withdrawals(&self) -> Option<&Withdrawals> { - self.chain_spec - .is_shanghai_active_at_timestamp(self.attributes().timestamp()) - .then(|| &self.attributes().payload_attributes.withdrawals) - } - - /// Returns the blob fields for the header. - /// - /// This will always return `Some(0)` after ecotone. - pub fn blob_fields(&self) -> (Option, Option) { - // OP doesn't support blobs/EIP-4844. - // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions - // Need [Some] or [None] based on hardfork to match block hash. - if self.is_ecotone_active() { - (Some(0), Some(0)) - } else { - (None, None) - } - } - /// Returns the extra data for the block. /// /// After holocene this extracts the extra data from the payload @@ -729,54 +530,52 @@ where self.attributes().payload_id() } - /// Returns true if regolith is active for the payload. - pub fn is_regolith_active(&self) -> bool { - self.chain_spec.is_regolith_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if ecotone is active for the payload. - pub fn is_ecotone_active(&self) -> bool { - self.chain_spec.is_ecotone_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if canyon is active for the payload. - pub fn is_canyon_active(&self) -> bool { - self.chain_spec.is_canyon_active_at_timestamp(self.attributes().timestamp()) - } - /// Returns true if holocene is active for the payload. pub fn is_holocene_active(&self) -> bool { self.chain_spec.is_holocene_active_at_timestamp(self.attributes().timestamp()) } - /// Returns true if isthmus is active for the payload. - pub fn is_isthmus_active(&self) -> bool { - self.chain_spec.is_isthmus_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if interop is active for the payload. - pub fn is_interop_active(&self) -> bool { - self.chain_spec.is_interop_active_at_timestamp(self.attributes().timestamp()) - } - /// Returns true if the fees are higher than the previous payload. pub fn is_better_payload(&self, total_fees: U256) -> bool { is_better_payload(self.best_payload.as_ref(), total_fees) } + + /// Prepares a [`BlockBuilder`] for the next block. + pub fn block_builder<'a, DB: Database>( + &'a self, + db: &'a mut State, + ) -> Result + 'a, PayloadBuilderError> { + self.evm_config + .builder_for_next_block( + db, + self.parent(), + OpNextBlockEnvAttributes { + timestamp: self.attributes().timestamp(), + suggested_fee_recipient: self.attributes().suggested_fee_recipient(), + prev_randao: self.attributes().prev_randao(), + gas_limit: self.attributes().gas_limit.unwrap_or(self.parent().gas_limit), + parent_beacon_block_root: self.attributes().parent_beacon_block_root(), + extra_data: self.extra_data()?, + }, + ) + .map_err(PayloadBuilderError::other) + } } -impl OpPayloadBuilderCtx +impl OpPayloadBuilderCtx where - EvmConfig: ConfigureEvmFor, + Evm: BlockExecutionStrategyFactory< + Primitives: OpPayloadPrimitives, + NextBlockEnvCtx = OpNextBlockEnvAttributes, + >, ChainSpec: EthChainSpec + OpHardforks, - N: OpPayloadPrimitives, { /// Executes all sequencer transactions that are included in the payload attributes. pub fn execute_sequencer_transactions( &self, - strategy: &mut impl BlockExecutionStrategy, - ) -> Result, PayloadBuilderError> { - let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); + builder: &mut impl BlockBuilder, + ) -> Result { + let mut info = ExecutionInfo::new(); for sequencer_tx in &self.attributes().transactions { // A sequencer's block should never contain blob transactions. @@ -794,7 +593,7 @@ where PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) })?; - let gas_used = match strategy.execute_transaction(sequencer_tx.as_recovered_ref()) { + let gas_used = match builder.execute_transaction(sequencer_tx.clone()) { Ok(gas_used) => gas_used, Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { error, @@ -811,10 +610,6 @@ where // add gas used by the transaction to cumulative gas used, before creating the receipt info.cumulative_gas_used += gas_used; - - // append sender and transaction to the respective lists - info.executed_senders.push(sequencer_tx.signer()); - info.executed_transactions.push(sequencer_tx.into_tx()); } Ok(info) @@ -825,16 +620,16 @@ where /// Returns `Ok(Some(())` if the job was cancelled. pub fn execute_best_transactions( &self, - info: &mut ExecutionInfo, - strategy: &mut impl BlockExecutionStrategy, + info: &mut ExecutionInfo, + builder: &mut impl BlockBuilder, mut best_txs: impl PayloadTransactions< - Transaction: PoolTransaction, + Transaction: PoolTransaction>, >, ) -> Result, PayloadBuilderError> { - let block_gas_limit = strategy.evm_mut().block().gas_limit; + let block_gas_limit = builder.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 = strategy.evm_mut().block().basefee; + let base_fee = builder.evm_mut().block().basefee; while let Some(tx) = best_txs.next(()) { let tx = tx.into_consensus(); @@ -857,7 +652,7 @@ where return Ok(Some(())) } - let gas_used = match strategy.execute_transaction(tx.as_recovered_ref()) { + let gas_used = match builder.execute_transaction(tx.clone()) { Ok(gas_used) => gas_used, Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { error, @@ -890,10 +685,6 @@ where .effective_tip_per_gas(base_fee) .expect("fee is always valid; execution succeeded"); info.total_fees += U256::from(miner_fee) * U256::from(gas_used); - - // append sender and transaction to the respective lists - info.executed_senders.push(tx.signer()); - info.executed_transactions.push(tx.into_tx()); } Ok(None) diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index 7a78af7f22..cf0076799c 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -32,7 +32,6 @@ reth-rpc-engine-api.workspace = true # op-reth reth-optimism-chainspec.workspace = true -reth-optimism-consensus.workspace = true reth-optimism-evm.workspace = true reth-optimism-payload-builder.workspace = true reth-optimism-txpool.workspace = true diff --git a/crates/optimism/rpc/src/eth/pending_block.rs b/crates/optimism/rpc/src/eth/pending_block.rs index d24c9582ac..b29c53b6b5 100644 --- a/crates/optimism/rpc/src/eth/pending_block.rs +++ b/crates/optimism/rpc/src/eth/pending_block.rs @@ -1,23 +1,19 @@ //! Loads OP pending block for a RPC response. use crate::OpEthApi; -use alloy_consensus::{ - constants::EMPTY_WITHDRAWALS, proofs::calculate_transaction_root, transaction::Recovered, - BlockHeader, 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 reth_chainspec::{EthChainSpec, EthereumHardforks}; +use alloy_consensus::BlockHeader; +use alloy_eips::BlockNumberOrTag; +use alloy_primitives::B256; +use reth_chainspec::EthChainSpec; use reth_evm::execute::BlockExecutionStrategyFactory; use reth_node_api::NodePrimitives; -use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; use reth_optimism_evm::OpNextBlockEnvAttributes; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{OpBlock, OpReceipt, OpTransactionSigned}; -use reth_primitives::{logs_bloom, BlockBody, RecoveredBlock, SealedHeader}; +use reth_primitives::{RecoveredBlock, SealedHeader}; use reth_provider::{ - BlockExecutionResult, BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, - ProviderHeader, ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory, + BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderHeader, + ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory, }; use reth_rpc_eth_api::{ helpers::{LoadPendingBlock, SpawnBlocking}, @@ -26,7 +22,6 @@ use reth_rpc_eth_api::{ }; use reth_rpc_eth_types::{EthApiError, PendingBlock}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use revm::{context::BlockEnv, context_interface::Block}; impl LoadPendingBlock for OpEthApi where @@ -51,6 +46,7 @@ where SignedTx = ProviderTx, BlockHeader = ProviderHeader, Receipt = ProviderReceipt, + Block = ProviderBlock, >, NextBlockEnvCtx = OpNextBlockEnvAttributes, >, @@ -110,61 +106,4 @@ where Ok(Some((block, receipts))) } - - fn assemble_block( - &self, - block_env: &BlockEnv, - result: &BlockExecutionResult>, - parent: &SealedHeader>, - state_root: B256, - transactions: Vec>>, - ) -> 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(&result.receipts, &chain_spec, timestamp); - - 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(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: block_env.beneficiary, - state_root, - transactions_root, - receipts_root, - withdrawals_root: (is_shanghai).then_some(EMPTY_WITHDRAWALS), - logs_bloom, - timestamp, - mix_hash: block_env.prevrandao.unwrap_or_default(), - nonce: BEACON_NONCE.into(), - base_fee_per_gas: Some(block_env.basefee), - number: block_env.number, - gas_limit: block_env.gas_limit, - difficulty: U256::ZERO, - gas_used: result.gas_used, - blob_gas_used: is_cancun.then(|| { - transactions.iter().map(|tx| tx.blob_gas_used().unwrap_or_default()).sum::() - }), - excess_blob_gas: block_env.blob_excess_gas(), - extra_data: Default::default(), - parent_beacon_block_root: is_cancun.then_some(B256::ZERO), - requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), - }; - - // seal the block - reth_primitives::Block { - header, - body: BlockBody { - transactions: transactions.into_iter().map(|tx| tx.into_tx()).collect(), - ommers: vec![], - withdrawals: None, - }, - } - } } diff --git a/crates/optimism/rpc/src/witness.rs b/crates/optimism/rpc/src/witness.rs index 12fcaccb8a..a9d82d8afa 100644 --- a/crates/optimism/rpc/src/witness.rs +++ b/crates/optimism/rpc/src/witness.rs @@ -22,18 +22,16 @@ use std::{fmt::Debug, sync::Arc}; use tokio::sync::{oneshot, Semaphore}; /// An extension to the `debug_` namespace of the RPC API. -pub struct OpDebugWitnessApi { +pub struct OpDebugWitnessApi { inner: Arc>, } -impl - OpDebugWitnessApi -{ +impl OpDebugWitnessApi { /// Creates a new instance of the `OpDebugWitnessApi`. pub fn new( provider: Provider, task_spawner: Box, - builder: OpPayloadBuilder, + builder: OpPayloadBuilder, ) -> Self { let semaphore = Arc::new(Semaphore::new(3)); let inner = OpDebugWitnessApiInner { provider, builder, task_spawner, semaphore }; @@ -97,28 +95,20 @@ where } } -impl Clone for OpDebugWitnessApi -where - EvmConfig: ConfigureEvm, - Provider: NodePrimitivesProvider, -{ +impl Clone for OpDebugWitnessApi { fn clone(&self) -> Self { Self { inner: Arc::clone(&self.inner) } } } -impl Debug for OpDebugWitnessApi -where - EvmConfig: ConfigureEvm, - Provider: NodePrimitivesProvider, -{ +impl Debug for OpDebugWitnessApi { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("OpDebugWitnessApi").finish_non_exhaustive() } } -struct OpDebugWitnessApiInner { +struct OpDebugWitnessApiInner { provider: Provider, - builder: OpPayloadBuilder, + builder: OpPayloadBuilder, task_spawner: Box, semaphore: Arc, } diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 1e5f8cbd31..c2b3f29db5 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -160,16 +160,16 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA let ctx = this .evm_config() .context_for_next_block(&parent, this.next_env_attributes(&parent)?); - let (transactions, result, results) = if trace_transfers { + let (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); + let builder = this.evm_config().create_block_builder(evm, &parent, ctx); simulate::execute_transactions( - strategy, + builder, calls, validation, default_gas_limit, @@ -178,9 +178,9 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA )? } else { let evm = this.evm_config().evm_with_env(&mut db, evm_env); - let strategy = this.evm_config().create_strategy(evm, ctx); + let builder = this.evm_config().create_block_builder(evm, &parent, ctx); simulate::execute_transactions( - strategy, + builder, calls, validation, default_gas_limit, @@ -189,23 +189,11 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA )? }; - let senders = transactions.iter().map(|tx| tx.signer()).collect(); - - let block = this.assemble_block( - &block_env, - &result, - &parent, - // state root calculation is skipped for performance reasons - B256::ZERO, - transactions, - ); - let block = simulate::build_simulated_block( - senders, + result.block, results, return_full_transactions, this.tx_resp_builder(), - block, )?; parent = SealedHeader::new( 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 7a3a22bec3..f7cf2373d1 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -3,35 +3,30 @@ use super::SpawnBlocking; use crate::{types::RpcTypes, EthApiTypes, FromEthApiError, FromEvmError, RpcNodeCore}; -use alloy_consensus::{transaction::Recovered, BlockHeader, Transaction}; +use alloy_consensus::{BlockHeader, Transaction}; use alloy_eips::eip4844::MAX_DATA_GAS_PER_BLOCK; -use alloy_primitives::B256; use alloy_rpc_types_eth::BlockNumberOrTag; use futures::Future; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_errors::{BlockExecutionError, BlockValidationError, RethError}; use reth_evm::{ - execute::{BlockExecutionStrategy, BlockExecutionStrategyFactory}, + execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutionStrategyFactory}, ConfigureEvmEnv, Evm, }; use reth_node_api::NodePrimitives; use reth_primitives::{InvalidTransactionError, RecoveredBlock, SealedHeader}; use reth_primitives_traits::Receipt; use reth_provider::{ - BlockExecutionResult, BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, - ProviderError, ProviderHeader, ProviderReceipt, ProviderTx, ReceiptProvider, - StateProviderFactory, -}; -use reth_revm::{ - database::StateProviderDatabase, - db::{states::bundle_state::BundleRetention, State}, + BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderError, ProviderHeader, + ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory, }; +use reth_revm::{database::StateProviderDatabase, db::State}; use reth_rpc_eth_types::{EthApiError, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; use reth_transaction_pool::{ error::InvalidPoolTransactionError, BestTransactionsAttributes, PoolTransaction, TransactionPool, }; -use revm::{context::BlockEnv, context_interface::Block}; +use revm::context_interface::Block; use std::time::{Duration, Instant}; use tokio::sync::Mutex; use tracing::debug; @@ -55,6 +50,7 @@ pub trait LoadPendingBlock: BlockHeader = ProviderHeader, SignedTx = ProviderTx, Receipt = ProviderReceipt, + Block = ProviderBlock, >, >, > @@ -191,16 +187,6 @@ pub trait LoadPendingBlock: } } - /// Assembles a pending block. - fn assemble_block( - &self, - block_env: &BlockEnv, - result: &BlockExecutionResult>, - parent: &SealedHeader>, - state_root: B256, - transactions: Vec>>, - ) -> ProviderBlock; - /// Builds a pending block using the configured provider and pool. /// /// If the origin is the actual pending block, the block is built with withdrawals. @@ -222,24 +208,23 @@ pub trait LoadPendingBlock: .provider() .history_by_block_hash(parent.hash()) .map_err(Self::Error::from_eth_err)?; - let state = StateProviderDatabase::new(state_provider); + let state = StateProviderDatabase::new(&state_provider); let mut db = State::builder().with_database(state).with_bundle_update().build(); - let mut strategy = self + let mut builder = self .evm_config() - .strategy_for_next_block(&mut db, parent, self.next_env_attributes(parent)?) + .builder_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)?; + builder.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?; - let block_env = strategy.evm_mut().block().clone(); + let block_env = builder.evm_mut().block().clone(); let mut cumulative_gas_used = 0; let mut sum_blob_gas_used = 0; 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( block_env.basefee, @@ -297,7 +282,7 @@ pub trait LoadPendingBlock: } } - let gas_used = match strategy.execute_transaction(tx.as_recovered_ref()) { + let gas_used = match builder.execute_transaction(tx.clone()) { Ok(gas_used) => gas_used, Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { error, @@ -333,26 +318,11 @@ pub trait LoadPendingBlock: // 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); } - let result = strategy.apply_post_execution_changes().map_err(Self::Error::from_eth_err)?; + let BlockBuilderOutcome { execution_result, block, .. } = + builder.finish(&state_provider).map_err(Self::Error::from_eth_err)?; - // merge all transitions into bundle state. - db.merge_transitions(BundleRetention::PlainState); - - let bundle_state = db.take_bundle(); - let hashed_state = db.database.hashed_post_state(&bundle_state); - - // calculate the state root - let state_root = db.database.state_root(hashed_state).map_err(Self::Error::from_eth_err)?; - - let senders = executed_txs.iter().map(|tx| tx.signer()).collect(); - - let block = self.assemble_block(&block_env, &result, parent, state_root, executed_txs); - - Ok((RecoveredBlock::new_unhashed(block, senders), result.receipts)) + Ok((block, execution_result.receipts)) } } diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index d54525dc9f..954bdddbc6 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -7,12 +7,15 @@ use alloy_rpc_types_eth::{ Block, BlockTransactionsKind, Header, }; use jsonrpsee_types::ErrorObject; -use reth_evm::{execute::BlockExecutionStrategy, Evm}; -use reth_execution_types::BlockExecutionResult; -use reth_primitives::{NodePrimitives, Recovered, RecoveredBlock}; +use reth_evm::{ + execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutionStrategy}, + Evm, +}; +use reth_primitives::{Recovered, RecoveredBlock, TxTy}; 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}; +use reth_storage_api::noop::NoopProvider; use revm::{context_interface::result::ExecutionResult, Database}; use revm_primitives::{Address, Bytes, TxKind}; @@ -55,8 +58,8 @@ impl ToRpcError for EthSimulateError { /// /// Returns all executed transactions and the result of the execution. #[expect(clippy::type_complexity)] -pub fn execute_transactions( - mut strategy: S, +pub fn execute_transactions( + mut builder: S, calls: Vec, validation: bool, default_gas_limit: u64, @@ -64,22 +67,19 @@ pub fn execute_transactions( tx_resp_builder: &T, ) -> Result< ( - Vec>, - BlockExecutionResult, - Vec::HaltReason>>, + BlockBuilderOutcome, + Vec::Evm as Evm>::HaltReason>>, ), EthApiError, > where - N: NodePrimitives, - S: BlockExecutionStrategy, - EthApiError: From + From<<::DB as Database>::Error>, - S::Evm: Evm>>, - T: TransactionCompat, + S: BlockBuilder< + Strategy: BlockExecutionStrategy>>>, + >, + T: TransactionCompat>, { - strategy.apply_pre_execution_changes()?; + builder.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 @@ -89,19 +89,18 @@ where validation, default_gas_limit, chain_id, - strategy.evm_mut().db_mut(), + builder.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); + builder + .execute_transaction_with_result_closure(tx, |result| results.push(result.clone()))?; } - let result = strategy.apply_post_execution_changes()?; + // Pass noop provider to skip state root calculations. + let result = builder.finish(NoopProvider::default())?; - Ok((transactions, result, results)) + Ok((result, results)) } /// Goes over the list of [`TransactionRequest`]s and populates missing fields trying to resolve @@ -118,7 +117,7 @@ pub fn resolve_transaction>( tx_resp_builder: &T, ) -> Result, EthApiError> where - EthApiError: From, + DB::Error: Into, { if tx.buildable_type().is_none() && validation { return Err(EthApiError::TransactionConversionError); @@ -135,7 +134,8 @@ where }; if tx.nonce.is_none() { - tx.nonce = Some(db.basic(from)?.map(|acc| acc.nonce).unwrap_or_default()); + tx.nonce = + Some(db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default()); } if tx.gas.is_none() { @@ -174,11 +174,10 @@ where /// Handles outputs of the calls execution and builds a [`SimulatedBlock`]. #[expect(clippy::type_complexity)] pub fn build_simulated_block( - senders: Vec
, + block: RecoveredBlock, results: Vec>, full_transactions: bool, tx_resp_builder: &T, - block: B, ) -> Result>>, T::Error> where T: TransactionCompat, Error: FromEthApiError + FromEvmHalt>, @@ -241,8 +240,6 @@ where calls.push(call); } - let block = RecoveredBlock::new_unhashed(block, senders); - let txs_kind = if full_transactions { BlockTransactionsKind::Full } else { BlockTransactionsKind::Hashes }; diff --git a/crates/rpc/rpc/src/eth/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs index 64477018b8..b3389efc48 100644 --- a/crates/rpc/rpc/src/eth/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -1,19 +1,13 @@ //! Support for building a pending block with transactions from local view of mempool. -use alloy_consensus::{ - constants::EMPTY_WITHDRAWALS, transaction::Recovered, BlockHeader, Header, Transaction, - EMPTY_OMMER_ROOT_HASH, -}; -use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; -use alloy_primitives::U256; +use alloy_consensus::BlockHeader; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_evm::{execute::BlockExecutionStrategyFactory, NextBlockEnvAttributes}; use reth_node_api::NodePrimitives; -use reth_primitives::{logs_bloom, BlockBody, Receipt, SealedHeader}; -use reth_primitives_traits::proofs::calculate_transaction_root; +use reth_primitives::SealedHeader; use reth_provider::{ - BlockExecutionResult, BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, - ProviderHeader, ProviderReceipt, ProviderTx, StateProviderFactory, + BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderHeader, + ProviderReceipt, ProviderTx, StateProviderFactory, }; use reth_rpc_eth_api::{ helpers::{LoadPendingBlock, SpawnBlocking}, @@ -22,7 +16,6 @@ use reth_rpc_eth_api::{ }; use reth_rpc_eth_types::PendingBlock; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use revm::{context::BlockEnv, context_interface::Block}; use revm_primitives::B256; use crate::EthApi; @@ -46,9 +39,10 @@ where >, Evm: BlockExecutionStrategyFactory< Primitives: NodePrimitives< - BlockHeader = Header, + BlockHeader = ProviderHeader, SignedTx = ProviderTx, Receipt = ProviderReceipt, + Block = ProviderBlock, >, NextBlockEnvCtx = NextBlockEnvAttributes, >, @@ -77,61 +71,4 @@ where withdrawals: None, }) } - - fn assemble_block( - &self, - block_env: &BlockEnv, - result: &BlockExecutionResult>, - parent: &SealedHeader>, - state_root: revm_primitives::B256, - transactions: Vec>>, - ) -> 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(&result.receipts); - - 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); - let is_cancun = chain_spec.is_cancun_active_at_timestamp(timestamp); - let is_prague = chain_spec.is_prague_active_at_timestamp(timestamp); - - let header = Header { - parent_hash: parent.hash(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: block_env.beneficiary, - state_root, - transactions_root, - receipts_root, - withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), - logs_bloom, - timestamp: block_env.timestamp, - mix_hash: block_env.prevrandao.unwrap_or_default(), - nonce: BEACON_NONCE.into(), - base_fee_per_gas: Some(block_env.basefee), - number: block_env.number, - gas_limit: block_env.gas_limit, - difficulty: U256::ZERO, - gas_used: result.gas_used, - blob_gas_used: is_cancun.then(|| { - transactions.iter().map(|tx| tx.blob_gas_used().unwrap_or_default()).sum::() - }), - excess_blob_gas: block_env.blob_excess_gas(), - extra_data: Default::default(), - parent_beacon_block_root: is_cancun.then_some(B256::ZERO), - requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), - }; - - // seal the block - reth_primitives::Block { - header, - body: BlockBody { - transactions: transactions.into_iter().map(|tx| tx.into_tx()).collect(), - ommers: vec![], - withdrawals: None, - }, - } - } } diff --git a/examples/custom-beacon-withdrawals/src/main.rs b/examples/custom-beacon-withdrawals/src/main.rs index 646208109b..60422e1cb5 100644 --- a/examples/custom-beacon-withdrawals/src/main.rs +++ b/examples/custom-beacon-withdrawals/src/main.rs @@ -3,7 +3,7 @@ #![warn(unused_crate_dependencies)] -use alloy_eips::eip4895::{Withdrawal, Withdrawals}; +use alloy_eips::eip4895::Withdrawal; use alloy_sol_macro::sol; use alloy_sol_types::SolCall; use reth::{ @@ -18,20 +18,24 @@ use reth::{ DatabaseCommit, }, }; -use reth_chainspec::{ChainSpec, EthereumHardforks}; +use reth_chainspec::ChainSpec; use reth_evm::{ execute::{ BlockExecutionError, BlockExecutionStrategy, BlockExecutionStrategyFactory, InternalBlockExecutionError, }, - ConfigureEvmEnv, Database, Evm, EvmEnv, EvmFor, InspectorFor, NextBlockEnvAttributes, + ConfigureEvmEnv, Database, Evm, EvmEnv, EvmFor, FromRecoveredTx, InspectorFor, + NextBlockEnvAttributes, +}; +use reth_evm_ethereum::{ + execute::{EthBlockExecutionCtx, EthExecutionStrategy}, + EthBlockAssembler, EthEvmConfig, }; -use reth_evm_ethereum::EthEvmConfig; use reth_node_ethereum::{node::EthereumAddOns, BasicBlockExecutorProvider, EthereumNode}; use reth_primitives::{ EthPrimitives, Receipt, Recovered, SealedBlock, SealedHeader, TransactionSigned, }; -use std::{borrow::Cow, fmt::Display}; +use std::fmt::Display; pub const SYSTEM_ADDRESS: Address = address!("fffffffffffffffffffffffffffffffffffffffe"); pub const WITHDRAWALS_ADDRESS: Address = address!("4200000000000000000000000000000000000000"); @@ -114,26 +118,27 @@ impl ConfigureEvm for CustomEvmConfig { } } -pub struct CustomExecutionCtx<'a> { - withdrawals: Option>, -} - impl BlockExecutionStrategyFactory for CustomEvmConfig { type Primitives = EthPrimitives; - type ExecutionCtx<'a> = CustomExecutionCtx<'a>; + type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; type Strategy<'a, DB: Database + 'a, I: InspectorFor<&'a mut State, Self> + 'a> = CustomExecutorStrategy<'a, EvmFor, I>>; + type BlockAssembler = EthBlockAssembler; + + fn block_assembler(&self) -> &Self::BlockAssembler { + self.inner.block_assembler() + } fn context_for_block<'a>(&self, block: &'a SealedBlock) -> Self::ExecutionCtx<'a> { - CustomExecutionCtx { withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed) } + self.inner.context_for_block(block) } fn context_for_next_block( &self, - _parent: &SealedHeader, + parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, ) -> Self::ExecutionCtx<'_> { - CustomExecutionCtx { withdrawals: attributes.withdrawals.map(Cow::Owned) } + self.inner.context_for_next_block(parent, attributes) } fn create_strategy<'a, DB, I>( @@ -145,63 +150,51 @@ impl BlockExecutionStrategyFactory for CustomEvmConfig { DB: Database, I: InspectorFor<&'a mut State, Self> + 'a, { - CustomExecutorStrategy { - evm, - chain_spec: self.inner.chain_spec(), - withdrawals: ctx.withdrawals, - } + CustomExecutorStrategy { inner: self.inner.create_strategy(evm, ctx) } } } pub struct CustomExecutorStrategy<'a, Evm> { - /// Chainspec. - chain_spec: &'a ChainSpec, - /// EVM used for execution. - evm: Evm, - /// Block withdrawals. - withdrawals: Option>, + /// Inner Ethereum execution strategy. + inner: EthExecutionStrategy<'a, Evm>, } impl<'db, DB, E> BlockExecutionStrategy for CustomExecutorStrategy<'_, E> where DB: Database + 'db, - E: Evm>, + E: Evm, Tx: FromRecoveredTx>, { 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.evm.block().number); - self.evm.db_mut().set_state_clear_flag(state_clear_flag); - - Ok(()) + self.inner.apply_pre_execution_changes() } fn execute_transaction_with_result_closure( &mut self, - _tx: Recovered<&TransactionSigned>, - _f: impl FnOnce(&ExecutionResult<::HaltReason>), + tx: Recovered<&TransactionSigned>, + f: impl FnOnce(&ExecutionResult<::HaltReason>), ) -> Result { - Ok(0) + self.inner.execute_transaction_with_result_closure(tx, f) } - fn apply_post_execution_changes( - mut self, - ) -> Result, Self::Error> { - if let Some(withdrawals) = self.withdrawals { - apply_withdrawals_contract_call(withdrawals.as_ref(), &mut self.evm)?; + fn finish(mut self) -> Result<(Self::Evm, BlockExecutionResult), Self::Error> { + if let Some(withdrawals) = self.inner.ctx.withdrawals.clone() { + apply_withdrawals_contract_call(withdrawals.as_ref(), self.inner.evm_mut())?; } - Ok(Default::default()) + // Invoke inner finish method to apply Ethereum post-execution changes + self.inner.finish() } - fn with_state_hook(&mut self, _hook: Option>) {} + fn with_state_hook(&mut self, _hook: Option>) { + self.inner.with_state_hook(_hook) + } fn evm_mut(&mut self) -> &mut Self::Evm { - &mut self.evm + self.inner.evm_mut() } } diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 6d4a0c31d4..2eca675e29 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -46,13 +46,14 @@ use std::sync::OnceLock; #[non_exhaustive] pub struct MyEvmFactory; -impl EvmFactory for MyEvmFactory { +impl EvmFactory for MyEvmFactory { type Evm, EthInterpreter>> = EthEvm>>; type Tx = TxEnv; type Error = EVMError; type HaltReason = HaltReason; type Context = EthEvmContext; + type Spec = SpecId; fn create_evm(&self, db: DB, input: EvmEnv) -> Self::Evm { let evm = Context::mainnet() diff --git a/examples/stateful-precompile/src/main.rs b/examples/stateful-precompile/src/main.rs index 98bf94cb05..85886ef47f 100644 --- a/examples/stateful-precompile/src/main.rs +++ b/examples/stateful-precompile/src/main.rs @@ -60,12 +60,13 @@ pub struct MyEvmFactory { precompile_cache: Arc>, } -impl EvmFactory for MyEvmFactory { +impl EvmFactory for MyEvmFactory { type Evm, EthInterpreter>> = WrappedEthEvm; type Tx = TxEnv; type Error = EVMError; type HaltReason = HaltReason; type Context = EthEvmContext; + type Spec = SpecId; fn create_evm(&self, db: DB, input: EvmEnv) -> Self::Evm { let new_cache = self.precompile_cache.clone();