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, ProofTrieNode, TrieNode};
13
14use crate::provider::TrieNodeProvider;
15
16/// Describes an update to a leaf in the sparse trie.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum LeafUpdate {
19    /// The leaf value has been changed to the given RLP-encoded value.
20    /// Empty Vec indicates the leaf has been removed.
21    Changed(Vec<u8>),
22    /// The leaf value may have changed, but the new value is not yet known.
23    /// Used for optimistic prewarming when the actual value is unavailable.
24    Touched,
25}
26
27impl LeafUpdate {
28    /// Returns true if the leaf update is a change.
29    pub const fn is_changed(&self) -> bool {
30        matches!(self, Self::Changed(_))
31    }
32
33    /// Returns true if the leaf update is a touched update.
34    pub const fn is_touched(&self) -> bool {
35        matches!(self, Self::Touched)
36    }
37}
38
39/// Trait defining common operations for revealed sparse trie implementations.
40///
41/// This trait abstracts over different sparse trie implementations (serial vs parallel)
42/// while providing a unified interface for the core trie operations needed by the
43/// [`crate::RevealableSparseTrie`] enum.
44pub trait SparseTrie: Sized + Debug + Send + Sync {
45    /// Configures the trie to have the given root node revealed.
46    ///
47    /// # Arguments
48    ///
49    /// * `root` - The root node to reveal
50    /// * `masks` - Trie masks for root branch node
51    /// * `retain_updates` - Whether to track updates
52    ///
53    /// # Returns
54    ///
55    /// `Ok(())` if successful, or an error if revealing fails.
56    ///
57    /// # Panics
58    ///
59    /// May panic if the trie is not new/cleared, and has already revealed nodes.
60    fn set_root(
61        &mut self,
62        root: TrieNode,
63        masks: Option<BranchNodeMasks>,
64        retain_updates: bool,
65    ) -> SparseTrieResult<()>;
66
67    /// Configures the trie to have the given root node revealed.
68    ///
69    /// See [`Self::set_root`] for more details.
70    fn with_root(
71        mut self,
72        root: TrieNode,
73        masks: Option<BranchNodeMasks>,
74        retain_updates: bool,
75    ) -> SparseTrieResult<Self> {
76        self.set_root(root, masks, retain_updates)?;
77        Ok(self)
78    }
79
80    /// Configures the trie to retain information about updates.
81    ///
82    /// If `retain_updates` is true, the trie will record branch node updates
83    /// and deletions. This information can be used to efficiently update
84    /// an external database.
85    ///
86    /// # Arguments
87    ///
88    /// * `retain_updates` - Whether to track updates
89    fn set_updates(&mut self, retain_updates: bool);
90
91    /// Configures the trie to retain information about updates.
92    ///
93    /// See [`Self::set_updates`] for more details.
94    fn with_updates(mut self, retain_updates: bool) -> Self {
95        self.set_updates(retain_updates);
96        self
97    }
98
99    /// Reserves capacity for additional trie nodes.
100    ///
101    /// # Arguments
102    ///
103    /// * `additional` - The number of additional trie nodes to reserve capacity for.
104    fn reserve_nodes(&mut self, _additional: usize) {}
105
106    /// The single-node version of `reveal_nodes`.
107    ///
108    /// # Returns
109    ///
110    /// `Ok(())` if successful, or an error if the node was not revealed.
111    fn reveal_node(
112        &mut self,
113        path: Nibbles,
114        node: TrieNode,
115        masks: Option<BranchNodeMasks>,
116    ) -> SparseTrieResult<()> {
117        self.reveal_nodes(&mut [ProofTrieNode { path, node, masks }])
118    }
119
120    /// Reveals one or more trie nodes if they have not been revealed before.
121    ///
122    /// This function decodes trie nodes and inserts them into the trie structure. It handles
123    /// different node types (leaf, extension, branch) by appropriately adding them to the trie and
124    /// recursively revealing their children.
125    ///
126    /// # Arguments
127    ///
128    /// * `nodes` - The nodes to be revealed, each having a path and optional set of branch node
129    ///   masks. The nodes will be unsorted.
130    ///
131    /// # Returns
132    ///
133    /// `Ok(())` if successful, or an error if any of the nodes was not revealed.
134    ///
135    /// # Note
136    ///
137    /// The implementation may modify the input nodes. A common thing to do is [`std::mem::replace`]
138    /// each node with [`TrieNode::EmptyRoot`] to avoid cloning.
139    fn reveal_nodes(&mut self, nodes: &mut [ProofTrieNode]) -> SparseTrieResult<()>;
140
141    /// Updates the value of a leaf node at the specified path.
142    ///
143    /// If the leaf doesn't exist, it will be created.
144    /// If it does exist, its value will be updated.
145    ///
146    /// # Arguments
147    ///
148    /// * `full_path` - The full path to the leaf
149    /// * `value` - The new value for the leaf
150    /// * `provider` - The trie provider for resolving missing nodes
151    ///
152    /// # Returns
153    ///
154    /// `Ok(())` if successful, or an error if the update failed.
155    fn update_leaf<P: TrieNodeProvider>(
156        &mut self,
157        full_path: Nibbles,
158        value: Vec<u8>,
159        provider: P,
160    ) -> SparseTrieResult<()>;
161
162    /// Removes a leaf node at the specified path.
163    ///
164    /// This will also handle collapsing the trie structure as needed
165    /// (e.g., removing branch nodes that become unnecessary).
166    ///
167    /// # Arguments
168    ///
169    /// * `full_path` - The full path to the leaf to remove
170    /// * `provider` - The trie node provider for resolving missing nodes
171    ///
172    /// # Returns
173    ///
174    /// `Ok(())` if successful, or an error if the removal failed.
175    fn remove_leaf<P: TrieNodeProvider>(
176        &mut self,
177        full_path: &Nibbles,
178        provider: P,
179    ) -> SparseTrieResult<()>;
180
181    /// Calculates and returns the root hash of the trie.
182    ///
183    /// This processes any dirty nodes by updating their RLP encodings
184    /// and returns the root hash.
185    ///
186    /// # Returns
187    ///
188    /// The root hash of the trie.
189    fn root(&mut self) -> B256;
190
191    /// Returns true if the root node is cached and does not need any recomputation.
192    fn is_root_cached(&self) -> bool;
193
194    /// Recalculates and updates the RLP hashes of subtries deeper than a certain level. The level
195    /// is defined in the implementation.
196    ///
197    /// The root node is considered to be at level 0. This method is useful for optimizing
198    /// hash recalculations after localized changes to the trie structure.
199    fn update_subtrie_hashes(&mut self);
200
201    /// Retrieves a reference to the leaf value at the specified path.
202    ///
203    /// # Arguments
204    ///
205    /// * `full_path` - The full path to the leaf value
206    ///
207    /// # Returns
208    ///
209    /// A reference to the leaf value stored at the given full path, if it is revealed.
210    ///
211    /// Note: a value can exist in the full trie and this function still returns `None`
212    /// because the value has not been revealed.
213    ///
214    /// Hence a `None` indicates two possibilities:
215    /// - The value does not exists in the trie, so it cannot be revealed
216    /// - The value has not yet been revealed. In order to determine which is true, one would need
217    ///   an exclusion proof.
218    fn get_leaf_value(&self, full_path: &Nibbles) -> Option<&Vec<u8>>;
219
220    /// Attempts to find a leaf node at the specified path.
221    ///
222    /// This method traverses the trie from the root down to the given path, checking
223    /// if a leaf exists at that path. It can be used to verify the existence of a leaf
224    /// or to generate an exclusion proof (proof that a leaf does not exist).
225    ///
226    /// # Parameters
227    ///
228    /// - `full_path`: The path to search for.
229    /// - `expected_value`: Optional expected value. If provided, will verify the leaf value
230    ///   matches.
231    ///
232    /// # Returns
233    ///
234    /// - `Ok(LeafLookup::Exists)` if the leaf exists with the expected value.
235    /// - `Ok(LeafLookup::NonExistent)` if the leaf definitely does not exist (exclusion proof).
236    /// - `Err(LeafLookupError)` if the search encountered a blinded node or found a different
237    ///   value.
238    fn find_leaf(
239        &self,
240        full_path: &Nibbles,
241        expected_value: Option<&Vec<u8>>,
242    ) -> Result<LeafLookup, LeafLookupError>;
243
244    /// Returns a reference to the current sparse trie updates.
245    ///
246    /// If no updates have been made/recorded, returns an empty update set.
247    fn updates_ref(&self) -> Cow<'_, SparseTrieUpdates>;
248
249    /// Consumes and returns the currently accumulated trie updates.
250    ///
251    /// This is useful when you want to apply the updates to an external database
252    /// and then start tracking a new set of updates.
253    ///
254    /// # Returns
255    ///
256    /// The accumulated updates, or an empty set if updates weren't being tracked.
257    fn take_updates(&mut self) -> SparseTrieUpdates;
258
259    /// Removes all nodes and values from the trie, resetting it to a blank state
260    /// with only an empty root node. This is used when a storage root is deleted.
261    ///
262    /// This should not be used when intending to reuse the trie for a fresh account/storage root;
263    /// use `clear` for that.
264    ///
265    /// Note: All previously tracked changes to the trie are also removed.
266    fn wipe(&mut self);
267
268    /// This clears all data structures in the sparse trie, keeping the backing data structures
269    /// allocated. A [`crate::SparseNode::Empty`] is inserted at the root.
270    ///
271    /// This is useful for reusing the trie without needing to reallocate memory.
272    fn clear(&mut self);
273
274    /// Shrink the capacity of the sparse trie's node storage to the given size.
275    /// This will reduce memory usage if the current capacity is higher than the given size.
276    fn shrink_nodes_to(&mut self, size: usize);
277
278    /// Shrink the capacity of the sparse trie's value storage to the given size.
279    /// This will reduce memory usage if the current capacity is higher than the given size.
280    fn shrink_values_to(&mut self, size: usize);
281
282    /// Returns a cheap O(1) size hint for the trie representing the count of revealed
283    /// (non-Hash) nodes.
284    ///
285    /// This is used as a heuristic for prioritizing which storage tries to keep
286    /// during pruning. Larger values indicate larger tries that are more valuable to preserve.
287    fn size_hint(&self) -> usize;
288
289    /// Replaces nodes beyond `max_depth` with hash stubs and removes their descendants.
290    ///
291    /// Depth counts nodes traversed (not nibbles), so extension nodes count as 1 depth
292    /// regardless of key length. `max_depth == 0` prunes all children of the root node.
293    ///
294    /// # Preconditions
295    ///
296    /// Must be called after `root()` to ensure all nodes have computed hashes.
297    /// Calling on a trie without computed hashes will result in no pruning.
298    ///
299    /// # Behavior
300    ///
301    /// - Embedded nodes (RLP < 32 bytes) are preserved since they have no hash
302    /// - Returns 0 if `max_depth` exceeds trie depth or trie is empty
303    ///
304    /// # Returns
305    ///
306    /// The number of nodes converted to hash stubs.
307    fn prune(&mut self, max_depth: usize) -> usize;
308
309    /// Applies leaf updates to the sparse trie.
310    ///
311    /// When a [`LeafUpdate::Changed`] is successfully applied, it is removed from the
312    /// given [`B256Map`]. If it could not be applied due to blinded nodes, it remains
313    /// in the map and the callback is invoked with the required proof target.
314    ///
315    /// Once that proof is calculated and revealed via [`SparseTrie::reveal_nodes`], the same
316    /// `updates` map can be reused to retry the update.
317    ///
318    /// The callback receives `(key, min_len)` where `key` is the full 32-byte hashed key
319    /// (right-padded with zeros from the blinded path) and `min_len` is the minimum depth
320    /// at which proof nodes should be returned.
321    ///
322    /// The callback may be invoked multiple times for the same target across retry loops.
323    /// Callers should deduplicate if needed.
324    ///
325    /// [`LeafUpdate::Touched`] behaves identically except it does not modify the leaf value.
326    fn update_leaves(
327        &mut self,
328        updates: &mut B256Map<LeafUpdate>,
329        proof_required_fn: impl FnMut(B256, u8),
330    ) -> SparseTrieResult<()>;
331}
332
333/// Tracks modifications to the sparse trie structure.
334///
335/// Maintains references to both modified and pruned/removed branches, enabling
336/// one to make batch updates to a persistent database.
337#[derive(Debug, Clone, Default, PartialEq, Eq)]
338pub struct SparseTrieUpdates {
339    /// Collection of updated intermediate nodes indexed by full path.
340    pub updated_nodes: HashMap<Nibbles, BranchNodeCompact>,
341    /// Collection of removed intermediate nodes indexed by full path.
342    pub removed_nodes: HashSet<Nibbles>,
343    /// Flag indicating whether the trie was wiped.
344    pub wiped: bool,
345}
346
347impl SparseTrieUpdates {
348    /// Initialize a [`Self`] with given capacities.
349    pub fn with_capacity(num_updated_nodes: usize, num_removed_nodes: usize) -> Self {
350        Self {
351            updated_nodes: HashMap::with_capacity_and_hasher(num_updated_nodes, Default::default()),
352            removed_nodes: HashSet::with_capacity_and_hasher(num_removed_nodes, Default::default()),
353            wiped: false,
354        }
355    }
356}
357
358/// Error type for a leaf lookup operation
359#[derive(Debug, Clone, PartialEq, Eq)]
360pub enum LeafLookupError {
361    /// The path leads to a blinded node, cannot determine if leaf exists.
362    /// This means the witness is not complete.
363    BlindedNode {
364        /// Path to the blinded node.
365        path: Nibbles,
366        /// Hash of the blinded node.
367        hash: B256,
368    },
369    /// The path leads to a leaf with a different value than expected.
370    /// This means the witness is malformed.
371    ValueMismatch {
372        /// Path to the leaf.
373        path: Nibbles,
374        /// Expected value.
375        expected: Option<Vec<u8>>,
376        /// Actual value found.
377        actual: Vec<u8>,
378    },
379}
380
381/// Success value for a leaf lookup operation
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub enum LeafLookup {
384    /// Leaf exists with expected value.
385    Exists,
386    /// Leaf does not exist (exclusion proof found).
387    NonExistent,
388}