Skip to content

Commit e58aa09

Browse files
authored
perf(engine): return sorted data from compute_trie_input (#19340)
1 parent a72c1da commit e58aa09

File tree

17 files changed

+501
-139
lines changed

17 files changed

+501
-139
lines changed

crates/chain-state/src/in_memory.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use reth_primitives_traits::{
1717
SignedTransaction,
1818
};
1919
use reth_storage_api::StateProviderBox;
20-
use reth_trie::{updates::TrieUpdates, HashedPostState};
20+
use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted};
2121
use std::{collections::BTreeMap, sync::Arc, time::Instant};
2222
use tokio::sync::{broadcast, watch};
2323

@@ -725,10 +725,10 @@ pub struct ExecutedBlock<N: NodePrimitives = EthPrimitives> {
725725
pub recovered_block: Arc<RecoveredBlock<N::Block>>,
726726
/// Block's execution outcome.
727727
pub execution_output: Arc<ExecutionOutcome<N::Receipt>>,
728-
/// Block's hashed state.
729-
pub hashed_state: Arc<HashedPostState>,
730-
/// Trie updates that result from calculating the state root for the block.
731-
pub trie_updates: Arc<TrieUpdates>,
728+
/// Block's sorted hashed state.
729+
pub hashed_state: Arc<HashedPostStateSorted>,
730+
/// Sorted trie updates that result from calculating the state root for the block.
731+
pub trie_updates: Arc<TrieUpdatesSorted>,
732732
}
733733

734734
impl<N: NodePrimitives> Default for ExecutedBlock<N> {
@@ -763,13 +763,13 @@ impl<N: NodePrimitives> ExecutedBlock<N> {
763763

764764
/// Returns a reference to the hashed state result of the execution outcome
765765
#[inline]
766-
pub fn hashed_state(&self) -> &HashedPostState {
766+
pub fn hashed_state(&self) -> &HashedPostStateSorted {
767767
&self.hashed_state
768768
}
769769

770770
/// Returns a reference to the trie updates resulting from the execution outcome
771771
#[inline]
772-
pub fn trie_updates(&self) -> &TrieUpdates {
772+
pub fn trie_updates(&self) -> &TrieUpdatesSorted {
773773
&self.trie_updates
774774
}
775775

@@ -875,8 +875,8 @@ mod tests {
875875
StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider,
876876
};
877877
use reth_trie::{
878-
AccountProof, HashedStorage, MultiProof, MultiProofTargets, StorageMultiProof,
879-
StorageProof, TrieInput,
878+
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
879+
MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
880880
};
881881

882882
fn create_mock_state(

crates/chain-state/src/memory_overlay.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
5353
/// Return lazy-loaded trie state aggregated from in-memory blocks.
5454
fn trie_input(&self) -> &TrieInput {
5555
self.trie_input.get_or_init(|| {
56-
TrieInput::from_blocks(
56+
TrieInput::from_blocks_sorted(
5757
self.in_memory
5858
.iter()
5959
.rev()

crates/chain-state/src/test_utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use reth_primitives_traits::{
2323
SignedTransaction,
2424
};
2525
use reth_storage_api::NodePrimitivesProvider;
26-
use reth_trie::{root::state_root_unhashed, updates::TrieUpdates, HashedPostState};
26+
use reth_trie::{root::state_root_unhashed, updates::TrieUpdatesSorted, HashedPostStateSorted};
2727
use revm_database::BundleState;
2828
use revm_state::AccountInfo;
2929
use std::{
@@ -216,8 +216,8 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
216216
block_number,
217217
vec![Requests::default()],
218218
)),
219-
hashed_state: Arc::new(HashedPostState::default()),
220-
trie_updates: Arc::new(TrieUpdates::default()),
219+
hashed_state: Arc::new(HashedPostStateSorted::default()),
220+
trie_updates: Arc::new(TrieUpdatesSorted::default()),
221221
}
222222
}
223223

crates/engine/tree/src/tree/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,8 +1794,8 @@ where
17941794
Ok(Some(ExecutedBlock {
17951795
recovered_block: Arc::new(RecoveredBlock::new_sealed(block, senders)),
17961796
execution_output: Arc::new(execution_output),
1797-
hashed_state: Arc::new(hashed_state),
1798-
trie_updates: Arc::new(trie_updates.into()),
1797+
hashed_state: Arc::new(hashed_state.into_sorted()),
1798+
trie_updates: Arc::new(trie_updates),
17991799
}))
18001800
}
18011801

crates/engine/tree/src/tree/payload_processor/multiproof.rs

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ use metrics::{Gauge, Histogram};
1313
use reth_metrics::Metrics;
1414
use reth_revm::state::EvmState;
1515
use reth_trie::{
16-
added_removed_keys::MultiAddedRemovedKeys, prefix_set::TriePrefixSetsMut,
17-
updates::TrieUpdatesSorted, DecodedMultiProof, HashedPostState, HashedPostStateSorted,
18-
HashedStorage, MultiProofTargets, TrieInput,
16+
added_removed_keys::MultiAddedRemovedKeys, DecodedMultiProof, HashedPostState, HashedStorage,
17+
MultiProofTargets,
1918
};
2019
use reth_trie_parallel::{
2120
proof::ParallelProof,
@@ -60,35 +59,6 @@ impl SparseTrieUpdate {
6059
}
6160
}
6261

63-
/// Common configuration for multi proof tasks
64-
#[derive(Debug, Clone, Default)]
65-
pub(crate) struct MultiProofConfig {
66-
/// The sorted collection of cached in-memory intermediate trie nodes that
67-
/// can be reused for computation.
68-
pub nodes_sorted: Arc<TrieUpdatesSorted>,
69-
/// The sorted in-memory overlay hashed state.
70-
pub state_sorted: Arc<HashedPostStateSorted>,
71-
/// The collection of prefix sets for the computation. Since the prefix sets _always_
72-
/// invalidate the in-memory nodes, not all keys from `state_sorted` might be present here,
73-
/// if we have cached nodes for them.
74-
pub prefix_sets: Arc<TriePrefixSetsMut>,
75-
}
76-
77-
impl MultiProofConfig {
78-
/// Creates a new state root config from the trie input.
79-
///
80-
/// This returns a cleared [`TrieInput`] so that we can reuse any allocated space in the
81-
/// [`TrieInput`].
82-
pub(crate) fn from_input(mut input: TrieInput) -> (TrieInput, Self) {
83-
let config = Self {
84-
nodes_sorted: Arc::new(input.nodes.drain_into_sorted()),
85-
state_sorted: Arc::new(input.state.drain_into_sorted()),
86-
prefix_sets: Arc::new(input.prefix_sets.clone()),
87-
};
88-
(input.cleared(), config)
89-
}
90-
}
91-
9262
/// Messages used internally by the multi proof task.
9363
#[derive(Debug)]
9464
pub(super) enum MultiProofMessage {

crates/engine/tree/src/tree/payload_validator.rs

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::tree::{
55
error::{InsertBlockError, InsertBlockErrorKind, InsertPayloadError},
66
executor::WorkloadExecutor,
77
instrumented_state::InstrumentedStateProvider,
8-
payload_processor::{multiproof::MultiProofConfig, PayloadProcessor},
8+
payload_processor::PayloadProcessor,
99
precompile_cache::{CachedPrecompile, CachedPrecompileMetrics, PrecompileCacheMap},
1010
sparse_trie::StateRootComputeOutcome,
1111
EngineApiMetrics, EngineApiTreeState, ExecutionEnv, PayloadHandle, StateProviderBuilder,
@@ -38,7 +38,7 @@ use reth_provider::{
3838
StateRootProvider, TrieReader,
3939
};
4040
use reth_revm::db::State;
41-
use reth_trie::{updates::TrieUpdates, HashedPostState, TrieInput};
41+
use reth_trie::{updates::TrieUpdates, HashedPostState, TrieInputSorted};
4242
use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
4343
use std::{collections::HashMap, sync::Arc, time::Instant};
4444
use tracing::{debug, debug_span, error, info, instrument, trace, warn};
@@ -121,8 +121,6 @@ where
121121
metrics: EngineApiMetrics,
122122
/// Validator for the payload.
123123
validator: V,
124-
/// A cleared trie input, kept around to be reused so allocations can be minimized.
125-
trie_input: Option<TrieInput>,
126124
}
127125

128126
impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
@@ -166,7 +164,6 @@ where
166164
invalid_block_hook,
167165
metrics: EngineApiMetrics::default(),
168166
validator,
169-
trie_input: Default::default(),
170167
}
171168
}
172169

@@ -530,8 +527,8 @@ where
530527
Ok(ExecutedBlock {
531528
recovered_block: Arc::new(block),
532529
execution_output: Arc::new(ExecutionOutcome::from((output, block_num_hash.number))),
533-
hashed_state: Arc::new(hashed_state),
534-
trie_updates: Arc::new(trie_output),
530+
hashed_state: Arc::new(hashed_state.into_sorted()),
531+
trie_updates: Arc::new(trie_output.into_sorted()),
535532
})
536533
}
537534

@@ -641,26 +638,24 @@ where
641638
hashed_state: &HashedPostState,
642639
state: &EngineApiTreeState<N>,
643640
) -> Result<(B256, TrieUpdates), ParallelStateRootError> {
644-
let (mut input, block_hash) = self.compute_trie_input(parent_hash, state, None)?;
641+
let (mut input, block_hash) = self.compute_trie_input(parent_hash, state)?;
645642

646-
// Extend with block we are validating root for.
647-
input.append_ref(hashed_state);
643+
// Extend state overlay with current block's sorted state.
644+
input.prefix_sets.extend(hashed_state.construct_prefix_sets());
645+
let sorted_hashed_state = hashed_state.clone().into_sorted();
646+
Arc::make_mut(&mut input.state).extend_ref(&sorted_hashed_state);
648647

649-
// Convert the TrieInput into a MultProofConfig, since everything uses the sorted
650-
// forms of the state/trie fields.
651-
let (_, multiproof_config) = MultiProofConfig::from_input(input);
648+
let TrieInputSorted { nodes, state, prefix_sets: prefix_sets_mut } = input;
652649

653650
let factory = OverlayStateProviderFactory::new(self.provider.clone())
654651
.with_block_hash(Some(block_hash))
655-
.with_trie_overlay(Some(multiproof_config.nodes_sorted))
656-
.with_hashed_state_overlay(Some(multiproof_config.state_sorted));
652+
.with_trie_overlay(Some(nodes))
653+
.with_hashed_state_overlay(Some(state));
657654

658655
// The `hashed_state` argument is already taken into account as part of the overlay, but we
659656
// need to use the prefix sets which were generated from it to indicate to the
660657
// ParallelStateRoot which parts of the trie need to be recomputed.
661-
let prefix_sets = Arc::into_inner(multiproof_config.prefix_sets)
662-
.expect("MultiProofConfig was never cloned")
663-
.freeze();
658+
let prefix_sets = prefix_sets_mut.freeze();
664659

665660
ParallelStateRoot::new(factory, prefix_sets).incremental_root_with_updates()
666661
}
@@ -756,26 +751,23 @@ where
756751
> {
757752
match strategy {
758753
StateRootStrategy::StateRootTask => {
759-
// get allocated trie input if it exists
760-
let allocated_trie_input = self.trie_input.take();
761-
762754
// Compute trie input
763755
let trie_input_start = Instant::now();
764-
let (trie_input, block_hash) =
765-
self.compute_trie_input(parent_hash, state, allocated_trie_input)?;
756+
let (trie_input, block_hash) = self.compute_trie_input(parent_hash, state)?;
757+
758+
self.metrics
759+
.block_validation
760+
.trie_input_duration
761+
.record(trie_input_start.elapsed().as_secs_f64());
766762

767-
// Convert the TrieInput into a MultProofConfig, since everything uses the sorted
768-
// forms of the state/trie fields.
769-
let (trie_input, multiproof_config) = MultiProofConfig::from_input(trie_input);
770-
self.trie_input.replace(trie_input);
763+
// Create OverlayStateProviderFactory with sorted trie data for multiproofs
764+
let TrieInputSorted { nodes, state, .. } = trie_input;
771765

772-
// Create OverlayStateProviderFactory with the multiproof config, for use with
773-
// multiproofs.
774766
let multiproof_provider_factory =
775767
OverlayStateProviderFactory::new(self.provider.clone())
776768
.with_block_hash(Some(block_hash))
777-
.with_trie_overlay(Some(multiproof_config.nodes_sorted))
778-
.with_hashed_state_overlay(Some(multiproof_config.state_sorted));
769+
.with_trie_overlay(Some(nodes))
770+
.with_hashed_state_overlay(Some(state));
779771

780772
// Use state root task only if prefix sets are empty, otherwise proof generation is
781773
// too expensive because it requires walking all paths in every proof.
@@ -887,14 +879,14 @@ where
887879
/// Computes the trie input at the provided parent hash, as well as the block number of the
888880
/// highest persisted ancestor.
889881
///
890-
/// The goal of this function is to take in-memory blocks and generate a [`TrieInput`] that
891-
/// serves as an overlay to the database blocks.
882+
/// The goal of this function is to take in-memory blocks and generate a [`TrieInputSorted`]
883+
/// that serves as an overlay to the database blocks.
892884
///
893885
/// It works as follows:
894886
/// 1. Collect in-memory blocks that are descendants of the provided parent hash using
895887
/// [`crate::tree::TreeState::blocks_by_hash`]. This returns the highest persisted ancestor
896888
/// hash (`block_hash`) and the list of in-memory descendant blocks.
897-
/// 2. Extend the `TrieInput` with the contents of these in-memory blocks (from oldest to
889+
/// 2. Extend the `TrieInputSorted` with the contents of these in-memory blocks (from oldest to
898890
/// newest) to build the overlay state and trie updates that sit on top of the database view
899891
/// anchored at `block_hash`.
900892
#[instrument(
@@ -907,11 +899,7 @@ where
907899
&self,
908900
parent_hash: B256,
909901
state: &EngineApiTreeState<N>,
910-
allocated_trie_input: Option<TrieInput>,
911-
) -> ProviderResult<(TrieInput, B256)> {
912-
// get allocated trie input or use a default trie input
913-
let mut input = allocated_trie_input.unwrap_or_default();
914-
902+
) -> ProviderResult<(TrieInputSorted, B256)> {
915903
let (block_hash, blocks) =
916904
state.tree_state.blocks_by_hash(parent_hash).unwrap_or_else(|| (parent_hash, vec![]));
917905

@@ -921,10 +909,24 @@ where
921909
debug!(target: "engine::tree::payload_validator", historical = ?block_hash, blocks = blocks.len(), "Parent found in memory");
922910
}
923911

924-
// Extend with contents of parent in-memory blocks.
925-
input.extend_with_blocks(
926-
blocks.iter().rev().map(|block| (block.hashed_state(), block.trie_updates())),
927-
);
912+
// Extend with contents of parent in-memory blocks directly in sorted form.
913+
let mut input = TrieInputSorted::default();
914+
let mut blocks_iter = blocks.iter().rev().peekable();
915+
916+
if let Some(first) = blocks_iter.next() {
917+
input.state = Arc::clone(&first.hashed_state);
918+
input.nodes = Arc::clone(&first.trie_updates);
919+
920+
// Only clone and mutate if there are more in-memory blocks.
921+
if blocks_iter.peek().is_some() {
922+
let state_mut = Arc::make_mut(&mut input.state);
923+
let nodes_mut = Arc::make_mut(&mut input.nodes);
924+
for block in blocks_iter {
925+
state_mut.extend_ref(block.hashed_state());
926+
nodes_mut.extend_ref(block.trie_updates());
927+
}
928+
}
929+
}
928930

929931
Ok((input, block_hash))
930932
}

crates/engine/tree/src/tree/tests.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -826,8 +826,8 @@ fn test_tree_state_on_new_head_deep_fork() {
826826
test_harness.tree.state.tree_state.insert_executed(ExecutedBlock {
827827
recovered_block: Arc::new(block.clone()),
828828
execution_output: Arc::new(ExecutionOutcome::default()),
829-
hashed_state: Arc::new(HashedPostState::default()),
830-
trie_updates: Arc::new(TrieUpdates::default()),
829+
hashed_state: Arc::new(HashedPostState::default().into_sorted()),
830+
trie_updates: Arc::new(TrieUpdates::default().into_sorted()),
831831
});
832832
}
833833
test_harness.tree.state.tree_state.set_canonical_head(chain_a.last().unwrap().num_hash());
@@ -836,8 +836,8 @@ fn test_tree_state_on_new_head_deep_fork() {
836836
test_harness.tree.state.tree_state.insert_executed(ExecutedBlock {
837837
recovered_block: Arc::new(block.clone()),
838838
execution_output: Arc::new(ExecutionOutcome::default()),
839-
hashed_state: Arc::new(HashedPostState::default()),
840-
trie_updates: Arc::new(TrieUpdates::default()),
839+
hashed_state: Arc::new(HashedPostState::default().into_sorted()),
840+
trie_updates: Arc::new(TrieUpdates::default().into_sorted()),
841841
});
842842
}
843843

crates/optimism/flashblocks/src/worker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ where
125125
ExecutedBlock {
126126
recovered_block: block.into(),
127127
execution_output: Arc::new(execution_outcome),
128-
hashed_state: Arc::new(hashed_state),
128+
hashed_state: Arc::new(hashed_state.into_sorted()),
129129
trie_updates: Arc::default(),
130130
},
131131
);

crates/optimism/payload/src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ impl<Txs> OpBuilder<'_, Txs> {
386386
let executed: BuiltPayloadExecutedBlock<N> = BuiltPayloadExecutedBlock {
387387
recovered_block: Arc::new(block),
388388
execution_output: Arc::new(execution_outcome),
389+
// Keep unsorted; conversion to sorted happens when needed downstream
389390
hashed_state: either::Either::Left(Arc::new(hashed_state)),
390391
trie_updates: either::Either::Left(Arc::new(trie_updates)),
391392
};

crates/payload/primitives/src/traits.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,21 @@ pub struct BuiltPayloadExecutedBlock<N: NodePrimitives> {
4242
impl<N: NodePrimitives> BuiltPayloadExecutedBlock<N> {
4343
/// Converts this into an [`reth_chain_state::ExecutedBlock`].
4444
///
45-
/// If the hashed state or trie updates are in sorted form, they will be converted
46-
/// back to their unsorted representations.
45+
/// Ensures hashed state and trie updates are in their sorted representations
46+
/// as required by `reth_chain_state::ExecutedBlock`.
4747
pub fn into_executed_payload(self) -> reth_chain_state::ExecutedBlock<N> {
4848
let hashed_state = match self.hashed_state {
49-
Either::Left(unsorted) => unsorted,
50-
Either::Right(sorted) => Arc::new(Arc::unwrap_or_clone(sorted).into()),
49+
// Convert unsorted to sorted
50+
Either::Left(unsorted) => Arc::new(Arc::unwrap_or_clone(unsorted).into_sorted()),
51+
// Already sorted
52+
Either::Right(sorted) => sorted,
5153
};
5254

5355
let trie_updates = match self.trie_updates {
54-
Either::Left(unsorted) => unsorted,
55-
Either::Right(sorted) => Arc::new(Arc::unwrap_or_clone(sorted).into()),
56+
// Convert unsorted to sorted
57+
Either::Left(unsorted) => Arc::new(Arc::unwrap_or_clone(unsorted).into_sorted()),
58+
// Already sorted
59+
Either::Right(sorted) => sorted,
5660
};
5761

5862
reth_chain_state::ExecutedBlock {

0 commit comments

Comments
 (0)