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