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    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}