From 80eb0d0fb64cd1d875cf2a4d47b1f64026cb86d6 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 16 Jan 2026 17:07:19 +0000 Subject: [PATCH] refactor: use `BlockExecutionOutcome` in `ExecutedBlock` (#21123) --- crates/chain-state/src/in_memory.rs | 56 ++++++------ crates/chain-state/src/test_utils.rs | 24 +++--- crates/engine/tree/src/tree/mod.rs | 20 +++-- .../tree/src/tree/payload_processor/mod.rs | 12 +-- .../src/tree/payload_processor/prewarm.rs | 12 +-- .../engine/tree/src/tree/payload_validator.rs | 13 ++- crates/engine/tree/src/tree/tests.rs | 6 +- crates/evm/execution-types/src/execute.rs | 35 ++++++++ crates/optimism/flashblocks/src/worker.rs | 14 +-- crates/optimism/payload/src/builder.rs | 10 +-- crates/payload/primitives/src/traits.rs | 4 +- .../rpc-eth-api/src/helpers/pending_block.rs | 10 +-- crates/rpc/rpc-eth-types/src/pending_block.rs | 4 +- .../src/providers/blockchain_provider.rs | 30 +++++-- .../provider/src/providers/consistent.rs | 26 +++--- .../src/providers/database/provider.rs | 33 ++++--- .../src/providers/rocksdb/provider.rs | 4 +- .../src/providers/static_file/manager.rs | 4 +- .../storage/storage-api/src/state_writer.rs | 85 +++++++++++++++++-- 19 files changed, 264 insertions(+), 138 deletions(-) diff --git a/crates/chain-state/src/in_memory.rs b/crates/chain-state/src/in_memory.rs index 7ffd939c83..7f2f328b19 100644 --- a/crates/chain-state/src/in_memory.rs +++ b/crates/chain-state/src/in_memory.rs @@ -10,7 +10,7 @@ use alloy_primitives::{map::HashMap, BlockNumber, TxHash, B256}; use parking_lot::RwLock; use reth_chainspec::ChainInfo; use reth_ethereum_primitives::EthPrimitives; -use reth_execution_types::{Chain, ExecutionOutcome}; +use reth_execution_types::{BlockExecutionOutput, BlockExecutionResult, Chain, ExecutionOutcome}; use reth_metrics::{metrics::Gauge, Metrics}; use reth_primitives_traits::{ BlockBody as _, IndexedTx, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader, @@ -18,7 +18,7 @@ use reth_primitives_traits::{ }; use reth_storage_api::StateProviderBox; use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, TrieInputSorted}; -use std::{collections::BTreeMap, ops::Deref, sync::Arc, time::Instant}; +use std::{collections::BTreeMap, sync::Arc, time::Instant}; use tokio::sync::{broadcast, watch}; /// Size of the broadcast channel used to notify canonical state events. @@ -648,7 +648,7 @@ impl BlockState { } /// Returns the `Receipts` of executed block that determines the state. - pub fn receipts(&self) -> &Vec> { + pub fn receipts(&self) -> &Vec { &self.block.execution_outcome().receipts } @@ -659,15 +659,7 @@ impl BlockState { /// /// This clones the vector of receipts. To avoid it, use [`Self::executed_block_receipts_ref`]. pub fn executed_block_receipts(&self) -> Vec { - let receipts = self.receipts(); - - debug_assert!( - receipts.len() <= 1, - "Expected at most one block's worth of receipts, found {}", - receipts.len() - ); - - receipts.first().cloned().unwrap_or_default() + self.receipts().clone() } /// Returns a slice of `Receipt` of executed block that determines the state. @@ -675,15 +667,7 @@ impl BlockState { /// has only one element corresponding to the executed block associated to /// the state. pub fn executed_block_receipts_ref(&self) -> &[N::Receipt] { - let receipts = self.receipts(); - - debug_assert!( - receipts.len() <= 1, - "Expected at most one block's worth of receipts, found {}", - receipts.len() - ); - - receipts.first().map(|receipts| receipts.deref()).unwrap_or_default() + self.receipts() } /// Returns an iterator over __parent__ `BlockStates`. @@ -767,7 +751,7 @@ pub struct ExecutedBlock { /// Recovered Block pub recovered_block: Arc>, /// Block's execution outcome. - pub execution_output: Arc>, + pub execution_output: Arc>, /// Deferred trie data produced by execution. /// /// This allows deferring the computation of the trie data which can be expensive. @@ -779,7 +763,15 @@ impl Default for ExecutedBlock { fn default() -> Self { Self { recovered_block: Default::default(), - execution_output: Default::default(), + execution_output: Arc::new(BlockExecutionOutput { + result: BlockExecutionResult { + receipts: Default::default(), + requests: Default::default(), + gas_used: 0, + blob_gas_used: 0, + }, + state: Default::default(), + }), trie_data: DeferredTrieData::ready(ComputedTrieData::default()), } } @@ -800,7 +792,7 @@ impl ExecutedBlock { /// payload builders). This is the safe default path. pub fn new( recovered_block: Arc>, - execution_output: Arc>, + execution_output: Arc>, trie_data: ComputedTrieData, ) -> Self { Self { recovered_block, execution_output, trie_data: DeferredTrieData::ready(trie_data) } @@ -822,7 +814,7 @@ impl ExecutedBlock { /// Use [`Self::new()`] instead when trie data is already computed and available immediately. pub const fn with_deferred_trie_data( recovered_block: Arc>, - execution_output: Arc>, + execution_output: Arc>, trie_data: DeferredTrieData, ) -> Self { Self { recovered_block, execution_output, trie_data } @@ -842,7 +834,7 @@ impl ExecutedBlock { /// Returns a reference to the block's execution outcome #[inline] - pub fn execution_outcome(&self) -> &ExecutionOutcome { + pub fn execution_outcome(&self) -> &BlockExecutionOutput { &self.execution_output } @@ -958,14 +950,20 @@ impl> NewCanonicalChain { [first, rest @ ..] => { let mut chain = Chain::from_block( first.recovered_block().clone(), - first.execution_outcome().clone(), + ExecutionOutcome::from(( + first.execution_outcome().clone(), + first.block_number(), + )), first.trie_updates(), first.hashed_state(), ); for exec in rest { chain.append_block( exec.recovered_block().clone(), - exec.execution_outcome().clone(), + ExecutionOutcome::from(( + exec.execution_outcome().clone(), + exec.block_number(), + )), exec.trie_updates(), exec.hashed_state(), ); @@ -1264,7 +1262,7 @@ mod tests { let state = BlockState::new(block); - assert_eq!(state.receipts(), &receipts); + assert_eq!(state.receipts(), receipts.first().unwrap()); } #[test] diff --git a/crates/chain-state/src/test_utils.rs b/crates/chain-state/src/test_utils.rs index 2be8de2d78..73bad27d79 100644 --- a/crates/chain-state/src/test_utils.rs +++ b/crates/chain-state/src/test_utils.rs @@ -3,10 +3,7 @@ use crate::{ CanonStateSubscriptions, ComputedTrieData, }; use alloy_consensus::{Header, SignableTransaction, TxEip1559, TxReceipt, EMPTY_ROOT_HASH}; -use alloy_eips::{ - eip1559::{ETHEREUM_BLOCK_GAS_LIMIT_30M, INITIAL_BASE_FEE}, - eip7685::Requests, -}; +use alloy_eips::eip1559::{ETHEREUM_BLOCK_GAS_LIMIT_30M, INITIAL_BASE_FEE}; use alloy_primitives::{Address, BlockNumber, B256, U256}; use alloy_signer::SignerSync; use alloy_signer_local::PrivateKeySigner; @@ -16,7 +13,7 @@ use reth_chainspec::{ChainSpec, EthereumHardfork, MIN_TRANSACTION_GAS}; use reth_ethereum_primitives::{ Block, BlockBody, EthPrimitives, Receipt, Transaction, TransactionSigned, }; -use reth_execution_types::{Chain, ExecutionOutcome}; +use reth_execution_types::{BlockExecutionOutput, BlockExecutionResult, Chain, ExecutionOutcome}; use reth_primitives_traits::{ proofs::{calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root}, Account, NodePrimitives, Recovered, RecoveredBlock, SealedBlock, SealedHeader, @@ -201,7 +198,7 @@ impl TestBlockBuilder { fn get_executed_block( &mut self, block_number: BlockNumber, - receipts: Vec>, + mut receipts: Vec>, parent_hash: B256, ) -> ExecutedBlock { let block = self.generate_random_block(block_number, parent_hash); @@ -209,12 +206,15 @@ impl TestBlockBuilder { let trie_data = ComputedTrieData::default(); ExecutedBlock::new( Arc::new(RecoveredBlock::new_sealed(block, senders)), - Arc::new(ExecutionOutcome::new( - BundleState::default(), - receipts, - block_number, - vec![Requests::default()], - )), + Arc::new(BlockExecutionOutput { + result: BlockExecutionResult { + receipts: receipts.pop().unwrap_or_default(), + requests: Default::default(), + gas_used: 0, + blob_gas_used: 0, + }, + state: BundleState::default(), + }), trie_data, ) } diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index c0eb40d337..6796e098d1 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -30,9 +30,9 @@ use reth_payload_primitives::{ }; use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; use reth_provider::{ - BlockNumReader, BlockReader, ChangeSetReader, DatabaseProviderFactory, HashedPostStateProvider, - ProviderError, StageCheckpointReader, StateProviderBox, StateProviderFactory, StateReader, - TransactionVariant, + BlockExecutionOutput, BlockExecutionResult, BlockNumReader, BlockReader, ChangeSetReader, + DatabaseProviderFactory, HashedPostStateProvider, ProviderError, StageCheckpointReader, + StateProviderBox, StateProviderFactory, StateReader, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_stages_api::ControlFlow; @@ -1856,7 +1856,7 @@ where .sealed_block_with_senders(hash.into(), TransactionVariant::WithHash)? .ok_or_else(|| ProviderError::HeaderNotFound(hash.into()))? .split_sealed(); - let execution_output = self + let mut execution_output = self .provider .get_state(block.header().number())? .ok_or_else(|| ProviderError::StateForNumberNotFound(block.header().number()))?; @@ -1880,9 +1880,19 @@ where let trie_data = ComputedTrieData::without_trie_input(sorted_hashed_state, sorted_trie_updates); + let execution_output = Arc::new(BlockExecutionOutput { + state: execution_output.bundle, + result: BlockExecutionResult { + receipts: execution_output.receipts.pop().unwrap_or_default(), + requests: execution_output.requests.pop().unwrap_or_default(), + gas_used: block.gas_used(), + blob_gas_used: block.blob_gas_used().unwrap_or_default(), + }, + }); + Ok(Some(ExecutedBlock::new( Arc::new(RecoveredBlock::new_sealed(block, senders)), - Arc::new(execution_output), + execution_output, trie_data, ))) } diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 6ba285c3bc..7392411705 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -28,10 +28,10 @@ use reth_evm::{ ConfigureEvm, EvmEnvFor, ExecutableTxIterator, ExecutableTxTuple, OnStateHook, SpecFor, TxEnvFor, }; -use reth_execution_types::ExecutionOutcome; use reth_primitives_traits::NodePrimitives; use reth_provider::{ - BlockReader, DatabaseProviderROFactory, StateProvider, StateProviderFactory, StateReader, + BlockExecutionOutput, BlockReader, DatabaseProviderROFactory, StateProvider, + StateProviderFactory, StateReader, }; use reth_revm::{db::BundleState, state::EvmState}; use reth_trie::{hashed_cursor::HashedCursorFactory, trie_cursor::TrieCursorFactory}; @@ -665,12 +665,12 @@ impl PayloadHandle { /// Terminates the entire caching task. /// - /// If the [`ExecutionOutcome`] is provided it will update the shared cache using its + /// If the [`BlockExecutionOutput`] is provided it will update the shared cache using its /// bundle state. Using `Arc` allows sharing with the main execution /// path without cloning the expensive `BundleState`. pub(super) fn terminate_caching( &mut self, - execution_outcome: Option>>, + execution_outcome: Option>>, ) { self.prewarm_handle.terminate_caching(execution_outcome) } @@ -707,11 +707,11 @@ impl CacheTaskHandle { /// Terminates the entire pre-warming task. /// - /// If the [`ExecutionOutcome`] is provided it will update the shared cache using its + /// If the [`BlockExecutionOutput`] is provided it will update the shared cache using its /// bundle state. Using `Arc` avoids cloning the expensive `BundleState`. pub(super) fn terminate_caching( &mut self, - execution_outcome: Option>>, + execution_outcome: Option>>, ) { if let Some(tx) = self.to_prewarm_task.take() { let event = PrewarmTaskEvent::Terminate { execution_outcome }; diff --git a/crates/engine/tree/src/tree/payload_processor/prewarm.rs b/crates/engine/tree/src/tree/payload_processor/prewarm.rs index 835cffe9e3..99c689dd19 100644 --- a/crates/engine/tree/src/tree/payload_processor/prewarm.rs +++ b/crates/engine/tree/src/tree/payload_processor/prewarm.rs @@ -30,10 +30,12 @@ use alloy_primitives::{keccak256, map::B256Set, B256}; use crossbeam_channel::Sender as CrossbeamSender; use metrics::{Counter, Gauge, Histogram}; use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, SpecFor}; -use reth_execution_types::ExecutionOutcome; use reth_metrics::Metrics; use reth_primitives_traits::NodePrimitives; -use reth_provider::{AccountReader, BlockReader, StateProvider, StateProviderFactory, StateReader}; +use reth_provider::{ + AccountReader, BlockExecutionOutput, BlockReader, StateProvider, StateProviderFactory, + StateReader, +}; use reth_revm::{database::StateProviderDatabase, state::EvmState}; use reth_trie::MultiProofTargets; use std::{ @@ -259,7 +261,7 @@ where /// /// This method is called from `run()` only after all execution tasks are complete. #[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)] - fn save_cache(self, execution_outcome: Arc>) { + fn save_cache(self, execution_outcome: Arc>) { let start = Instant::now(); let Self { execution_cache, ctx: PrewarmContext { env, metrics, saved_cache, .. }, .. } = @@ -277,7 +279,7 @@ where // Insert state into cache while holding the lock // Access the BundleState through the shared ExecutionOutcome - if new_cache.cache().insert_state(execution_outcome.state()).is_err() { + if new_cache.cache().insert_state(&execution_outcome.state).is_err() { // Clear the cache on error to prevent having a polluted cache *cached = None; debug!(target: "engine::caching", "cleared execution cache on update error"); @@ -810,7 +812,7 @@ pub(super) enum PrewarmTaskEvent { Terminate { /// The final execution outcome. Using `Arc` allows sharing with the main execution /// path without cloning the expensive `BundleState`. - execution_outcome: Option>>, + execution_outcome: Option>>, }, /// The outcome of a pre-warm task Outcome { diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index 056b413e7a..2d2dbe1cb7 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -41,9 +41,9 @@ use reth_primitives_traits::{ }; use reth_provider::{ providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockNumReader, BlockReader, - ChangeSetReader, DatabaseProviderFactory, DatabaseProviderROFactory, ExecutionOutcome, - HashedPostStateProvider, ProviderError, PruneCheckpointReader, StageCheckpointReader, - StateProvider, StateProviderFactory, StateReader, + ChangeSetReader, DatabaseProviderFactory, DatabaseProviderROFactory, HashedPostStateProvider, + ProviderError, PruneCheckpointReader, StageCheckpointReader, StateProvider, + StateProviderFactory, StateReader, }; use reth_revm::db::State; use reth_trie::{ @@ -376,7 +376,6 @@ where } let parent_hash = input.parent_hash(); - let block_num_hash = input.num_hash(); trace!(target: "engine::tree::payload_validator", "Fetching block state provider"); let _enter = @@ -586,7 +585,7 @@ where // Create ExecutionOutcome and wrap in Arc for sharing with both the caching task // and the deferred trie task. This avoids cloning the expensive BundleState. - let execution_outcome = Arc::new(ExecutionOutcome::from((output, block_num_hash.number))); + let execution_outcome = Arc::new(output); // Terminate prewarming task with the shared execution outcome handle.terminate_caching(Some(Arc::clone(&execution_outcome))); @@ -1097,7 +1096,7 @@ where fn spawn_deferred_trie_task( &self, block: RecoveredBlock, - execution_outcome: Arc>, + execution_outcome: Arc>, ctx: &TreeCtx<'_, N>, hashed_state: HashedPostState, trie_output: TrieUpdates, @@ -1344,7 +1343,7 @@ where fn on_inserted_executed_block(&self, block: ExecutedBlock) { self.payload_processor.on_inserted_executed_block( block.recovered_block.block_with_parent(), - block.execution_output.state(), + &block.execution_output.state, ); } } diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index d5d1ef5adc..adfc62ef4b 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -27,7 +27,7 @@ use reth_ethereum_engine_primitives::EthEngineTypes; use reth_ethereum_primitives::{Block, EthPrimitives}; use reth_evm_ethereum::MockEvmConfig; use reth_primitives_traits::Block as _; -use reth_provider::{test_utils::MockEthProvider, ExecutionOutcome}; +use reth_provider::test_utils::MockEthProvider; use std::{ collections::BTreeMap, str::FromStr, @@ -838,7 +838,7 @@ fn test_tree_state_on_new_head_deep_fork() { for block in &chain_a { test_harness.tree.state.tree_state.insert_executed(ExecutedBlock::new( Arc::new(block.clone()), - Arc::new(ExecutionOutcome::default()), + Arc::new(BlockExecutionOutput::default()), empty_trie_data(), )); } @@ -847,7 +847,7 @@ fn test_tree_state_on_new_head_deep_fork() { for block in &chain_b { test_harness.tree.state.tree_state.insert_executed(ExecutedBlock::new( Arc::new(block.clone()), - Arc::new(ExecutionOutcome::default()), + Arc::new(BlockExecutionOutput::default()), empty_trie_data(), )); } diff --git a/crates/evm/execution-types/src/execute.rs b/crates/evm/execution-types/src/execute.rs index b014df0752..452ebae2b3 100644 --- a/crates/evm/execution-types/src/execute.rs +++ b/crates/evm/execution-types/src/execute.rs @@ -1,3 +1,5 @@ +use alloy_primitives::{Address, B256, U256}; +use reth_primitives_traits::{Account, Bytecode}; use revm::database::BundleState; pub use alloy_evm::block::BlockExecutionResult; @@ -23,3 +25,36 @@ pub struct BlockExecutionOutput { /// The changed state of the block after execution. pub state: BundleState, } + +impl BlockExecutionOutput { + /// Return bytecode if known. + pub fn bytecode(&self, code_hash: &B256) -> Option { + self.state.bytecode(code_hash).map(Bytecode) + } + + /// Get account if account is known. + pub fn account(&self, address: &Address) -> Option> { + self.state.account(address).map(|a| a.info.as_ref().map(Into::into)) + } + + /// Get storage if value is known. + /// + /// This means that depending on status we can potentially return `U256::ZERO`. + pub fn storage(&self, address: &Address, storage_key: U256) -> Option { + self.state.account(address).and_then(|a| a.storage_slot(storage_key)) + } +} + +impl Default for BlockExecutionOutput { + fn default() -> Self { + Self { + result: BlockExecutionResult { + receipts: Default::default(), + requests: Default::default(), + gas_used: 0, + blob_gas_used: 0, + }, + state: Default::default(), + } + } +} diff --git a/crates/optimism/flashblocks/src/worker.rs b/crates/optimism/flashblocks/src/worker.rs index 7d9ab860a5..e1b29c2729 100644 --- a/crates/optimism/flashblocks/src/worker.rs +++ b/crates/optimism/flashblocks/src/worker.rs @@ -8,10 +8,8 @@ use reth_evm::{ execute::{BlockBuilder, BlockBuilderOutcome}, ConfigureEvm, }; -use reth_execution_types::ExecutionOutcome; -use reth_primitives_traits::{ - AlloyBlockHeader, BlockTy, HeaderTy, NodePrimitives, ReceiptTy, Recovered, -}; +use reth_execution_types::BlockExecutionOutput; +use reth_primitives_traits::{BlockTy, HeaderTy, NodePrimitives, ReceiptTy, Recovered}; use reth_revm::{cached::CachedReads, database::StateProviderDatabase, db::State}; use reth_rpc_eth_types::{EthApiError, PendingBlock}; use reth_storage_api::{noop::NoopProvider, BlockReaderIdExt, StateProviderFactory}; @@ -112,12 +110,8 @@ where builder.finish(NoopProvider::default())? }; - let execution_outcome = ExecutionOutcome::new( - state.take_bundle(), - vec![execution_result.receipts], - block.number(), - vec![execution_result.requests], - ); + let execution_outcome = + BlockExecutionOutput { state: state.take_bundle(), result: execution_result }; let pending_block = PendingBlock::with_executed_block( Instant::now() + Duration::from_secs(1), diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 05d156ab3b..1cb766db09 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -18,7 +18,7 @@ use reth_evm::{ op_revm::{constants::L1_BLOCK_CONTRACT, L1BlockInfo}, ConfigureEvm, Database, }; -use reth_execution_types::ExecutionOutcome; +use reth_execution_types::BlockExecutionOutput; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{transaction::OpTransaction, L2_TO_L1_MESSAGE_PASSER_ADDRESS}; use reth_optimism_txpool::{ @@ -375,12 +375,8 @@ impl OpBuilder<'_, Txs> { 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(), - ); + let execution_outcome = + BlockExecutionOutput { state: db.take_bundle(), result: execution_result }; // create the executed block data let executed: BuiltPayloadExecutedBlock = BuiltPayloadExecutedBlock { diff --git a/crates/payload/primitives/src/traits.rs b/crates/payload/primitives/src/traits.rs index 726122743e..fa102c85e2 100644 --- a/crates/payload/primitives/src/traits.rs +++ b/crates/payload/primitives/src/traits.rs @@ -11,7 +11,7 @@ use alloy_rpc_types_engine::{PayloadAttributes as EthPayloadAttributes, PayloadI use core::fmt; use either::Either; use reth_chain_state::ComputedTrieData; -use reth_execution_types::ExecutionOutcome; +use reth_execution_types::BlockExecutionOutput; use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; use reth_trie_common::{ updates::{TrieUpdates, TrieUpdatesSorted}, @@ -27,7 +27,7 @@ pub struct BuiltPayloadExecutedBlock { /// Recovered Block pub recovered_block: Arc>, /// Block's execution outcome. - pub execution_output: Arc>, + pub execution_output: Arc>, /// Block's hashed state. /// /// Supports both unsorted and sorted variants so payload builders can avoid cloning in order 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 3fad2ae01f..dc6222f9df 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -12,7 +12,7 @@ use reth_chain_state::{BlockState, ComputedTrieData, ExecutedBlock}; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_errors::{BlockExecutionError, BlockValidationError, ProviderError, RethError}; use reth_evm::{ - execute::{BlockBuilder, BlockBuilderOutcome, ExecutionOutcome}, + execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutionOutput}, ConfigureEvm, Evm, NextBlockEnvAttributes, }; use reth_primitives_traits::{transaction::error::InvalidTransactionError, HeaderTy, SealedHeader}; @@ -363,12 +363,8 @@ pub trait LoadPendingBlock: let BlockBuilderOutcome { execution_result, block, hashed_state, trie_updates } = builder.finish(NoopProvider::default()).map_err(Self::Error::from_eth_err)?; - let execution_outcome = ExecutionOutcome::new( - db.take_bundle(), - vec![execution_result.receipts], - block.number(), - vec![execution_result.requests], - ); + let execution_outcome = + BlockExecutionOutput { state: db.take_bundle(), result: execution_result }; Ok(ExecutedBlock::new( block.into(), diff --git a/crates/rpc/rpc-eth-types/src/pending_block.rs b/crates/rpc/rpc-eth-types/src/pending_block.rs index 92a4ad6bde..0cc01eee15 100644 --- a/crates/rpc/rpc-eth-types/src/pending_block.rs +++ b/crates/rpc/rpc-eth-types/src/pending_block.rs @@ -99,9 +99,7 @@ impl PendingBlock { pub fn with_executed_block(expires_at: Instant, executed_block: ExecutedBlock) -> Self { Self { expires_at, - receipts: Arc::new( - executed_block.execution_output.receipts.iter().flatten().cloned().collect(), - ), + receipts: Arc::new(executed_block.execution_output.receipts.clone()), executed_block, } } diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index 0e290f4aec..58ec1e2557 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -790,7 +790,9 @@ mod tests { use reth_db_api::models::{AccountBeforeTx, StoredBlockBodyIndices}; use reth_errors::ProviderError; use reth_ethereum_primitives::{Block, Receipt}; - use reth_execution_types::{Chain, ExecutionOutcome}; + use reth_execution_types::{ + BlockExecutionOutput, BlockExecutionResult, Chain, ExecutionOutcome, + }; use reth_primitives_traits::{RecoveredBlock, SealedBlock, SignerRecoverable}; use reth_storage_api::{ BlockBodyIndicesProvider, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, @@ -909,8 +911,15 @@ mod tests { .map(|block| { let senders = block.senders().expect("failed to recover senders"); let block_receipts = receipts.get(block.number as usize).unwrap().clone(); - let execution_outcome = - ExecutionOutcome { receipts: vec![block_receipts], ..Default::default() }; + let execution_outcome = BlockExecutionOutput { + result: BlockExecutionResult { + receipts: block_receipts, + requests: Default::default(), + gas_used: 0, + blob_gas_used: 0, + }, + state: BundleState::default(), + }; ExecutedBlock { recovered_block: Arc::new(RecoveredBlock::new_sealed( @@ -979,8 +988,7 @@ mod tests { state.parent_state_chain().last().expect("qed").block(); let num_hash = lowest_memory_block.recovered_block().num_hash(); - let mut execution_output = (*lowest_memory_block.execution_output).clone(); - execution_output.first_block = lowest_memory_block.recovered_block().number; + let execution_output = (*lowest_memory_block.execution_output).clone(); lowest_memory_block.execution_output = Arc::new(execution_output); // Push to disk @@ -1708,8 +1716,8 @@ mod tests { block.clone(), senders, )), - execution_output: Arc::new(ExecutionOutcome { - bundle: BundleState::new( + execution_output: Arc::new(BlockExecutionOutput { + state: BundleState::new( in_memory_state.into_iter().map(|(address, (account, _))| { (address, None, Some(account.into()), Default::default()) }), @@ -1718,8 +1726,12 @@ mod tests { })], [], ), - first_block: first_in_memory_block, - ..Default::default() + result: BlockExecutionResult { + receipts: Default::default(), + requests: Default::default(), + gas_used: 0, + blob_gas_used: 0, + }, }), ..Default::default() } diff --git a/crates/storage/provider/src/providers/consistent.rs b/crates/storage/provider/src/providers/consistent.rs index b4eb5769c6..e0c503eae0 100644 --- a/crates/storage/provider/src/providers/consistent.rs +++ b/crates/storage/provider/src/providers/consistent.rs @@ -1307,7 +1307,7 @@ impl StorageChangeSetReader for ConsistentProvider { let changesets = state .block() .execution_output - .bundle + .state .reverts .clone() .to_plain_state_reverts() @@ -1360,7 +1360,7 @@ impl ChangeSetReader for ConsistentProvider { let changesets = state .block_ref() .execution_output - .bundle + .state .reverts .clone() .to_plain_state_reverts() @@ -1406,7 +1406,7 @@ impl ChangeSetReader for ConsistentProvider { let changeset = state .block_ref() .execution_output - .bundle + .state .reverts .clone() .to_plain_state_reverts() @@ -1460,7 +1460,7 @@ impl ChangeSetReader for ConsistentProvider { let block_changesets = state .block_ref() .execution_output - .bundle + .state .reverts .clone() .to_plain_state_reverts() @@ -1508,7 +1508,7 @@ impl ChangeSetReader for ConsistentProvider { count += state .block_ref() .execution_output - .bundle + .state .reverts .clone() .to_plain_state_reverts() @@ -1551,7 +1551,7 @@ impl StateReader for ConsistentProvider { ) -> ProviderResult>> { if let Some(state) = self.head_block.as_ref().and_then(|b| b.block_on_chain(block.into())) { let state = state.block_ref().execution_outcome().clone(); - Ok(Some(state)) + Ok(Some(ExecutionOutcome::from((state, block)))) } else { Self::get_state(self, block..=block) } @@ -1571,7 +1571,7 @@ mod tests { use reth_chain_state::{ExecutedBlock, NewCanonicalChain}; use reth_db_api::models::AccountBeforeTx; use reth_ethereum_primitives::Block; - use reth_execution_types::ExecutionOutcome; + use reth_execution_types::{BlockExecutionOutput, BlockExecutionResult, ExecutionOutcome}; use reth_primitives_traits::{RecoveredBlock, SealedBlock}; use reth_storage_api::{BlockReader, BlockSource, ChangeSetReader}; use reth_testing_utils::generators::{ @@ -1883,8 +1883,8 @@ mod tests { block.clone(), senders, )), - execution_output: Arc::new(ExecutionOutcome { - bundle: BundleState::new( + execution_output: Arc::new(BlockExecutionOutput { + state: BundleState::new( in_memory_state.into_iter().map(|(address, (account, _))| { (address, None, Some(account.into()), Default::default()) }), @@ -1893,8 +1893,12 @@ mod tests { })], [], ), - first_block: first_in_memory_block, - ..Default::default() + result: BlockExecutionResult { + receipts: Default::default(), + requests: Default::default(), + gas_used: 0, + blob_gas_used: 0, + }, }), ..Default::default() } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 8a5e04b3c7..3ec5bd28cb 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -47,7 +47,7 @@ use reth_db_api::{ transaction::{DbTx, DbTxMut}, BlockNumberList, PlainAccountState, PlainStorageState, }; -use reth_execution_types::{Chain, ExecutionOutcome}; +use reth_execution_types::{BlockExecutionOutput, BlockExecutionResult, Chain, ExecutionOutcome}; use reth_node_types::{BlockTy, BodyTy, HeaderTy, NodeTypes, ReceiptTy, TxTy}; use reth_primitives_traits::{ Account, Block as _, BlockBody as _, Bytecode, RecoveredBlock, SealedHeader, StorageEntry, @@ -60,7 +60,7 @@ use reth_static_file_types::StaticFileSegment; use reth_storage_api::{ BlockBodyIndicesProvider, BlockBodyReader, MetadataProvider, MetadataWriter, NodePrimitivesProvider, StateProvider, StateWriteConfig, StorageChangeSetReader, - StorageSettingsCache, TryIntoHistoricalStateProvider, + StorageSettingsCache, TryIntoHistoricalStateProvider, WriteStateInput, }; use reth_storage_errors::provider::{ProviderResult, StaticFileWriterError}; use reth_trie::{ @@ -537,7 +537,10 @@ impl DatabaseProvider StateWriter type Receipt = ReceiptTy; #[instrument(level = "debug", target = "providers::db", skip_all)] - fn write_state( + fn write_state<'a>( &self, - execution_outcome: &ExecutionOutcome, + execution_outcome: impl Into>, is_value_known: OriginalValuesKnown, config: StateWriteConfig, ) -> ProviderResult<()> { + let execution_outcome = execution_outcome.into(); let first_block = execution_outcome.first_block(); let (plain_state, reverts) = - execution_outcome.bundle.to_plain_state_and_reverts(is_value_known); + execution_outcome.state().to_plain_state_and_reverts(is_value_known); self.write_state_reverts(reverts, first_block, config)?; self.write_state_changes(plain_state)?; @@ -2101,7 +2105,7 @@ impl StateWriter } for (idx, (receipts, first_tx_index)) in - execution_outcome.receipts.iter().zip(block_indices).enumerate() + execution_outcome.receipts().zip(block_indices).enumerate() { let block_number = first_block + idx as u64; @@ -3130,12 +3134,15 @@ impl BlockWriter // Wrap block in ExecutedBlock with empty execution output (no receipts/state/trie) let executed_block = ExecutedBlock::new( Arc::new(block.clone()), - Arc::new(ExecutionOutcome::new( - Default::default(), - Vec::>>::new(), - block_number, - vec![], - )), + Arc::new(BlockExecutionOutput { + result: BlockExecutionResult { + receipts: Default::default(), + requests: Default::default(), + gas_used: 0, + blob_gas_used: 0, + }, + state: Default::default(), + }), ComputedTrieData::default(), ); diff --git a/crates/storage/provider/src/providers/rocksdb/provider.rs b/crates/storage/provider/src/providers/rocksdb/provider.rs index 88f09a9d35..626e73ad05 100644 --- a/crates/storage/provider/src/providers/rocksdb/provider.rs +++ b/crates/storage/provider/src/providers/rocksdb/provider.rs @@ -587,7 +587,7 @@ impl RocksDBProvider { let mut account_history: BTreeMap> = BTreeMap::new(); for (block_idx, block) in blocks.iter().enumerate() { let block_number = ctx.first_block_number + block_idx as u64; - let bundle = &block.execution_outcome().bundle; + let bundle = &block.execution_outcome().state; for &address in bundle.state().keys() { account_history.entry(address).or_default().push(block_number); } @@ -612,7 +612,7 @@ impl RocksDBProvider { let mut storage_history: BTreeMap<(Address, B256), Vec> = BTreeMap::new(); for (block_idx, block) in blocks.iter().enumerate() { let block_number = ctx.first_block_number + block_idx as u64; - let bundle = &block.execution_outcome().bundle; + let bundle = &block.execution_outcome().state; for (&address, account) in bundle.state() { for &slot in account.storage.keys() { let key = B256::new(slot.to_be_bytes()); diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 718283114f..b835a4491d 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -594,7 +594,7 @@ impl StaticFileProvider { continue } - for (i, receipt) in block.execution_outcome().receipts.iter().flatten().enumerate() { + for (i, receipt) in block.execution_outcome().receipts.iter().enumerate() { w.append_receipt(first_tx + i as u64, receipt)?; } } @@ -609,7 +609,7 @@ impl StaticFileProvider { ) -> ProviderResult<()> { for block in blocks { let block_number = block.recovered_block().number(); - let reverts = block.execution_outcome().bundle.reverts.to_plain_state_reverts(); + let reverts = block.execution_outcome().state.reverts.to_plain_state_reverts(); for account_block_reverts in reverts.accounts { let changeset = account_block_reverts diff --git a/crates/storage/storage-api/src/state_writer.rs b/crates/storage/storage-api/src/state_writer.rs index 3daab1a85a..f2c193559b 100644 --- a/crates/storage/storage-api/src/state_writer.rs +++ b/crates/storage/storage-api/src/state_writer.rs @@ -1,23 +1,98 @@ +use alloc::vec::Vec; +use alloy_consensus::transaction::Either; use alloy_primitives::BlockNumber; -use reth_execution_types::ExecutionOutcome; +use reth_execution_types::{BlockExecutionOutput, ExecutionOutcome}; use reth_storage_errors::provider::ProviderResult; use reth_trie_common::HashedPostStateSorted; use revm_database::{ states::{PlainStateReverts, StateChangeset}, - OriginalValuesKnown, + BundleState, OriginalValuesKnown, }; +/// A helper type used as input to [`StateWriter`] for writing execution outcome for one or many +/// blocks. +#[derive(Debug)] +pub enum WriteStateInput<'a, R> { + /// A single block execution outcome. + Single { + /// The execution outcome. + outcome: &'a BlockExecutionOutput, + /// Block number + block: BlockNumber, + }, + /// Multiple block execution outcomes. + Multiple(&'a ExecutionOutcome), +} + +impl<'a, R> WriteStateInput<'a, R> { + /// Number of blocks in the execution outcome. + pub const fn len(&self) -> usize { + match self { + Self::Single { .. } => 1, + Self::Multiple(outcome) => outcome.len(), + } + } + + /// Returns true if the execution outcome is empty. + pub const fn is_empty(&self) -> bool { + match self { + Self::Single { outcome, .. } => outcome.result.receipts.is_empty(), + Self::Multiple(outcome) => outcome.is_empty(), + } + } + + /// Number of the first block. + pub const fn first_block(&self) -> BlockNumber { + match self { + Self::Single { block, .. } => *block, + Self::Multiple(outcome) => outcome.first_block(), + } + } + + /// Number of the last block. + pub const fn last_block(&self) -> BlockNumber { + match self { + Self::Single { block, .. } => *block, + Self::Multiple(outcome) => outcome.last_block(), + } + } + + /// Returns a reference to the [`BundleState`]. + pub const fn state(&self) -> &BundleState { + match self { + Self::Single { outcome, .. } => &outcome.state, + Self::Multiple(outcome) => &outcome.bundle, + } + } + + /// Returns an iterator over receipt sets for each block. + pub fn receipts(&self) -> impl Iterator> { + match self { + Self::Single { outcome, .. } => { + Either::Left(core::iter::once(&outcome.result.receipts)) + } + Self::Multiple(outcome) => Either::Right(outcome.receipts.iter()), + } + } +} + +impl<'a, R> From<&'a ExecutionOutcome> for WriteStateInput<'a, R> { + fn from(outcome: &'a ExecutionOutcome) -> Self { + Self::Multiple(outcome) + } +} + /// A trait specifically for writing state changes or reverts pub trait StateWriter { /// Receipt type included into [`ExecutionOutcome`]. - type Receipt; + type Receipt: 'static; /// Write the state and optionally receipts to the database. /// /// Use `config` to skip writing certain data types when they are written elsewhere. - fn write_state( + fn write_state<'a>( &self, - execution_outcome: &ExecutionOutcome, + execution_outcome: impl Into>, is_value_known: OriginalValuesKnown, config: StateWriteConfig, ) -> ProviderResult<()>;