Skip to main content

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