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;
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    /// Replaces nodes beyond `max_depth` with hash stubs and removes their descendants.
298    ///
299    /// Depth counts nodes traversed (not nibbles), so extension nodes count as 1 depth
300    /// regardless of key length. `max_depth == 0` prunes all children of the root node.
301    ///
302    /// # Preconditions
303    ///
304    /// Must be called after `root()` to ensure all nodes have computed hashes.
305    /// Calling on a trie without computed hashes will result in no pruning.
306    ///
307    /// # Behavior
308    ///
309    /// - Embedded nodes (RLP < 32 bytes) are preserved since they have no hash
310    /// - Returns 0 if `max_depth` exceeds trie depth or trie is empty
311    ///
312    /// # Returns
313    ///
314    /// The number of nodes converted to hash stubs.
315    fn prune(&mut self, max_depth: usize) -> usize;
316
317    /// Takes the debug recorder out of this trie, replacing it with an empty one.
318    ///
319    /// Returns the recorder containing all recorded mutations since the last reset.
320    /// The default implementation returns an empty recorder.
321    #[cfg(feature = "trie-debug")]
322    fn take_debug_recorder(&mut self) -> TrieDebugRecorder {
323        TrieDebugRecorder::default()
324    }
325
326    /// Applies leaf updates to the sparse trie.
327    ///
328    /// When a [`LeafUpdate::Changed`] is successfully applied, it is removed from the
329    /// given [`B256Map`]. If it could not be applied due to blinded nodes, it remains
330    /// in the map and the callback is invoked with the required proof target.
331    ///
332    /// Once that proof is calculated and revealed via [`SparseTrie::reveal_nodes`], the same
333    /// `updates` map can be reused to retry the update.
334    ///
335    /// The callback receives `(key, min_len)` where `key` is the full 32-byte hashed key
336    /// (right-padded with zeros from the blinded path) and `min_len` is the minimum depth
337    /// at which proof nodes should be returned.
338    ///
339    /// The callback may be invoked multiple times for the same target across retry loops.
340    /// Callers should deduplicate if needed.
341    ///
342    /// [`LeafUpdate::Touched`] behaves identically except it does not modify the leaf value.
343    fn update_leaves(
344        &mut self,
345        updates: &mut B256Map<LeafUpdate>,
346        proof_required_fn: impl FnMut(B256, u8),
347    ) -> SparseTrieResult<()>;
348
349    /// Commits the updated nodes to internal trie state.
350    fn commit_updates(
351        &mut self,
352        updated: &HashMap<Nibbles, BranchNodeCompact>,
353        removed: &HashSet<Nibbles>,
354    );
355}
356
357/// Tracks modifications to the sparse trie structure.
358///
359/// Maintains references to both modified and pruned/removed branches, enabling
360/// one to make batch updates to a persistent database.
361#[derive(Debug, Clone, Default, PartialEq, Eq)]
362pub struct SparseTrieUpdates {
363    /// Collection of updated intermediate nodes indexed by full path.
364    pub updated_nodes: HashMap<Nibbles, BranchNodeCompact>,
365    /// Collection of removed intermediate nodes indexed by full path.
366    pub removed_nodes: HashSet<Nibbles>,
367    /// Flag indicating whether the trie was wiped.
368    pub wiped: bool,
369}
370
371impl SparseTrieUpdates {
372    /// Initialize a [`Self`] with given capacities.
373    pub fn with_capacity(num_updated_nodes: usize, num_removed_nodes: usize) -> Self {
374        Self {
375            updated_nodes: HashMap::with_capacity_and_hasher(num_updated_nodes, Default::default()),
376            removed_nodes: HashSet::with_capacity_and_hasher(num_removed_nodes, Default::default()),
377            wiped: false,
378        }
379    }
380}
381
382/// Error type for a leaf lookup operation
383#[derive(Debug, Clone, PartialEq, Eq)]
384pub enum LeafLookupError {
385    /// The path leads to a blinded node, cannot determine if leaf exists.
386    /// This means the witness is not complete.
387    BlindedNode {
388        /// Path to the blinded node.
389        path: Nibbles,
390        /// Hash of the blinded node.
391        hash: B256,
392    },
393    /// The path leads to a leaf with a different value than expected.
394    /// This means the witness is malformed.
395    ValueMismatch {
396        /// Path to the leaf.
397        path: Nibbles,
398        /// Expected value.
399        expected: Option<Vec<u8>>,
400        /// Actual value found.
401        actual: Vec<u8>,
402    },
403}
404
405/// Success value for a leaf lookup operation
406#[derive(Debug, Clone, PartialEq, Eq)]
407pub enum LeafLookup {
408    /// Leaf exists with expected value.
409    Exists,
410    /// Leaf does not exist (exclusion proof found).
411    NonExistent,
412}