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