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