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