perf(trie): reuse proof nodes buffer in reveal_nodes (#21648)

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Alexey Shekhirin <github@shekhirin.com>
This commit is contained in:
Georgios Konstantopoulos
2026-02-04 08:35:03 -08:00
committed by GitHub
parent 543c77a374
commit 12d0b74a16
6 changed files with 138 additions and 66 deletions

View File

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

View File

@@ -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<A, S> {
) -> (SparseStateTrie<A, S>, 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<A, S> {
) -> (SparseStateTrie<A, S>, 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<A, S> {
) -> (SparseStateTrie<A, S>, 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<A, S> {
) -> (SparseStateTrie<A, S>, 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<A, S> {
) -> (SparseStateTrie<A, S>, 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.

View File

@@ -175,7 +175,7 @@ impl SparseTrie for ParallelSparseTrie {
self.updates = retain_updates.then(Default::default);
}
fn reveal_nodes(&mut self, mut nodes: Vec<ProofTrieNode>) -> 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<ProofTrieNode> = hash_builder_proof_nodes
let mut revealed_nodes: Vec<ProofTrieNode> = 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<ProofTrieNode> = hash_builder_proof_nodes
let mut revealed_nodes: Vec<ProofTrieNode> = 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<ProofTrieNode> = hash_builder_proof_nodes
let mut revealed_nodes: Vec<ProofTrieNode> = 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<ProofTrieNode> = hash_builder_proof_nodes
let mut revealed_nodes: Vec<ProofTrieNode> = 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<ProofTrieNode> = hash_builder_proof_nodes
let mut revealed_nodes: Vec<ProofTrieNode> = 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();

View File

@@ -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<Vec<ProofTrieNode>>,
}
#[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<u8>,
/// 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<A, S> SparseStateTrie<A, S> {
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<A, S> SparseStateTrie<A, S>
@@ -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<ProofTrieNode>,
) -> 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<ProofTrieNode>,
revealed_nodes: &mut HashSet<Nibbles>,
trie: &mut RevealableSparseTrie<S>,
bufs: &mut Vec<Vec<ProofTrieNode>>,
retain_updates: bool,
) -> SparseStateTrieResult<ProofNodesMetricValues> {
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<Nibbles>,
trie: &mut RevealableSparseTrie<S>,
bufs: &mut Vec<Vec<ProofTrieNode>>,
retain_updates: bool,
) -> SparseStateTrieResult<ProofNodesMetricValues> {
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)
}

View File

@@ -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<BranchNodeMasks>,
) -> 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<ProofTrieNode>) -> 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.
///

View File

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