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}