Skip to main content

reth_trie_sparse/
traits.rs

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