From 12d0b74a16b9441b8faf3b6e445364466b264022 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Wed, 4 Feb 2026 08:35:03 -0800 Subject: [PATCH] perf(trie): reuse proof nodes buffer in `reveal_nodes` (#21648) Co-authored-by: Amp Co-authored-by: Alexey Shekhirin --- .../tree/src/tree/payload_processor/mod.rs | 18 ++-- .../src/tree/payload_processor/sparse_trie.rs | 21 +++-- crates/trie/sparse-parallel/src/trie.rs | 58 ++++++------- crates/trie/sparse/src/state.rs | 85 +++++++++++++++---- crates/trie/sparse/src/traits.rs | 11 ++- crates/trie/sparse/src/trie.rs | 11 ++- 6 files changed, 138 insertions(+), 66 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index ee611f8bf2..ca1d78b5e7 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -587,11 +587,14 @@ where target: "engine::tree::payload_processor", "State root receiver dropped, clearing trie" ); - let trie = task.into_cleared_trie( + let (trie, deferred) = task.into_cleared_trie( SPARSE_TRIE_MAX_NODES_SHRINK_CAPACITY, SPARSE_TRIE_MAX_VALUES_SHRINK_CAPACITY, ); guard.store(PreservedSparseTrie::cleared(trie)); + // Drop guard before deferred to release lock before expensive deallocations + drop(guard); + drop(deferred); return; } @@ -599,9 +602,9 @@ where // A failed computation may have left the trie in a partially updated state. let _enter = debug_span!(target: "engine::tree::payload_processor", "preserve").entered(); - if let Some(state_root) = computed_state_root { + let deferred = if let Some(state_root) = computed_state_root { let start = std::time::Instant::now(); - let trie = task.into_trie_for_reuse( + let (trie, deferred) = task.into_trie_for_reuse( prune_depth, max_storage_tries, SPARSE_TRIE_MAX_NODES_SHRINK_CAPACITY, @@ -611,17 +614,22 @@ where .into_trie_for_reuse_duration_histogram .record(start.elapsed().as_secs_f64()); guard.store(PreservedSparseTrie::anchored(trie, state_root)); + deferred } else { debug!( target: "engine::tree::payload_processor", "State root computation failed, clearing trie" ); - let trie = task.into_cleared_trie( + let (trie, deferred) = task.into_cleared_trie( SPARSE_TRIE_MAX_NODES_SHRINK_CAPACITY, SPARSE_TRIE_MAX_VALUES_SHRINK_CAPACITY, ); guard.store(PreservedSparseTrie::cleared(trie)); - } + deferred + }; + // Drop guard before deferred to release lock before expensive deallocations + drop(guard); + drop(deferred); }); } diff --git a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs index 32bb147dcd..6dd3933633 100644 --- a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs @@ -28,7 +28,7 @@ use reth_trie_parallel::{ use reth_trie_sparse::{ errors::{SparseStateTrieResult, SparseTrieErrorKind, SparseTrieResult}, provider::{TrieNodeProvider, TrieNodeProviderFactory}, - LeafUpdate, SerialSparseTrie, SparseStateTrie, SparseTrie, SparseTrieExt, + DeferredDrops, LeafUpdate, SerialSparseTrie, SparseStateTrie, SparseTrie, SparseTrieExt, }; use revm_primitives::{hash_map::Entry, B256Map}; use smallvec::SmallVec; @@ -72,7 +72,7 @@ where max_storage_tries: usize, max_nodes_capacity: usize, max_values_capacity: usize, - ) -> SparseStateTrie { + ) -> (SparseStateTrie, DeferredDrops) { match self { Self::Cleared(task) => task.into_cleared_trie(max_nodes_capacity, max_values_capacity), Self::Cached(task) => task.into_trie_for_reuse( @@ -88,7 +88,7 @@ where self, max_nodes_capacity: usize, max_values_capacity: usize, - ) -> SparseStateTrie { + ) -> (SparseStateTrie, DeferredDrops) { match self { Self::Cleared(task) => task.into_cleared_trie(max_nodes_capacity, max_values_capacity), Self::Cached(task) => task.into_cleared_trie(max_nodes_capacity, max_values_capacity), @@ -199,10 +199,11 @@ where mut self, max_nodes_capacity: usize, max_values_capacity: usize, - ) -> SparseStateTrie { + ) -> (SparseStateTrie, DeferredDrops) { self.trie.clear(); self.trie.shrink_to(max_nodes_capacity, max_values_capacity); - self.trie + let deferred = self.trie.take_deferred_drops(); + (self.trie, deferred) } } @@ -312,10 +313,11 @@ where max_storage_tries: usize, max_nodes_capacity: usize, max_values_capacity: usize, - ) -> SparseStateTrie { + ) -> (SparseStateTrie, DeferredDrops) { self.trie.prune(prune_depth, max_storage_tries); self.trie.shrink_to(max_nodes_capacity, max_values_capacity); - self.trie + let deferred = self.trie.take_deferred_drops(); + (self.trie, deferred) } /// Clears and shrinks the trie, discarding all state. @@ -326,10 +328,11 @@ where mut self, max_nodes_capacity: usize, max_values_capacity: usize, - ) -> SparseStateTrie { + ) -> (SparseStateTrie, DeferredDrops) { self.trie.clear(); self.trie.shrink_to(max_nodes_capacity, max_values_capacity); - self.trie + let deferred = self.trie.take_deferred_drops(); + (self.trie, deferred) } /// Runs the sparse trie task to completion. diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index 38b0726275..1d4a1701dd 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -175,7 +175,7 @@ impl SparseTrie for ParallelSparseTrie { self.updates = retain_updates.then(Default::default); } - fn reveal_nodes(&mut self, mut nodes: Vec) -> SparseTrieResult<()> { + fn reveal_nodes(&mut self, nodes: &mut [ProofTrieNode]) -> SparseTrieResult<()> { if nodes.is_empty() { return Ok(()) } @@ -192,7 +192,7 @@ impl SparseTrie for ParallelSparseTrie { // Update the top-level branch node masks. This is simple and can't be done in parallel. self.branch_node_masks.reserve(nodes.len()); - for ProofTrieNode { path, masks, .. } in &nodes { + for ProofTrieNode { path, masks, .. } in nodes.iter() { if let Some(branch_masks) = masks { self.branch_node_masks.insert(*path, *branch_masks); } @@ -4095,7 +4095,7 @@ mod tests { let node = create_leaf_node([0x2, 0x3], 42); let masks = None; - trie.reveal_nodes(vec![ProofTrieNode { path, node, masks }]).unwrap(); + trie.reveal_nodes(&mut [ProofTrieNode { path, node, masks }]).unwrap(); assert_matches!( trie.upper_subtrie.nodes.get(&path), @@ -4116,7 +4116,7 @@ mod tests { let node = create_leaf_node([0x3, 0x4], 42); let masks = None; - trie.reveal_nodes(vec![ProofTrieNode { path, node, masks }]).unwrap(); + trie.reveal_nodes(&mut [ProofTrieNode { path, node, masks }]).unwrap(); // Check that the lower subtrie was created let idx = path_subtrie_index_unchecked(&path); @@ -4140,7 +4140,7 @@ mod tests { let node = create_leaf_node([0x4, 0x5], 42); let masks = None; - trie.reveal_nodes(vec![ProofTrieNode { path, node, masks }]).unwrap(); + trie.reveal_nodes(&mut [ProofTrieNode { path, node, masks }]).unwrap(); // Check that the lower subtrie's path hasn't changed let idx = path_subtrie_index_unchecked(&path); @@ -4201,7 +4201,7 @@ mod tests { let node = create_extension_node([0x2], child_hash); let masks = None; - trie.reveal_nodes(vec![ProofTrieNode { path, node, masks }]).unwrap(); + trie.reveal_nodes(&mut [ProofTrieNode { path, node, masks }]).unwrap(); // Extension node should be in upper trie assert_matches!( @@ -4263,7 +4263,7 @@ mod tests { let node = create_branch_node_with_children(&[0x0, 0x7, 0xf], child_hashes.clone()); let masks = None; - trie.reveal_nodes(vec![ProofTrieNode { path, node, masks }]).unwrap(); + trie.reveal_nodes(&mut [ProofTrieNode { path, node, masks }]).unwrap(); // Branch node should be in upper trie assert_matches!( @@ -4319,7 +4319,7 @@ mod tests { let branch_node = create_branch_node_with_children(&[0x0, 0x1, 0x2], child_hashes); // Reveal nodes using reveal_nodes - trie.reveal_nodes(vec![ + trie.reveal_nodes(&mut [ ProofTrieNode { path: branch_path, node: branch_node, masks: None }, ProofTrieNode { path: leaf_1_path, node: leaf_1, masks: None }, ProofTrieNode { path: leaf_2_path, node: leaf_2, masks: None }, @@ -5022,7 +5022,7 @@ mod tests { let removed_branch_path = Nibbles::from_nibbles([0x4, 0xf, 0x8, 0x8, 0x0, 0x7, 0x2]); // Convert the logs into reveal_nodes call on a fresh ParallelSparseTrie - let nodes = vec![ + let mut nodes = vec![ // Branch at 0x4f8807 ProofTrieNode { path: branch_path, @@ -5153,7 +5153,7 @@ mod tests { .unwrap(); // Call reveal_nodes - trie.reveal_nodes(nodes).unwrap(); + trie.reveal_nodes(&mut nodes).unwrap(); // Remove the leaf at "0x4f88072c077f86613088dfcae648abe831fadca55ad43ab165d1680dd567b5d6" let leaf_key = Nibbles::from_nibbles([ @@ -5239,7 +5239,7 @@ mod tests { // Step 2: Reveal nodes in the trie let mut trie = ParallelSparseTrie::from_root(extension, None, true).unwrap(); - trie.reveal_nodes(vec![ + trie.reveal_nodes(&mut [ ProofTrieNode { path: branch_path, node: branch, masks: None }, ProofTrieNode { path: leaf_1_path, node: leaf_1, masks: None }, ProofTrieNode { path: leaf_2_path, node: leaf_2, masks: None }, @@ -5779,7 +5779,7 @@ mod tests { // ├── 0 -> Hash (Path = 0) // └── 1 -> Leaf (Path = 1) sparse - .reveal_nodes(vec![ + .reveal_nodes(&mut [ ProofTrieNode { path: Nibbles::default(), node: branch, @@ -5834,7 +5834,7 @@ mod tests { // ├── 0 -> Hash (Path = 0) // └── 1 -> Leaf (Path = 1) sparse - .reveal_nodes(vec![ + .reveal_nodes(&mut [ ProofTrieNode { path: Nibbles::default(), node: branch, @@ -6200,7 +6200,7 @@ mod tests { Default::default(), [key1()], ); - let revealed_nodes: Vec = hash_builder_proof_nodes + let mut revealed_nodes: Vec = hash_builder_proof_nodes .nodes_sorted() .into_iter() .map(|(path, node)| { @@ -6210,7 +6210,7 @@ mod tests { ProofTrieNode { path, node: TrieNode::decode(&mut &node[..]).unwrap(), masks } }) .collect(); - sparse.reveal_nodes(revealed_nodes).unwrap(); + sparse.reveal_nodes(&mut revealed_nodes).unwrap(); // Check that the branch node exists with only two nibbles set assert_eq!( @@ -6235,7 +6235,7 @@ mod tests { Default::default(), [key3()], ); - let revealed_nodes: Vec = hash_builder_proof_nodes + let mut revealed_nodes: Vec = hash_builder_proof_nodes .nodes_sorted() .into_iter() .map(|(path, node)| { @@ -6245,7 +6245,7 @@ mod tests { ProofTrieNode { path, node: TrieNode::decode(&mut &node[..]).unwrap(), masks } }) .collect(); - sparse.reveal_nodes(revealed_nodes).unwrap(); + sparse.reveal_nodes(&mut revealed_nodes).unwrap(); // Check that nothing changed in the branch node assert_eq!( @@ -6321,7 +6321,7 @@ mod tests { Default::default(), [key1(), Nibbles::from_nibbles_unchecked([0x01])], ); - let revealed_nodes: Vec = hash_builder_proof_nodes + let mut revealed_nodes: Vec = hash_builder_proof_nodes .nodes_sorted() .into_iter() .map(|(path, node)| { @@ -6331,7 +6331,7 @@ mod tests { ProofTrieNode { path, node: TrieNode::decode(&mut &node[..]).unwrap(), masks } }) .collect(); - sparse.reveal_nodes(revealed_nodes).unwrap(); + sparse.reveal_nodes(&mut revealed_nodes).unwrap(); // Check that the branch node exists assert_eq!( @@ -6356,7 +6356,7 @@ mod tests { Default::default(), [key2()], ); - let revealed_nodes: Vec = hash_builder_proof_nodes + let mut revealed_nodes: Vec = hash_builder_proof_nodes .nodes_sorted() .into_iter() .map(|(path, node)| { @@ -6366,7 +6366,7 @@ mod tests { ProofTrieNode { path, node: TrieNode::decode(&mut &node[..]).unwrap(), masks } }) .collect(); - sparse.reveal_nodes(revealed_nodes).unwrap(); + sparse.reveal_nodes(&mut revealed_nodes).unwrap(); // Check that nothing changed in the extension node assert_eq!( @@ -6448,7 +6448,7 @@ mod tests { Default::default(), [key1()], ); - let revealed_nodes: Vec = hash_builder_proof_nodes + let mut revealed_nodes: Vec = hash_builder_proof_nodes .nodes_sorted() .into_iter() .map(|(path, node)| { @@ -6458,7 +6458,7 @@ mod tests { ProofTrieNode { path, node: TrieNode::decode(&mut &node[..]).unwrap(), masks } }) .collect(); - sparse.reveal_nodes(revealed_nodes).unwrap(); + sparse.reveal_nodes(&mut revealed_nodes).unwrap(); // Check that the branch node wasn't overwritten by the extension node in the proof assert_matches!( @@ -7422,7 +7422,7 @@ mod tests { let leaf_node = LeafNode::new(leaf_key, leaf_value); let leaf_masks = None; - trie.reveal_nodes(vec![ + trie.reveal_nodes(&mut [ ProofTrieNode { path: Nibbles::from_nibbles([0x3]), node: TrieNode::Branch(branch_0x3_node), @@ -7732,7 +7732,7 @@ mod tests { ); // Reveal the trie structure using ProofTrieNode - let proof_nodes = vec![ + let mut proof_nodes = vec![ ProofTrieNode { path: Nibbles::from_nibbles([0x3]), node: branch_0x3_node, @@ -7763,7 +7763,7 @@ mod tests { ) .expect("root revealed"); - trie.reveal_nodes(proof_nodes).unwrap(); + trie.reveal_nodes(&mut proof_nodes).unwrap(); // Update the leaf in order to reveal it in the trie trie.update_leaf(leaf_nibbles, leaf_value, &provider).unwrap(); @@ -7811,7 +7811,7 @@ mod tests { let leaf_node = create_leaf_node(leaf_key.to_vec(), 42); // Reveal the leaf node - trie.reveal_nodes(vec![ProofTrieNode { path: leaf_path, node: leaf_node, masks: None }]) + trie.reveal_nodes(&mut [ProofTrieNode { path: leaf_path, node: leaf_node, masks: None }]) .unwrap(); // The full path is leaf_path + leaf_key @@ -8999,7 +8999,7 @@ mod tests { // Create a trie with some data let mut trie = ParallelSparseTrie::default(); - let nodes = vec![ + let mut nodes = vec![ ProofTrieNode { path: Nibbles::from_nibbles_unchecked([0x1, 0x2]), node: TrieNode::Leaf(LeafNode { @@ -9017,7 +9017,7 @@ mod tests { masks: None, }, ]; - trie.reveal_nodes(nodes).unwrap(); + trie.reveal_nodes(&mut nodes).unwrap(); let populated_size = trie.memory_size(); diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index fd3a613159..79c4dee7d9 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -23,6 +23,16 @@ use reth_trie_common::{ use tracing::debug; use tracing::{instrument, trace}; +/// Holds data that should be dropped after any locks are released. +/// +/// This is used to defer expensive deallocations (like proof node buffers) until after final state +/// root is calculated +#[derive(Debug, Default)] +pub struct DeferredDrops { + /// Each nodes reveal operation creates a new buffer, uses it, and pushes it here. + pub proof_nodes_bufs: Vec>, +} + #[derive(Debug)] /// Sparse state trie representing lazy-loaded Ethereum state trie. pub struct SparseStateTrie< @@ -39,6 +49,8 @@ pub struct SparseStateTrie< retain_updates: bool, /// Reusable buffer for RLP encoding of trie accounts. account_rlp_buf: Vec, + /// Holds data that should be dropped after final state root is calculated. + deferred_drops: DeferredDrops, /// Metrics for the sparse state trie. #[cfg(feature = "metrics")] metrics: crate::metrics::SparseStateTrieMetrics, @@ -56,6 +68,7 @@ where storage: Default::default(), retain_updates: false, account_rlp_buf: Vec::with_capacity(TRIE_ACCOUNT_RLP_MAX_SIZE), + deferred_drops: DeferredDrops::default(), #[cfg(feature = "metrics")] metrics: Default::default(), } @@ -105,6 +118,14 @@ impl SparseStateTrie { self.set_default_storage_trie(trie); self } + + /// Takes the data structures for deferred dropping. + /// + /// This allows the caller to drop the buffers later, avoiding expensive deallocations while + /// calculating the state root. + pub fn take_deferred_drops(&mut self) -> DeferredDrops { + core::mem::take(&mut self.deferred_drops) + } } impl SparseStateTrie @@ -273,22 +294,24 @@ where }) .par_bridge_buffered() .map(|(account, storage_subtree, mut revealed_nodes, mut trie)| { + let mut bufs = Vec::new(); let result = Self::reveal_decoded_storage_multiproof_inner( account, storage_subtree, &mut revealed_nodes, &mut trie, + &mut bufs, retain_updates, ); - (account, revealed_nodes, trie, result) + (account, revealed_nodes, trie, result, bufs) }) .collect(); // Return `revealed_nodes` and `RevealableSparseTrie` for each account, incrementing // metrics and returning the last error seen if any. let mut any_err = Ok(()); - for (account, revealed_nodes, trie, result) in results { + for (account, revealed_nodes, trie, result, bufs) in results { self.storage.revealed_paths.insert(account, revealed_nodes); self.storage.tries.insert(account, trie); // Mark this storage trie as hot (accessed this tick) @@ -304,6 +327,9 @@ where } else { any_err = result.map(|_| ()); } + + // Keep buffers for deferred dropping + self.deferred_drops.proof_nodes_bufs.extend(bufs); } any_err @@ -369,19 +395,21 @@ where }) .par_bridge_buffered() .map(|(account, storage_proofs, mut revealed_nodes, mut trie)| { + let mut bufs = Vec::new(); let result = Self::reveal_storage_v2_proof_nodes_inner( account, storage_proofs, &mut revealed_nodes, &mut trie, + &mut bufs, retain_updates, ); - (account, result, revealed_nodes, trie) + (account, result, revealed_nodes, trie, bufs) }) .collect(); let mut any_err = Ok(()); - for (account, result, revealed_nodes, trie) in results { + for (account, result, revealed_nodes, trie, bufs) in results { self.storage.revealed_paths.insert(account, revealed_nodes); self.storage.tries.insert(account, trie); // Mark this storage trie as hot (accessed this tick) @@ -397,6 +425,9 @@ where } else { any_err = result.map(|_| ()); } + + // Keep buffers for deferred dropping + self.deferred_drops.proof_nodes_bufs.extend(bufs); } any_err @@ -420,12 +451,16 @@ where account_subtree: DecodedProofNodes, branch_node_masks: BranchNodeMasksMap, ) -> SparseStateTrieResult<()> { - let FilterMappedProofNodes { root_node, nodes, new_nodes, metric_values: _metric_values } = - filter_map_revealed_nodes( - account_subtree, - &mut self.revealed_account_paths, - &branch_node_masks, - )?; + let FilterMappedProofNodes { + root_node, + mut nodes, + new_nodes, + metric_values: _metric_values, + } = filter_map_revealed_nodes( + account_subtree, + &mut self.revealed_account_paths, + &branch_node_masks, + )?; #[cfg(feature = "metrics")] { self.metrics.increment_total_account_nodes(_metric_values.total_nodes as u64); @@ -443,9 +478,12 @@ where trie.reserve_nodes(new_nodes); trace!(target: "trie::sparse", total_nodes = ?nodes.len(), "Revealing account nodes"); - trie.reveal_nodes(nodes)?; + trie.reveal_nodes(&mut nodes)?; } + // Keep buffer for deferred dropping + self.deferred_drops.proof_nodes_bufs.push(nodes); + Ok(()) } @@ -457,7 +495,7 @@ where &mut self, nodes: Vec, ) -> SparseStateTrieResult<()> { - let FilteredV2ProofNodes { root_node, nodes, new_nodes, metric_values: _metric_values } = + let FilteredV2ProofNodes { root_node, mut nodes, new_nodes, metric_values: _metric_values } = filter_revealed_v2_proof_nodes(nodes, &mut self.revealed_account_paths)?; #[cfg(feature = "metrics")] @@ -476,7 +514,10 @@ where trie.reserve_nodes(new_nodes); trace!(target: "trie::sparse", total_nodes = ?nodes.len(), "Revealing account nodes from V2 proof"); - trie.reveal_nodes(nodes)?; + trie.reveal_nodes(&mut nodes)?; + + // Keep buffer for deferred dropping + self.deferred_drops.proof_nodes_bufs.push(nodes); Ok(()) } @@ -496,6 +537,7 @@ where nodes, revealed_paths, trie, + &mut self.deferred_drops.proof_nodes_bufs, self.retain_updates, )?; @@ -515,9 +557,10 @@ where nodes: Vec, revealed_nodes: &mut HashSet, trie: &mut RevealableSparseTrie, + bufs: &mut Vec>, retain_updates: bool, ) -> SparseStateTrieResult { - let FilteredV2ProofNodes { root_node, nodes, new_nodes, metric_values } = + let FilteredV2ProofNodes { root_node, mut nodes, new_nodes, metric_values } = filter_revealed_v2_proof_nodes(nodes, revealed_nodes)?; let trie = if let Some(root_node) = root_node { @@ -530,7 +573,10 @@ where trie.reserve_nodes(new_nodes); trace!(target: "trie::sparse", ?account, total_nodes = ?nodes.len(), "Revealing storage nodes from V2 proof"); - trie.reveal_nodes(nodes)?; + trie.reveal_nodes(&mut nodes)?; + + // Keep buffer for deferred dropping + bufs.push(nodes); Ok(metric_values) } @@ -558,6 +604,7 @@ where storage_subtree, revealed_paths, trie, + &mut self.deferred_drops.proof_nodes_bufs, self.retain_updates, )?; @@ -577,9 +624,10 @@ where storage_subtree: DecodedStorageMultiProof, revealed_nodes: &mut HashSet, trie: &mut RevealableSparseTrie, + bufs: &mut Vec>, retain_updates: bool, ) -> SparseStateTrieResult { - let FilterMappedProofNodes { root_node, nodes, new_nodes, metric_values } = + let FilterMappedProofNodes { root_node, mut nodes, new_nodes, metric_values } = filter_map_revealed_nodes( storage_subtree.subtree, revealed_nodes, @@ -596,9 +644,12 @@ where trie.reserve_nodes(new_nodes); trace!(target: "trie::sparse", ?account, total_nodes = ?nodes.len(), "Revealing storage nodes"); - trie.reveal_nodes(nodes)?; + trie.reveal_nodes(&mut nodes)?; } + // Keep buffer for deferred dropping + bufs.push(nodes); + Ok(metric_values) } diff --git a/crates/trie/sparse/src/traits.rs b/crates/trie/sparse/src/traits.rs index 077e2fb9d5..2f0f9b3351 100644 --- a/crates/trie/sparse/src/traits.rs +++ b/crates/trie/sparse/src/traits.rs @@ -2,7 +2,7 @@ use core::fmt::Debug; -use alloc::{borrow::Cow, vec, vec::Vec}; +use alloc::{borrow::Cow, vec::Vec}; use alloy_primitives::{ map::{B256Map, HashMap, HashSet}, B256, @@ -102,7 +102,7 @@ pub trait SparseTrie: Sized + Debug + Send + Sync { node: TrieNode, masks: Option, ) -> SparseTrieResult<()> { - self.reveal_nodes(vec![ProofTrieNode { path, node, masks }]) + self.reveal_nodes(&mut [ProofTrieNode { path, node, masks }]) } /// Reveals one or more trie nodes if they have not been revealed before. @@ -119,7 +119,12 @@ pub trait SparseTrie: Sized + Debug + Send + Sync { /// # Returns /// /// `Ok(())` if successful, or an error if any of the nodes was not revealed. - fn reveal_nodes(&mut self, nodes: Vec) -> SparseTrieResult<()>; + /// + /// # Note + /// + /// The implementation may modify the input nodes. A common thing to do is [`std::mem::replace`] + /// each node with [`TrieNode::EmptyRoot`] to avoid cloning. + fn reveal_nodes(&mut self, nodes: &mut [ProofTrieNode]) -> SparseTrieResult<()>; /// Updates the value of a leaf node at the specified path. /// diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 21120e7bb3..365bfdecb7 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -476,6 +476,7 @@ impl SparseTrieTrait for SerialSparseTrie { fn reserve_nodes(&mut self, additional: usize) { self.nodes.reserve(additional); } + fn reveal_node( &mut self, path: Nibbles, @@ -618,10 +619,14 @@ impl SparseTrieTrait for SerialSparseTrie { Ok(()) } - fn reveal_nodes(&mut self, mut nodes: Vec) -> SparseTrieResult<()> { + fn reveal_nodes(&mut self, nodes: &mut [ProofTrieNode]) -> SparseTrieResult<()> { nodes.sort_unstable_by_key(|node| node.path); - for node in nodes { - self.reveal_node(node.path, node.node, node.masks)?; + for node in nodes.iter_mut() { + self.reveal_node( + node.path, + core::mem::replace(&mut node.node, TrieNode::EmptyRoot), + node.masks.take(), + )?; } Ok(()) }