reth_trie_sparse/traits.rs
1//! Traits for sparse trie implementations.
2
3use core::fmt::Debug;
4
5use alloc::{borrow::Cow, vec::Vec};
6use alloy_primitives::{
7 map::{B256Map, HashMap, HashSet},
8 B256,
9};
10use alloy_trie::BranchNodeCompact;
11use reth_execution_errors::SparseTrieResult;
12use reth_trie_common::{BranchNodeMasks, Nibbles, ProofTrieNodeV2, TrieNodeV2};
13
14#[cfg(feature = "trie-debug")]
15use crate::debug_recorder::TrieDebugRecorder;
16use crate::provider::TrieNodeProvider;
17
18/// Describes an update to a leaf in the sparse trie.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum LeafUpdate {
21 /// The leaf value has been changed to the given RLP-encoded value.
22 /// Empty Vec indicates the leaf has been removed.
23 Changed(Vec<u8>),
24 /// The leaf value may have changed, but the new value is not yet known.
25 /// Used for optimistic prewarming when the actual value is unavailable.
26 Touched,
27}
28
29impl LeafUpdate {
30 /// Returns true if the leaf update is a change.
31 pub const fn is_changed(&self) -> bool {
32 matches!(self, Self::Changed(_))
33 }
34
35 /// Returns true if the leaf update is a touched update.
36 pub const fn is_touched(&self) -> bool {
37 matches!(self, Self::Touched)
38 }
39}
40
41/// Trait defining common operations for revealed sparse trie implementations.
42///
43/// This trait abstracts over different sparse trie implementations (serial vs parallel)
44/// while providing a unified interface for the core trie operations needed by the
45/// [`crate::RevealableSparseTrie`] enum.
46pub trait SparseTrie: Sized + Debug + Send + Sync {
47 /// Configures the trie to have the given root node revealed.
48 ///
49 /// # Arguments
50 ///
51 /// * `root` - The root node to reveal
52 /// * `masks` - Trie masks for root branch node
53 /// * `retain_updates` - Whether to track updates
54 ///
55 /// # Returns
56 ///
57 /// `Ok(())` if successful, or an error if revealing fails.
58 ///
59 /// # Panics
60 ///
61 /// May panic if the trie is not new/cleared, and has already revealed nodes.
62 fn set_root(
63 &mut self,
64 root: TrieNodeV2,
65 masks: Option<BranchNodeMasks>,
66 retain_updates: bool,
67 ) -> SparseTrieResult<()>;
68
69 /// Configures the trie to have the given root node revealed.
70 ///
71 /// See [`Self::set_root`] for more details.
72 fn with_root(
73 mut self,
74 root: TrieNodeV2,
75 masks: Option<BranchNodeMasks>,
76 retain_updates: bool,
77 ) -> SparseTrieResult<Self> {
78 self.set_root(root, masks, retain_updates)?;
79 Ok(self)
80 }
81
82 /// Configures the trie to retain information about updates.
83 ///
84 /// If `retain_updates` is true, the trie will record branch node updates
85 /// and deletions. This information can be used to efficiently update
86 /// an external database.
87 ///
88 /// # Arguments
89 ///
90 /// * `retain_updates` - Whether to track updates
91 fn set_updates(&mut self, retain_updates: bool);
92
93 /// Configures the trie to retain information about updates.
94 ///
95 /// See [`Self::set_updates`] for more details.
96 fn with_updates(mut self, retain_updates: bool) -> Self {
97 self.set_updates(retain_updates);
98 self
99 }
100
101 /// Reserves capacity for additional trie nodes.
102 ///
103 /// # Arguments
104 ///
105 /// * `additional` - The number of additional trie nodes to reserve capacity for.
106 fn reserve_nodes(&mut self, _additional: usize) {}
107
108 /// The single-node version of `reveal_nodes`.
109 ///
110 /// # Returns
111 ///
112 /// `Ok(())` if successful, or an error if the node was not revealed.
113 fn reveal_node(
114 &mut self,
115 path: Nibbles,
116 node: TrieNodeV2,
117 masks: Option<BranchNodeMasks>,
118 ) -> SparseTrieResult<()> {
119 self.reveal_nodes(&mut [ProofTrieNodeV2 { path, node, masks }])
120 }
121
122 /// Reveals one or more trie nodes if they have not been revealed before.
123 ///
124 /// This function decodes trie nodes and inserts them into the trie structure. It handles
125 /// different node types (leaf, extension, branch) by appropriately adding them to the trie and
126 /// recursively revealing their children.
127 ///
128 /// # Arguments
129 ///
130 /// * `nodes` - The nodes to be revealed, each having a path and optional set of branch node
131 /// masks. The nodes will be unsorted.
132 ///
133 /// # Returns
134 ///
135 /// `Ok(())` if successful, or an error if any of the nodes was not revealed.
136 ///
137 /// # Note
138 ///
139 /// The implementation may modify the input nodes. A common thing to do is [`std::mem::replace`]
140 /// each node with [`TrieNodeV2::EmptyRoot`] to avoid cloning.
141 fn reveal_nodes(&mut self, nodes: &mut [ProofTrieNodeV2]) -> SparseTrieResult<()>;
142
143 /// Updates the value of a leaf node at the specified path.
144 ///
145 /// If the leaf doesn't exist, it will be created.
146 /// If it does exist, its value will be updated.
147 ///
148 /// # Arguments
149 ///
150 /// * `full_path` - The full path to the leaf
151 /// * `value` - The new value for the leaf
152 /// * `provider` - The trie provider for resolving missing nodes
153 ///
154 /// # Returns
155 ///
156 /// `Ok(())` if successful, or an error if the update failed.
157 fn update_leaf<P: TrieNodeProvider>(
158 &mut self,
159 full_path: Nibbles,
160 value: Vec<u8>,
161 provider: P,
162 ) -> SparseTrieResult<()>;
163
164 /// Removes a leaf node at the specified path.
165 ///
166 /// This will also handle collapsing the trie structure as needed
167 /// (e.g., removing branch nodes that become unnecessary).
168 ///
169 /// # Arguments
170 ///
171 /// * `full_path` - The full path to the leaf to remove
172 /// * `provider` - The trie node provider for resolving missing nodes
173 ///
174 /// # Returns
175 ///
176 /// `Ok(())` if successful, or an error if the removal failed.
177 fn remove_leaf<P: TrieNodeProvider>(
178 &mut self,
179 full_path: &Nibbles,
180 provider: P,
181 ) -> SparseTrieResult<()>;
182
183 /// Calculates and returns the root hash of the trie.
184 ///
185 /// This processes any dirty nodes by updating their RLP encodings
186 /// and returns the root hash.
187 ///
188 /// # Returns
189 ///
190 /// The root hash of the trie.
191 fn root(&mut self) -> B256;
192
193 /// Returns true if the root node is cached and does not need any recomputation.
194 fn is_root_cached(&self) -> bool;
195
196 /// Recalculates and updates the RLP hashes of subtries deeper than a certain level. The level
197 /// is defined in the implementation.
198 ///
199 /// The root node is considered to be at level 0. This method is useful for optimizing
200 /// hash recalculations after localized changes to the trie structure.
201 fn update_subtrie_hashes(&mut self);
202
203 /// Retrieves a reference to the leaf value at the specified path.
204 ///
205 /// # Arguments
206 ///
207 /// * `full_path` - The full path to the leaf value
208 ///
209 /// # Returns
210 ///
211 /// A reference to the leaf value stored at the given full path, if it is revealed.
212 ///
213 /// Note: a value can exist in the full trie and this function still returns `None`
214 /// because the value has not been revealed.
215 ///
216 /// Hence a `None` indicates two possibilities:
217 /// - The value does not exists in the trie, so it cannot be revealed
218 /// - The value has not yet been revealed. In order to determine which is true, one would need
219 /// an exclusion proof.
220 fn get_leaf_value(&self, full_path: &Nibbles) -> Option<&Vec<u8>>;
221
222 /// Attempts to find a leaf node at the specified path.
223 ///
224 /// This method traverses the trie from the root down to the given path, checking
225 /// if a leaf exists at that path. It can be used to verify the existence of a leaf
226 /// or to generate an exclusion proof (proof that a leaf does not exist).
227 ///
228 /// # Parameters
229 ///
230 /// - `full_path`: The path to search for.
231 /// - `expected_value`: Optional expected value. If provided, will verify the leaf value
232 /// matches.
233 ///
234 /// # Returns
235 ///
236 /// - `Ok(LeafLookup::Exists)` if the leaf exists with the expected value.
237 /// - `Ok(LeafLookup::NonExistent)` if the leaf definitely does not exist (exclusion proof).
238 /// - `Err(LeafLookupError)` if the search encountered a blinded node or found a different
239 /// value.
240 fn find_leaf(
241 &self,
242 full_path: &Nibbles,
243 expected_value: Option<&Vec<u8>>,
244 ) -> Result<LeafLookup, LeafLookupError>;
245
246 /// Returns a reference to the current sparse trie updates.
247 ///
248 /// If no updates have been made/recorded, returns an empty update set.
249 fn updates_ref(&self) -> Cow<'_, SparseTrieUpdates>;
250
251 /// Consumes and returns the currently accumulated trie updates.
252 ///
253 /// This is useful when you want to apply the updates to an external database
254 /// and then start tracking a new set of updates.
255 ///
256 /// # Returns
257 ///
258 /// The accumulated updates, or an empty set if updates weren't being tracked.
259 fn take_updates(&mut self) -> SparseTrieUpdates;
260
261 /// Removes all nodes and values from the trie, resetting it to a blank state
262 /// with only an empty root node. This is used when a storage root is deleted.
263 ///
264 /// This should not be used when intending to reuse the trie for a fresh account/storage root;
265 /// use `clear` for that.
266 ///
267 /// Note: All previously tracked changes to the trie are also removed.
268 fn wipe(&mut self);
269
270 /// This clears all data structures in the sparse trie, keeping the backing data structures
271 /// allocated. A [`crate::SparseNode::Empty`] is inserted at the root.
272 ///
273 /// This is useful for reusing the trie without needing to reallocate memory.
274 fn clear(&mut self);
275
276 /// Shrink the capacity of the sparse trie's node storage to the given size.
277 /// This will reduce memory usage if the current capacity is higher than the given size.
278 fn shrink_nodes_to(&mut self, size: usize);
279
280 /// Shrink the capacity of the sparse trie's value storage to the given size.
281 /// This will reduce memory usage if the current capacity is higher than the given size.
282 fn shrink_values_to(&mut self, size: usize);
283
284 /// Returns a cheap O(1) size hint for the trie representing the count of revealed
285 /// (non-Hash) nodes.
286 ///
287 /// This is used as a heuristic for prioritizing which storage tries to keep
288 /// during pruning. Larger values indicate larger tries that are more valuable to preserve.
289 fn size_hint(&self) -> usize;
290
291 /// Returns a heuristic for the in-memory size of this trie in bytes.
292 ///
293 /// This is an approximation that accounts for the trie's nodes, values,
294 /// and auxiliary data structures.
295 fn memory_size(&self) -> usize;
296
297 /// Prunes all subtrees that do not contain retained leaves.
298 ///
299 /// Each retained leaf is a full key path (usually 64 nibbles for hashed keys).
300 /// Any revealed subtree that is not a prefix of at least one retained key is collapsed into
301 /// hash stubs when hashes are available.
302 ///
303 /// # Preconditions
304 ///
305 /// Must be called after `root()` to ensure all nodes have computed hashes.
306 /// Calling on a trie without computed hashes will result in limited or no pruning.
307 ///
308 /// # Returns
309 ///
310 /// The number of nodes converted to hash stubs.
311 fn prune(&mut self, retained_leaves: &[Nibbles]) -> usize;
312
313 /// Takes the debug recorder out of this trie, replacing it with an empty one.
314 ///
315 /// Returns the recorder containing all recorded mutations since the last reset.
316 /// The default implementation returns an empty recorder.
317 #[cfg(feature = "trie-debug")]
318 fn take_debug_recorder(&mut self) -> TrieDebugRecorder {
319 TrieDebugRecorder::default()
320 }
321
322 /// Applies leaf updates to the sparse trie.
323 ///
324 /// When a [`LeafUpdate::Changed`] is successfully applied, it is removed from the
325 /// given [`B256Map`]. If it could not be applied due to blinded nodes, it remains
326 /// in the map and the callback is invoked with the required proof target.
327 ///
328 /// Once that proof is calculated and revealed via [`SparseTrie::reveal_nodes`], the same
329 /// `updates` map can be reused to retry the update.
330 ///
331 /// The callback receives `(key, min_len)` where `key` is the full 32-byte hashed key
332 /// (right-padded with zeros from the blinded path) and `min_len` is the minimum depth
333 /// at which proof nodes should be returned.
334 ///
335 /// The callback may be invoked multiple times for the same target across retry loops.
336 /// Callers should deduplicate if needed.
337 ///
338 /// [`LeafUpdate::Touched`] behaves identically except it does not modify the leaf value.
339 fn update_leaves(
340 &mut self,
341 updates: &mut B256Map<LeafUpdate>,
342 proof_required_fn: impl FnMut(B256, u8),
343 ) -> SparseTrieResult<()>;
344
345 /// Commits the updated nodes to internal trie state.
346 fn commit_updates(
347 &mut self,
348 updated: &HashMap<Nibbles, BranchNodeCompact>,
349 removed: &HashSet<Nibbles>,
350 );
351}
352
353/// Tracks modifications to the sparse trie structure.
354///
355/// Maintains references to both modified and pruned/removed branches, enabling
356/// one to make batch updates to a persistent database.
357#[derive(Debug, Clone, Default, PartialEq, Eq)]
358pub struct SparseTrieUpdates {
359 /// Collection of updated intermediate nodes indexed by full path.
360 pub updated_nodes: HashMap<Nibbles, BranchNodeCompact>,
361 /// Collection of removed intermediate nodes indexed by full path.
362 pub removed_nodes: HashSet<Nibbles>,
363 /// Flag indicating whether the trie was wiped.
364 pub wiped: bool,
365}
366
367impl SparseTrieUpdates {
368 /// Initialize a [`Self`] with given capacities.
369 pub fn with_capacity(num_updated_nodes: usize, num_removed_nodes: usize) -> Self {
370 Self {
371 updated_nodes: HashMap::with_capacity_and_hasher(num_updated_nodes, Default::default()),
372 removed_nodes: HashSet::with_capacity_and_hasher(num_removed_nodes, Default::default()),
373 wiped: false,
374 }
375 }
376}
377
378/// Error type for a leaf lookup operation
379#[derive(Debug, Clone, PartialEq, Eq)]
380pub enum LeafLookupError {
381 /// The path leads to a blinded node, cannot determine if leaf exists.
382 /// This means the witness is not complete.
383 BlindedNode {
384 /// Path to the blinded node.
385 path: Nibbles,
386 /// Hash of the blinded node.
387 hash: B256,
388 },
389 /// The path leads to a leaf with a different value than expected.
390 /// This means the witness is malformed.
391 ValueMismatch {
392 /// Path to the leaf.
393 path: Nibbles,
394 /// Expected value.
395 expected: Option<Vec<u8>>,
396 /// Actual value found.
397 actual: Vec<u8>,
398 },
399}
400
401/// Success value for a leaf lookup operation
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub enum LeafLookup {
404 /// Leaf exists with expected value.
405 Exists,
406 /// Leaf does not exist (exclusion proof found).
407 NonExistent,
408}
409
410#[cfg(feature = "std")]
411mod configurable_sparse_trie {
412 use super::*;
413 use crate::{arena::ArenaParallelSparseTrie, parallel::ParallelSparseTrie};
414
415 /// An enum wrapping two different [`SparseTrie`] implementations, forwarding all calls to the
416 /// underlying trie.
417 #[derive(Debug, Clone)]
418 pub enum ConfigurableSparseTrie {
419 /// The arena-based parallel sparse trie implementation.
420 Arena(ArenaParallelSparseTrie),
421 /// The hash-map-based parallel sparse trie implementation.
422 HashMap(ParallelSparseTrie),
423 }
424
425 impl Default for ConfigurableSparseTrie {
426 fn default() -> Self {
427 Self::Arena(ArenaParallelSparseTrie::default())
428 }
429 }
430
431 macro_rules! delegate {
432 ($self:ident, $method:ident $(, $arg:expr)*) => {
433 match $self {
434 Self::Arena(inner) => inner.$method($($arg),*),
435 Self::HashMap(inner) => inner.$method($($arg),*),
436 }
437 };
438 }
439
440 impl SparseTrie for ConfigurableSparseTrie {
441 fn set_root(
442 &mut self,
443 root: TrieNodeV2,
444 masks: Option<BranchNodeMasks>,
445 retain_updates: bool,
446 ) -> SparseTrieResult<()> {
447 delegate!(self, set_root, root, masks, retain_updates)
448 }
449
450 fn set_updates(&mut self, retain_updates: bool) {
451 delegate!(self, set_updates, retain_updates)
452 }
453
454 fn reserve_nodes(&mut self, additional: usize) {
455 delegate!(self, reserve_nodes, additional)
456 }
457
458 fn reveal_nodes(&mut self, nodes: &mut [ProofTrieNodeV2]) -> SparseTrieResult<()> {
459 delegate!(self, reveal_nodes, nodes)
460 }
461
462 fn update_leaf<P: TrieNodeProvider>(
463 &mut self,
464 full_path: Nibbles,
465 value: Vec<u8>,
466 provider: P,
467 ) -> SparseTrieResult<()> {
468 delegate!(self, update_leaf, full_path, value, provider)
469 }
470
471 fn remove_leaf<P: TrieNodeProvider>(
472 &mut self,
473 full_path: &Nibbles,
474 provider: P,
475 ) -> SparseTrieResult<()> {
476 delegate!(self, remove_leaf, full_path, provider)
477 }
478
479 fn root(&mut self) -> B256 {
480 delegate!(self, root)
481 }
482
483 fn is_root_cached(&self) -> bool {
484 delegate!(self, is_root_cached)
485 }
486
487 fn update_subtrie_hashes(&mut self) {
488 delegate!(self, update_subtrie_hashes)
489 }
490
491 fn get_leaf_value(&self, full_path: &Nibbles) -> Option<&Vec<u8>> {
492 delegate!(self, get_leaf_value, full_path)
493 }
494
495 fn find_leaf(
496 &self,
497 full_path: &Nibbles,
498 expected_value: Option<&Vec<u8>>,
499 ) -> Result<LeafLookup, LeafLookupError> {
500 delegate!(self, find_leaf, full_path, expected_value)
501 }
502
503 fn updates_ref(&self) -> Cow<'_, SparseTrieUpdates> {
504 delegate!(self, updates_ref)
505 }
506
507 fn take_updates(&mut self) -> SparseTrieUpdates {
508 delegate!(self, take_updates)
509 }
510
511 fn wipe(&mut self) {
512 delegate!(self, wipe)
513 }
514
515 fn clear(&mut self) {
516 delegate!(self, clear)
517 }
518
519 fn shrink_nodes_to(&mut self, size: usize) {
520 delegate!(self, shrink_nodes_to, size)
521 }
522
523 fn shrink_values_to(&mut self, size: usize) {
524 delegate!(self, shrink_values_to, size)
525 }
526
527 fn size_hint(&self) -> usize {
528 delegate!(self, size_hint)
529 }
530
531 fn memory_size(&self) -> usize {
532 delegate!(self, memory_size)
533 }
534
535 fn prune(&mut self, retained_leaves: &[Nibbles]) -> usize {
536 delegate!(self, prune, retained_leaves)
537 }
538
539 #[cfg(feature = "trie-debug")]
540 fn take_debug_recorder(&mut self) -> TrieDebugRecorder {
541 delegate!(self, take_debug_recorder)
542 }
543
544 fn update_leaves(
545 &mut self,
546 updates: &mut B256Map<LeafUpdate>,
547 proof_required_fn: impl FnMut(B256, u8),
548 ) -> SparseTrieResult<()> {
549 delegate!(self, update_leaves, updates, proof_required_fn)
550 }
551
552 fn commit_updates(
553 &mut self,
554 updated: &HashMap<Nibbles, BranchNodeCompact>,
555 removed: &HashSet<Nibbles>,
556 ) {
557 delegate!(self, commit_updates, updated, removed)
558 }
559 }
560}
561
562#[cfg(feature = "std")]
563pub use configurable_sparse_trie::ConfigurableSparseTrie;