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::{
13    prefix_set::PrefixSetMut, BranchNodeMasks, Nibbles, ProofTrieNodeV2, TrieNodeV2,
14};
15
16#[cfg(feature = "trie-debug")]
17use crate::debug_recorder::TrieDebugRecorder;
18
19/// Describes an update to a leaf in the sparse trie.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum LeafUpdate {
22    /// The leaf value has been changed to the given RLP-encoded value.
23    /// Empty Vec indicates the leaf has been removed.
24    Changed(Vec<u8>),
25    /// The leaf value may have changed, but the new value is not yet known.
26    /// Used for optimistic prewarming when the actual value is unavailable.
27    Touched,
28}
29
30impl LeafUpdate {
31    /// Returns true if the leaf update is a change.
32    pub const fn is_changed(&self) -> bool {
33        matches!(self, Self::Changed(_))
34    }
35
36    /// Returns true if the leaf update is a touched update.
37    pub const fn is_touched(&self) -> bool {
38        matches!(self, Self::Touched)
39    }
40}
41
42/// Trait defining common operations for revealed sparse trie implementations.
43///
44/// This trait provides a unified interface for the core trie operations needed by
45/// `RevealableSparseTrie`.
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 retain information about updates.
70    ///
71    /// If `retain_updates` is true, the trie will record branch node updates
72    /// and deletions. This information can be used to efficiently update
73    /// an external database.
74    ///
75    /// # Arguments
76    ///
77    /// * `retain_updates` - Whether to track updates
78    fn set_updates(&mut self, retain_updates: bool);
79
80    /// Configures the trie to retain changed node base paths during hashing.
81    fn set_changed_paths(&mut self, retain_changed_paths: bool);
82
83    /// Reveals one or more trie nodes if they have not been revealed before.
84    ///
85    /// This function decodes trie nodes and inserts them into the trie structure. It handles
86    /// different node types (leaf, extension, branch) by appropriately adding them to the trie and
87    /// recursively revealing their children.
88    ///
89    /// # Arguments
90    ///
91    /// * `nodes` - The nodes to be revealed, each having a path and optional set of branch node
92    ///   masks. The nodes will be unsorted.
93    ///
94    /// # Returns
95    ///
96    /// `Ok(())` if successful, or an error if any of the nodes was not revealed.
97    ///
98    /// # Note
99    ///
100    /// The implementation may modify the input nodes. A common thing to do is [`std::mem::replace`]
101    /// each node with [`TrieNodeV2::EmptyRoot`] to avoid cloning.
102    fn reveal_nodes(&mut self, nodes: &mut [ProofTrieNodeV2]) -> SparseTrieResult<()>;
103
104    /// Calculates and returns the root hash of the trie.
105    ///
106    /// This processes any dirty nodes by updating their RLP encodings
107    /// and returns the root hash.
108    ///
109    /// # Returns
110    ///
111    /// The root hash of the trie.
112    fn root(&mut self) -> B256;
113
114    /// Returns true if the root node is cached and does not need any recomputation.
115    fn is_root_cached(&self) -> bool;
116
117    /// Recalculates and updates the RLP hashes of subtries deeper than a certain level. The level
118    /// is defined in the implementation.
119    ///
120    /// The root node is considered to be at level 0. This method is useful for optimizing
121    /// hash recalculations after localized changes to the trie structure.
122    fn update_subtrie_hashes(&mut self);
123
124    /// Retrieves a reference to the leaf value at the specified path.
125    ///
126    /// # Arguments
127    ///
128    /// * `full_path` - The full path to the leaf value
129    ///
130    /// # Returns
131    ///
132    /// A reference to the leaf value stored at the given full path, if it is revealed.
133    ///
134    /// Note: a value can exist in the full trie and this function still returns `None`
135    /// because the value has not been revealed.
136    ///
137    /// Hence a `None` indicates two possibilities:
138    /// - The value does not exists in the trie, so it cannot be revealed
139    /// - The value has not yet been revealed. In order to determine which is true, one would need
140    ///   an exclusion proof.
141    fn get_leaf_value(&self, full_path: &Nibbles) -> Option<&Vec<u8>>;
142
143    /// Attempts to find a leaf node at the specified path.
144    ///
145    /// This method traverses the trie from the root down to the given path, checking
146    /// if a leaf exists at that path. It can be used to verify the existence of a leaf
147    /// or to generate an exclusion proof (proof that a leaf does not exist).
148    ///
149    /// # Parameters
150    ///
151    /// - `full_path`: The path to search for.
152    /// - `expected_value`: Optional expected value. If provided, will verify the leaf value
153    ///   matches.
154    ///
155    /// # Returns
156    ///
157    /// - `Ok(LeafLookup::Exists)` if the leaf exists with the expected value.
158    /// - `Ok(LeafLookup::NonExistent)` if the leaf definitely does not exist (exclusion proof).
159    /// - `Err(LeafLookupError)` if the search encountered a blinded node or found a different
160    ///   value.
161    fn find_leaf(
162        &self,
163        full_path: &Nibbles,
164        expected_value: Option<&Vec<u8>>,
165    ) -> Result<LeafLookup, LeafLookupError>;
166
167    /// Returns a reference to the current sparse trie updates.
168    ///
169    /// If no updates have been made/recorded, returns an empty update set.
170    fn updates_ref(&self) -> Cow<'_, SparseTrieUpdates>;
171
172    /// Consumes and returns the currently accumulated trie updates.
173    ///
174    /// This is useful when you want to apply the updates to an external database
175    /// and then start tracking a new set of updates.
176    ///
177    /// # Returns
178    ///
179    /// The accumulated updates, or an empty set if updates weren't being tracked.
180    fn take_updates(&mut self) -> SparseTrieUpdates;
181
182    /// Consumes and returns the currently accumulated changed node base paths.
183    ///
184    /// Ancestor paths may be excluded when a descendant path is already present.
185    ///
186    /// Returns an empty set if changed paths weren't being tracked.
187    fn take_changed_paths(&mut self) -> PrefixSetMut;
188
189    /// Removes all nodes and values from the trie, resetting it to a blank state
190    /// with only an empty root node. This is used when a storage root is deleted.
191    ///
192    /// This should not be used when intending to reuse the trie for a fresh account/storage root;
193    /// use `clear` for that.
194    ///
195    /// Note: All previously tracked changes to the trie are also removed.
196    fn wipe(&mut self);
197
198    /// This clears all data structures in the sparse trie, keeping the backing data structures
199    /// allocated. An empty root node is inserted at the root.
200    ///
201    /// This is useful for reusing the trie without needing to reallocate memory.
202    fn clear(&mut self);
203
204    /// Returns a cheap O(1) size hint for the trie representing the count of revealed
205    /// (non-Hash) nodes.
206    ///
207    /// This is used as a heuristic for prioritizing which storage tries to keep
208    /// during pruning. Larger values indicate larger tries that are more valuable to preserve.
209    fn size_hint(&self) -> usize;
210
211    /// Returns a heuristic for the in-memory size of this trie in bytes.
212    ///
213    /// This is an approximation that accounts for the trie's nodes, values,
214    /// and auxiliary data structures.
215    fn memory_size(&self) -> usize;
216
217    /// Prunes all subtrees that do not contain retained leaves.
218    ///
219    /// Each retained leaf is a full key path (usually 64 nibbles for hashed keys).
220    /// Any revealed subtree that is not a prefix of at least one retained key is collapsed into
221    /// hash stubs when hashes are available.
222    ///
223    /// # Preconditions
224    ///
225    /// Must be called only after `root()` has computed hashes for the current trie state.
226    /// Calling `prune` on a dirty trie is a hard error and may panic.
227    ///
228    /// `retained_leaves` must be sorted lexicographically.
229    ///
230    /// # Returns
231    ///
232    /// The number of nodes converted to hash stubs.
233    fn prune(&mut self, retained_leaves: &[Nibbles]) -> usize;
234
235    /// Takes the debug recorder out of this trie, replacing it with an empty one.
236    ///
237    /// Returns the recorder containing all recorded mutations since the last reset.
238    /// The default implementation returns an empty recorder.
239    #[cfg(feature = "trie-debug")]
240    fn take_debug_recorder(&mut self) -> TrieDebugRecorder {
241        TrieDebugRecorder::default()
242    }
243
244    /// Applies leaf updates to the sparse trie.
245    ///
246    /// When a [`LeafUpdate::Changed`] is successfully applied, it is removed from the
247    /// given [`B256Map`]. If it could not be applied due to blinded nodes, it remains
248    /// in the map and the callback is invoked with the required proof target.
249    ///
250    /// Once that proof is calculated and revealed via [`SparseTrie::reveal_nodes`], the same
251    /// `updates` map can be reused to retry the update.
252    ///
253    /// The callback receives `(key, min_len)` where `key` is the full 32-byte hashed key
254    /// (right-padded with zeros from the blinded path) and `min_len` is the minimum depth
255    /// at which proof nodes should be returned.
256    ///
257    /// The callback may be invoked multiple times for the same target across retry loops.
258    /// Callers should deduplicate if needed.
259    ///
260    /// [`LeafUpdate::Touched`] behaves identically except it does not modify the leaf value.
261    fn update_leaves(
262        &mut self,
263        updates: &mut B256Map<LeafUpdate>,
264        proof_required_fn: impl FnMut(B256, u8),
265    ) -> SparseTrieResult<()>;
266}
267
268/// Tracks modifications to the sparse trie structure.
269///
270/// Maintains references to both modified and pruned/removed branches, enabling
271/// one to make batch updates to a persistent database.
272#[derive(Debug, Clone, Default, PartialEq, Eq)]
273pub struct SparseTrieUpdates {
274    /// Collection of updated intermediate nodes indexed by full path.
275    pub updated_nodes: HashMap<Nibbles, BranchNodeCompact>,
276    /// Collection of removed intermediate nodes indexed by full path.
277    pub removed_nodes: HashSet<Nibbles>,
278    /// Flag indicating whether the trie was wiped.
279    pub wiped: bool,
280}
281
282impl SparseTrieUpdates {
283    /// Initialize a [`Self`] with given capacities.
284    pub fn with_capacity(num_updated_nodes: usize, num_removed_nodes: usize) -> Self {
285        Self {
286            updated_nodes: HashMap::with_capacity_and_hasher(num_updated_nodes, Default::default()),
287            removed_nodes: HashSet::with_capacity_and_hasher(num_removed_nodes, Default::default()),
288            wiped: false,
289        }
290    }
291}
292
293/// Error type for a leaf lookup operation
294#[derive(Debug, Clone, PartialEq, Eq)]
295pub enum LeafLookupError {
296    /// The path leads to a blinded node, cannot determine if leaf exists.
297    /// This means the witness is not complete.
298    BlindedNode {
299        /// Path to the blinded node.
300        path: Nibbles,
301        /// Hash of the blinded node.
302        hash: B256,
303    },
304    /// The path leads to a leaf with a different value than expected.
305    /// This means the witness is malformed.
306    ValueMismatch {
307        /// Path to the leaf.
308        path: Nibbles,
309        /// Expected value.
310        expected: Option<Vec<u8>>,
311        /// Actual value found.
312        actual: Vec<u8>,
313    },
314}
315
316/// Success value for a leaf lookup operation
317#[derive(Debug, Clone, PartialEq, Eq)]
318pub enum LeafLookup {
319    /// Leaf exists with expected value.
320    Exists,
321    /// Leaf does not exist (exclusion proof found).
322    NonExistent,
323}