refactor: use BlockExecutionOutcome in ExecutedBlock (#21123)

This commit is contained in:
Arsenii Kulikov
2026-01-16 17:07:19 +00:00
committed by GitHub
parent 5e178f6ac6
commit 80eb0d0fb6
19 changed files with 264 additions and 138 deletions

View File

@@ -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<N: NodePrimitives> BlockState<N> {
}
/// Returns the `Receipts` of executed block that determines the state.
pub fn receipts(&self) -> &Vec<Vec<N::Receipt>> {
pub fn receipts(&self) -> &Vec<N::Receipt> {
&self.block.execution_outcome().receipts
}
@@ -659,15 +659,7 @@ impl<N: NodePrimitives> BlockState<N> {
///
/// This clones the vector of receipts. To avoid it, use [`Self::executed_block_receipts_ref`].
pub fn executed_block_receipts(&self) -> Vec<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().cloned().unwrap_or_default()
self.receipts().clone()
}
/// Returns a slice of `Receipt` of executed block that determines the state.
@@ -675,15 +667,7 @@ impl<N: NodePrimitives> BlockState<N> {
/// 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<N: NodePrimitives = EthPrimitives> {
/// Recovered Block
pub recovered_block: Arc<RecoveredBlock<N::Block>>,
/// Block's execution outcome.
pub execution_output: Arc<ExecutionOutcome<N::Receipt>>,
pub execution_output: Arc<BlockExecutionOutput<N::Receipt>>,
/// Deferred trie data produced by execution.
///
/// This allows deferring the computation of the trie data which can be expensive.
@@ -779,7 +763,15 @@ impl<N: NodePrimitives> Default for ExecutedBlock<N> {
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<N: NodePrimitives> ExecutedBlock<N> {
/// payload builders). This is the safe default path.
pub fn new(
recovered_block: Arc<RecoveredBlock<N::Block>>,
execution_output: Arc<ExecutionOutcome<N::Receipt>>,
execution_output: Arc<BlockExecutionOutput<N::Receipt>>,
trie_data: ComputedTrieData,
) -> Self {
Self { recovered_block, execution_output, trie_data: DeferredTrieData::ready(trie_data) }
@@ -822,7 +814,7 @@ impl<N: NodePrimitives> ExecutedBlock<N> {
/// Use [`Self::new()`] instead when trie data is already computed and available immediately.
pub const fn with_deferred_trie_data(
recovered_block: Arc<RecoveredBlock<N::Block>>,
execution_output: Arc<ExecutionOutcome<N::Receipt>>,
execution_output: Arc<BlockExecutionOutput<N::Receipt>>,
trie_data: DeferredTrieData,
) -> Self {
Self { recovered_block, execution_output, trie_data }
@@ -842,7 +834,7 @@ impl<N: NodePrimitives> ExecutedBlock<N> {
/// Returns a reference to the block's execution outcome
#[inline]
pub fn execution_outcome(&self) -> &ExecutionOutcome<N::Receipt> {
pub fn execution_outcome(&self) -> &BlockExecutionOutput<N::Receipt> {
&self.execution_output
}
@@ -958,14 +950,20 @@ impl<N: NodePrimitives<SignedTx: SignedTransaction>> NewCanonicalChain<N> {
[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]

View File

@@ -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<N: NodePrimitives> TestBlockBuilder<N> {
fn get_executed_block(
&mut self,
block_number: BlockNumber,
receipts: Vec<Vec<Receipt>>,
mut receipts: Vec<Vec<Receipt>>,
parent_hash: B256,
) -> ExecutedBlock {
let block = self.generate_random_block(block_number, parent_hash);
@@ -209,12 +206,15 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
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,
)
}

View File

@@ -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,
)))
}

View File

@@ -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<Tx, Err, R: Send + Sync + 'static> PayloadHandle<Tx, Err, R> {
/// 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<ExecutionOutcome>` allows sharing with the main execution
/// path without cloning the expensive `BundleState`.
pub(super) fn terminate_caching(
&mut self,
execution_outcome: Option<Arc<ExecutionOutcome<R>>>,
execution_outcome: Option<Arc<BlockExecutionOutput<R>>>,
) {
self.prewarm_handle.terminate_caching(execution_outcome)
}
@@ -707,11 +707,11 @@ impl<R: Send + Sync + 'static> CacheTaskHandle<R> {
/// 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<ExecutionOutcome>` avoids cloning the expensive `BundleState`.
pub(super) fn terminate_caching(
&mut self,
execution_outcome: Option<Arc<ExecutionOutcome<R>>>,
execution_outcome: Option<Arc<BlockExecutionOutput<R>>>,
) {
if let Some(tx) = self.to_prewarm_task.take() {
let event = PrewarmTaskEvent::Terminate { execution_outcome };

View File

@@ -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<ExecutionOutcome<N::Receipt>>) {
fn save_cache(self, execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>) {
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<R> {
Terminate {
/// The final execution outcome. Using `Arc` allows sharing with the main execution
/// path without cloning the expensive `BundleState`.
execution_outcome: Option<Arc<ExecutionOutcome<R>>>,
execution_outcome: Option<Arc<BlockExecutionOutput<R>>>,
},
/// The outcome of a pre-warm task
Outcome {

View File

@@ -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<N::Block>,
execution_outcome: Arc<ExecutionOutcome<N::Receipt>>,
execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
ctx: &TreeCtx<'_, N>,
hashed_state: HashedPostState,
trie_output: TrieUpdates,
@@ -1344,7 +1343,7 @@ where
fn on_inserted_executed_block(&self, block: ExecutedBlock<N>) {
self.payload_processor.on_inserted_executed_block(
block.recovered_block.block_with_parent(),
block.execution_output.state(),
&block.execution_output.state,
);
}
}

View File

@@ -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(),
));
}

View File

@@ -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<T> {
/// The changed state of the block after execution.
pub state: BundleState,
}
impl<T> BlockExecutionOutput<T> {
/// Return bytecode if known.
pub fn bytecode(&self, code_hash: &B256) -> Option<Bytecode> {
self.state.bytecode(code_hash).map(Bytecode)
}
/// Get account if account is known.
pub fn account(&self, address: &Address) -> Option<Option<Account>> {
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<U256> {
self.state.account(address).and_then(|a| a.storage_slot(storage_key))
}
}
impl<T> Default for BlockExecutionOutput<T> {
fn default() -> Self {
Self {
result: BlockExecutionResult {
receipts: Default::default(),
requests: Default::default(),
gas_used: 0,
blob_gas_used: 0,
},
state: Default::default(),
}
}
}

View File

@@ -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),

View File

@@ -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<Txs> 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<N> = BuiltPayloadExecutedBlock {

View File

@@ -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<N: NodePrimitives> {
/// Recovered Block
pub recovered_block: Arc<RecoveredBlock<N::Block>>,
/// Block's execution outcome.
pub execution_output: Arc<ExecutionOutcome<N::Receipt>>,
pub execution_output: Arc<BlockExecutionOutput<N::Receipt>>,
/// Block's hashed state.
///
/// Supports both unsorted and sorted variants so payload builders can avoid cloning in order

View File

@@ -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(),

View File

@@ -99,9 +99,7 @@ impl<N: NodePrimitives> PendingBlock<N> {
pub fn with_executed_block(expires_at: Instant, executed_block: ExecutedBlock<N>) -> 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,
}
}

View File

@@ -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()
}

View File

@@ -1307,7 +1307,7 @@ impl<N: ProviderNodeTypes> StorageChangeSetReader for ConsistentProvider<N> {
let changesets = state
.block()
.execution_output
.bundle
.state
.reverts
.clone()
.to_plain_state_reverts()
@@ -1360,7 +1360,7 @@ impl<N: ProviderNodeTypes> ChangeSetReader for ConsistentProvider<N> {
let changesets = state
.block_ref()
.execution_output
.bundle
.state
.reverts
.clone()
.to_plain_state_reverts()
@@ -1406,7 +1406,7 @@ impl<N: ProviderNodeTypes> ChangeSetReader for ConsistentProvider<N> {
let changeset = state
.block_ref()
.execution_output
.bundle
.state
.reverts
.clone()
.to_plain_state_reverts()
@@ -1460,7 +1460,7 @@ impl<N: ProviderNodeTypes> ChangeSetReader for ConsistentProvider<N> {
let block_changesets = state
.block_ref()
.execution_output
.bundle
.state
.reverts
.clone()
.to_plain_state_reverts()
@@ -1508,7 +1508,7 @@ impl<N: ProviderNodeTypes> ChangeSetReader for ConsistentProvider<N> {
count += state
.block_ref()
.execution_output
.bundle
.state
.reverts
.clone()
.to_plain_state_reverts()
@@ -1551,7 +1551,7 @@ impl<N: ProviderNodeTypes> StateReader for ConsistentProvider<N> {
) -> ProviderResult<Option<ExecutionOutcome<Self::Receipt>>> {
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()
}

View File

@@ -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<TX: DbTx + DbTxMut + 'static, N: NodeTypesForProvider> DatabaseProvider<TX,
// Skip receipts/account changesets if they're being written to static files.
let start = Instant::now();
self.write_state(
execution_output,
WriteStateInput::Single {
outcome: execution_output,
block: recovered_block.number(),
},
OriginalValuesKnown::No,
StateWriteConfig {
write_receipts: !sf_ctx.write_receipts,
@@ -2037,16 +2040,17 @@ impl<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider> StateWriter
type Receipt = ReceiptTy<N>;
#[instrument(level = "debug", target = "providers::db", skip_all)]
fn write_state(
fn write_state<'a>(
&self,
execution_outcome: &ExecutionOutcome<Self::Receipt>,
execution_outcome: impl Into<WriteStateInput<'a, Self::Receipt>>,
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<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider> 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<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider> 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::<Vec<ReceiptTy<N>>>::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(),
);

View File

@@ -587,7 +587,7 @@ impl RocksDBProvider {
let mut account_history: BTreeMap<Address, Vec<u64>> = 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<u64>> = 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());

View File

@@ -594,7 +594,7 @@ impl<N: NodePrimitives> StaticFileProvider<N> {
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<N: NodePrimitives> StaticFileProvider<N> {
) -> 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

View File

@@ -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<R>,
/// Block number
block: BlockNumber,
},
/// Multiple block execution outcomes.
Multiple(&'a ExecutionOutcome<R>),
}
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<Item = &Vec<R>> {
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<R>> for WriteStateInput<'a, R> {
fn from(outcome: &'a ExecutionOutcome<R>) -> 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<Self::Receipt>,
execution_outcome: impl Into<WriteStateInput<'a, Self::Receipt>>,
is_value_known: OriginalValuesKnown,
config: StateWriteConfig,
) -> ProviderResult<()>;