Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
eb7d8c6
perf(engine): return sorted data from compute_trie_input
yongkangc Oct 28, 2025
43ceb89
refactor(engine): optimize trie input handling in compute_trie_input
yongkangc Oct 28, 2025
3fd4996
update call site
yongkangc Oct 28, 2025
9b83edd
fix fmt
yongkangc Oct 28, 2025
7c85ed3
refactor(chain-state): update to sorted trie structures
yongkangc Oct 29, 2025
4249c66
added sorted for test
yongkangc Oct 29, 2025
08c9a1f
refactor(engine): optimize payload validation with sorted trie input
yongkangc Oct 29, 2025
23a9338
hashed_state convert to sorted
yongkangc Oct 29, 2025
861579e
refactor(trie): streamline TrieInput creation from sorted blocks
yongkangc Oct 29, 2025
87642e4
refactor(engine): ExecutedBlock to Arc
yongkangc Oct 30, 2025
2fbcbb4
refactor(multiproof): remove MultiProofConfig struct and related methods
yongkangc Oct 30, 2025
9e1fa4a
refactor(engine): simplify trie input handling in payload validation …
yongkangc Oct 30, 2025
0e2c3e7
refactor(trie): enhance TrieInput with efficient prepend and Arc usage
yongkangc Oct 30, 2025
5f51ed3
refactor: reference arc directly
yongkangc Oct 30, 2025
b346694
feat(trie): add extend_from_sorted for extending and converting hashe…
yongkangc Oct 30, 2025
56ee954
extend_from_sorted
yongkangc Oct 30, 2025
6fd8fd2
added comment
yongkangc Oct 30, 2025
acfb780
added comment
yongkangc Oct 30, 2025
d3e56a2
added extend_from_sorted
yongkangc Oct 30, 2025
6c5cd7c
clear data
yongkangc Oct 30, 2025
76babb4
remove into
yongkangc Oct 30, 2025
66c73e5
Merge branch 'main' into yk/compute_trie2
yongkangc Oct 30, 2025
665ae71
remove drain into sorted
yongkangc Oct 30, 2025
4ca196a
rm clear
yongkangc Oct 30, 2025
2667271
rm clear
yongkangc Oct 30, 2025
dae97c6
change function signature to rm alloc
yongkangc Oct 30, 2025
d251dee
comments
yongkangc Oct 30, 2025
28b84bf
added back clear
yongkangc Oct 30, 2025
990cb15
destructure instead of arc
yongkangc Oct 30, 2025
5f90e0f
comment
yongkangc Oct 30, 2025
cdff945
rm alloc, use trie destructuring
yongkangc Oct 30, 2025
b15a062
Refactor payload validator to improve block handling and reduce alloc…
yongkangc Oct 30, 2025
ba70ee7
fast path
yongkangc Oct 30, 2025
37bdb2d
fmt
yongkangc Oct 30, 2025
0e0e2d7
Enhance TrieInputSorted and HashedPostStateSorted with new methods fo…
yongkangc Oct 30, 2025
062aa27
Revert "Enhance TrieInputSorted and HashedPostStateSorted with new me…
yongkangc Oct 30, 2025
ceb77c0
added tests
yongkangc Oct 30, 2025
f1454d7
fmt
yongkangc Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions crates/chain-state/src/in_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use reth_primitives_traits::{
SignedTransaction,
};
use reth_storage_api::StateProviderBox;
use reth_trie::{updates::TrieUpdates, HashedPostState};
use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted};
use std::{collections::BTreeMap, sync::Arc, time::Instant};
use tokio::sync::{broadcast, watch};

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

impl<N: NodePrimitives> Default for ExecutedBlock<N> {
Expand Down Expand Up @@ -763,13 +763,13 @@ impl<N: NodePrimitives> ExecutedBlock<N> {

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

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

Expand Down Expand Up @@ -875,8 +875,8 @@ mod tests {
StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider,
};
use reth_trie::{
AccountProof, HashedStorage, MultiProof, MultiProofTargets, StorageMultiProof,
StorageProof, TrieInput,
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
};

fn create_mock_state(
Expand Down
6 changes: 3 additions & 3 deletions crates/chain-state/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use reth_primitives_traits::{
SignedTransaction,
};
use reth_storage_api::NodePrimitivesProvider;
use reth_trie::{root::state_root_unhashed, updates::TrieUpdates, HashedPostState};
use reth_trie::{root::state_root_unhashed, updates::TrieUpdatesSorted, HashedPostStateSorted};
use revm_database::BundleState;
use revm_state::AccountInfo;
use std::{
Expand Down Expand Up @@ -216,8 +216,8 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
block_number,
vec![Requests::default()],
)),
hashed_state: Arc::new(HashedPostState::default()),
trie_updates: Arc::new(TrieUpdates::default()),
hashed_state: Arc::new(HashedPostStateSorted::default()),
trie_updates: Arc::new(TrieUpdatesSorted::default()),
}
}

Expand Down
11 changes: 5 additions & 6 deletions crates/engine/tree/src/tree/payload_processor/multiproof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use reth_revm::state::EvmState;
use reth_trie::{
added_removed_keys::MultiAddedRemovedKeys, prefix_set::TriePrefixSetsMut,
updates::TrieUpdatesSorted, DecodedMultiProof, HashedPostState, HashedPostStateSorted,
HashedStorage, MultiProofTargets, TrieInput,
HashedStorage, MultiProofTargets, TrieInputSorted,
};
use reth_trie_parallel::{
proof::ParallelProof,
Expand Down Expand Up @@ -73,12 +73,11 @@ pub(crate) struct MultiProofConfig {
impl MultiProofConfig {
/// Creates a new state root config from the trie input.
///
/// This returns a cleared [`TrieInput`] so that we can reuse any allocated space in the
/// [`TrieInput`].
pub(crate) fn from_input(mut input: TrieInput) -> (TrieInput, Self) {
/// This returns a cleared [`TrieInputSorted`] so that we can reuse any allocated space.
pub(crate) fn from_input(mut input: TrieInputSorted) -> (TrieInputSorted, Self) {
let config = Self {
nodes_sorted: Arc::new(input.nodes.drain_into_sorted()),
state_sorted: Arc::new(input.state.drain_into_sorted()),
nodes_sorted: Arc::new(core::mem::take(&mut input.nodes)),
state_sorted: Arc::new(core::mem::take(&mut input.state)),
prefix_sets: Arc::new(input.prefix_sets.clone()),
};
(input.cleared(), config)
Expand Down
41 changes: 27 additions & 14 deletions crates/engine/tree/src/tree/payload_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use reth_provider::{
StateRootProvider, TrieReader,
};
use reth_revm::db::State;
use reth_trie::{updates::TrieUpdates, HashedPostState, TrieInput};
use reth_trie::{updates::TrieUpdates, HashedPostState, TrieInput, TrieInputSorted};
use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
use std::{collections::HashMap, sync::Arc, time::Instant};
use tracing::{debug, debug_span, error, info, instrument, trace, warn};
Expand Down Expand Up @@ -682,7 +682,7 @@ where
) -> Result<(B256, TrieUpdates), ParallelStateRootError> {
let provider = self.provider.database_provider_ro()?;

let (mut input, block_number) =
let (mut input, _reusable_input, block_number) =
self.compute_trie_input(provider, parent_hash, state, None)?;

// Extend with block we are validating root for.
Expand Down Expand Up @@ -802,15 +802,15 @@ where
match strategy {
StateRootStrategy::StateRootTask => {
// get allocated trie input if it exists
let allocated_trie_input = self.trie_input.take();
let allocated = self.trie_input.take();

// Compute trie input
let trie_input_start = Instant::now();
let (trie_input, block_number) = self.compute_trie_input(
let (trie_input, mut reusable_input, block_number) = self.compute_trie_input(
self.provider.database_provider_ro()?,
parent_hash,
state,
allocated_trie_input,
allocated,
)?;

self.metrics
Expand All @@ -820,8 +820,13 @@ where

// Convert the TrieInput into a MultProofConfig, since everything uses the sorted
// forms of the state/trie fields.
let (trie_input, multiproof_config) = MultiProofConfig::from_input(trie_input);
self.trie_input.replace(trie_input);
let (mut cleared_sorted_input, multiproof_config) =
MultiProofConfig::from_input(trie_input);

// Rescue the prefix_sets from the cleared sorted input and attach to reusable
// builder
reusable_input.prefix_sets = core::mem::take(&mut cleared_sorted_input.prefix_sets);
self.trie_input.replace(reusable_input);

// Create OverlayStateProviderFactory with the multiproof config, for use with
// multiproofs.
Expand Down Expand Up @@ -982,9 +987,10 @@ where
parent_hash: B256,
state: &EngineApiTreeState<N>,
allocated_trie_input: Option<TrieInput>,
) -> ProviderResult<(TrieInput, BlockNumber)> {
// get allocated trie input or use a default trie input
) -> ProviderResult<(TrieInputSorted, TrieInput, BlockNumber)> {
// Build with unsorted structures for fast append operations, reusing allocated capacity.
let mut input = allocated_trie_input.unwrap_or_default();
input.clear(); // Keep HashMap capacity, drop contents

let (historical, blocks) = state
.tree_state
Expand All @@ -1002,12 +1008,19 @@ where
.convert_hash_or_number(historical)?
.ok_or_else(|| ProviderError::BlockHashNotFound(historical.as_hash().unwrap()))?;

// Extend with contents of parent in-memory blocks.
input.extend_with_blocks(
blocks.iter().rev().map(|block| (block.hashed_state(), block.trie_updates())),
);
// Rebuild the overlay directly in sorted form, reusing the prefix-set allocation we already
// paid for on previous iterations.
let mut sorted_input = TrieInputSorted {
prefix_sets: core::mem::take(&mut input.prefix_sets),
..Default::default()
};

for block in blocks.iter().rev() {
sorted_input.state.extend_ref(block.hashed_state());
sorted_input.nodes.extend_ref(block.trie_updates());
}

Ok((input, block_number))
Ok((sorted_input, input, block_number))
}
}

Expand Down
7 changes: 7 additions & 0 deletions crates/trie/common/src/hashed_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,13 @@ impl HashedPostStateSorted {
.or_insert_with(|| other_storage.clone());
}
}

/// Clears all accounts and storage data.
pub fn clear(&mut self) {
self.accounts.accounts.clear();
self.accounts.destroyed_accounts.clear();
self.storages.clear();
}
}

impl AsRef<Self> for HashedPostStateSorted {
Expand Down
77 changes: 76 additions & 1 deletion crates/trie/common/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::{prefix_set::TriePrefixSetsMut, updates::TrieUpdates, HashedPostState};
use crate::{
prefix_set::TriePrefixSetsMut,
updates::{TrieUpdates, TrieUpdatesSorted},
HashedPostState, HashedPostStateSorted,
};

/// Inputs for trie-related computations.
#[derive(Default, Debug, Clone)]
Expand Down Expand Up @@ -118,4 +122,75 @@ impl TrieInput {
self.clear();
self
}

/// Converts trie input into [`TrieInputSorted`], draining the internal maps while keeping
/// their allocated capacity.
///
/// This effectively clears all the fields in the [`TrieInput`] while preserving the
/// `HashMap` capacity for reuse. This allows us to reuse the allocated space for subsequent
/// block validations, avoiding repeated allocations and rehashing in hot paths.
///
/// The sorted output allocates new `Vec` space, but the original `HashMap` capacity is
/// retained for the next cycle.
pub fn drain_into_sorted(&mut self) -> TrieInputSorted {
TrieInputSorted {
nodes: self.nodes.drain_into_sorted(),
state: self.state.drain_into_sorted(),
prefix_sets: core::mem::take(&mut self.prefix_sets),
}
}
}

/// Sorted variant of [`TrieInput`] for efficient proof generation.
///
/// This type holds sorted versions of trie data structures, which eliminates the need
/// for expensive sorting operations during multiproof generation.
#[derive(Default, Debug, Clone)]
pub struct TrieInputSorted {
/// Sorted cached in-memory intermediate trie nodes.
pub nodes: TrieUpdatesSorted,
/// Sorted in-memory overlay hashed state.
pub state: HashedPostStateSorted,
/// Prefix sets for computation.
pub prefix_sets: TriePrefixSetsMut,
}

impl TrieInputSorted {
/// Create new sorted trie input.
pub const fn new(
nodes: TrieUpdatesSorted,
state: HashedPostStateSorted,
prefix_sets: TriePrefixSetsMut,
) -> Self {
Self { nodes, state, prefix_sets }
}

/// Create from unsorted [`TrieInput`] by sorting.
pub fn from_unsorted(input: TrieInput) -> Self {
Self {
nodes: input.nodes.into_sorted(),
state: input.state.into_sorted(),
prefix_sets: input.prefix_sets,
}
}

/// Append state to the input by reference and extend the prefix sets.
pub fn append_ref(&mut self, state: &HashedPostState) {
self.prefix_sets.extend(state.construct_prefix_sets());
let sorted_state = state.clone().into_sorted();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so basically we are applying sorting here, would that be a concern for performance regression?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're not using this method in this PR then I don't think we should add it. This logic made sense on TrieInput when we were not able to revert trie data, but now it's not necessary I don't think

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like theres alot more code that i can get rid off, currently still looking through / understanding the flow to see how it can be simpler.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, in this case I would have append_ref at least accept a &HashedPostStateSorted, and do the into_sorted outside, so it might be possible in the future to eliminate that too

self.state.extend_ref(&sorted_state);
}

/// Clear all data.
pub fn clear(&mut self) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use this method? I don't think it makes much sense to use it, given that we're holding onto Arcs

self.nodes.clear();
self.state.clear();
self.prefix_sets.clear();
}

/// Return a cleared version of this sorted trie input.
pub fn cleared(mut self) -> Self {
self.clear();
self
}
}
2 changes: 1 addition & 1 deletion crates/trie/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub use hashed_state::*;

/// Input for trie computation.
mod input;
pub use input::TrieInput;
pub use input::{TrieInput, TrieInputSorted};

/// The implementation of hash builder.
pub mod hash_builder;
Expand Down
6 changes: 6 additions & 0 deletions crates/trie/common/src/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,12 @@ impl TrieUpdatesSorted {
.or_insert_with(|| storage_trie.clone());
}
}

/// Clears all account nodes and storage tries.
pub fn clear(&mut self) {
self.account_nodes.clear();
self.storage_tries.clear();
}
}

impl AsRef<Self> for TrieUpdatesSorted {
Expand Down
Loading