Skip to main content

reth_trie_sparse/
trie.rs

1use crate::{LeafUpdate, ParallelSparseTrie, SparseTrie as SparseTrieTrait, SparseTrieUpdates};
2use alloc::{borrow::Cow, boxed::Box, vec::Vec};
3use alloy_primitives::{map::B256Map, B256};
4use reth_execution_errors::{SparseTrieErrorKind, SparseTrieResult};
5use reth_trie_common::{BranchNodeMasks, Nibbles, RlpNode, TrieMask, TrieNodeV2};
6use tracing::instrument;
7
8/// A sparse trie that is either in a "blind" state (no nodes are revealed, root node hash is
9/// unknown) or in a "revealed" state (root node has been revealed and the trie can be updated).
10///
11/// In blind mode the trie does not contain any decoded node data, which saves memory but
12/// prevents direct access to node contents. The revealed mode stores decoded nodes along
13/// with additional information such as values, allowing direct manipulation.
14///
15/// The sparse trie design is optimised for:
16/// 1. Memory efficiency - only revealed nodes are loaded into memory
17/// 2. Update tracking - changes to the trie structure can be tracked and selectively persisted
18/// 3. Incremental operations - nodes can be revealed as needed without loading the entire trie.
19///    This is what gives rise to the notion of a "sparse" trie.
20#[derive(PartialEq, Eq, Debug, Clone)]
21pub enum RevealableSparseTrie<T = ParallelSparseTrie> {
22    /// The trie is blind -- no nodes have been revealed
23    ///
24    /// This is the default state. In this state, the trie cannot be directly queried or modified
25    /// until nodes are revealed.
26    ///
27    /// In this state the `RevealableSparseTrie` can optionally carry with it a cleared
28    /// sparse trie. This allows for reusing the trie's allocations between payload executions.
29    Blind(Option<Box<T>>),
30    /// Some nodes in the Trie have been revealed.
31    ///
32    /// In this state, the trie can be queried and modified for the parts
33    /// that have been revealed. Other parts remain blind and require revealing
34    /// before they can be accessed.
35    Revealed(Box<T>),
36}
37
38impl<T: Default> Default for RevealableSparseTrie<T> {
39    fn default() -> Self {
40        Self::Blind(None)
41    }
42}
43
44impl<T: SparseTrieTrait + Default> RevealableSparseTrie<T> {
45    /// Creates a new revealed but empty sparse trie with `SparseNode::Empty` as root node.
46    pub fn revealed_empty() -> Self {
47        Self::Revealed(Box::default())
48    }
49
50    /// Reveals the root node, converting a blind trie into a revealed one.
51    ///
52    /// If the trie is blinded, its root node is replaced with `root`.
53    ///
54    /// The `masks` are used to determine how the node's children are stored.
55    /// The `retain_updates` flag controls whether changes to the trie structure
56    /// should be tracked.
57    ///
58    /// # Returns
59    ///
60    /// A mutable reference to the underlying [`RevealableSparseTrie`](SparseTrieTrait).
61    pub fn reveal_root(
62        &mut self,
63        root: TrieNodeV2,
64        masks: Option<BranchNodeMasks>,
65        retain_updates: bool,
66    ) -> SparseTrieResult<&mut T> {
67        // if `Blind`, we initialize the revealed trie with the given root node, using a
68        // pre-allocated trie if available.
69        if self.is_blind() {
70            let mut revealed_trie = if let Self::Blind(Some(cleared_trie)) = core::mem::take(self) {
71                cleared_trie
72            } else {
73                Box::default()
74            };
75
76            revealed_trie.set_root(root, masks, retain_updates)?;
77            *self = Self::Revealed(revealed_trie);
78        }
79
80        Ok(self.as_revealed_mut().unwrap())
81    }
82}
83
84impl<T: SparseTrieTrait> RevealableSparseTrie<T> {
85    /// Creates a new blind sparse trie.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use reth_trie_sparse::RevealableSparseTrie;
91    ///
92    /// let trie = <RevealableSparseTrie>::blind();
93    /// assert!(trie.is_blind());
94    /// let trie = <RevealableSparseTrie>::default();
95    /// assert!(trie.is_blind());
96    /// ```
97    pub const fn blind() -> Self {
98        Self::Blind(None)
99    }
100
101    /// Creates a new blind sparse trie, clearing and later reusing the given
102    /// [`RevealableSparseTrie`](SparseTrieTrait).
103    pub fn blind_from(mut trie: T) -> Self {
104        trie.clear();
105        Self::Blind(Some(Box::new(trie)))
106    }
107
108    /// Returns `true` if the sparse trie has no revealed nodes.
109    pub const fn is_blind(&self) -> bool {
110        matches!(self, Self::Blind(_))
111    }
112
113    /// Returns `true` if the sparse trie is revealed.
114    pub const fn is_revealed(&self) -> bool {
115        matches!(self, Self::Revealed(_))
116    }
117
118    /// Returns an immutable reference to the underlying revealed sparse trie.
119    ///
120    /// Returns `None` if the trie is blinded.
121    pub const fn as_revealed_ref(&self) -> Option<&T> {
122        if let Self::Revealed(revealed) = self {
123            Some(revealed)
124        } else {
125            None
126        }
127    }
128
129    /// Returns a mutable reference to the underlying revealed sparse trie.
130    ///
131    /// Returns `None` if the trie is blinded.
132    pub fn as_revealed_mut(&mut self) -> Option<&mut T> {
133        if let Self::Revealed(revealed) = self {
134            Some(revealed)
135        } else {
136            None
137        }
138    }
139
140    /// Wipes the trie by removing all nodes and values,
141    /// and resetting the trie to only contain an empty root node.
142    ///
143    /// Note: This method will error if the trie is blinded.
144    pub fn wipe(&mut self) -> SparseTrieResult<()> {
145        let revealed = self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?;
146        revealed.wipe();
147        Ok(())
148    }
149
150    /// Calculates the root hash of the trie.
151    ///
152    /// This will update any remaining dirty nodes before computing the root hash.
153    /// "dirty" nodes are nodes that need their hashes to be recomputed because one or more of their
154    /// children's hashes have changed.
155    ///
156    /// # Returns
157    ///
158    /// - `Some(B256)` with the calculated root hash if the trie is revealed.
159    /// - `None` if the trie is still blind.
160    pub fn root(&mut self) -> Option<B256> {
161        Some(self.as_revealed_mut()?.root())
162    }
163
164    /// Returns true if the root node is cached and does not need any recomputation.
165    pub fn is_root_cached(&self) -> bool {
166        self.as_revealed_ref().is_some_and(|trie| trie.is_root_cached())
167    }
168
169    /// Returns the root hash along with any accumulated update information.
170    ///
171    /// This is useful for when you need both the root hash and information about
172    /// what nodes were modified, which can be used to efficiently update
173    /// an external database.
174    ///
175    /// # Returns
176    ///
177    /// An `Option` tuple consisting of:
178    ///  - The trie root hash (`B256`).
179    ///  - A [`SparseTrieUpdates`] structure containing information about updated nodes.
180    ///  - `None` if the trie is still blind.
181    pub fn root_with_updates(&mut self) -> Option<(B256, SparseTrieUpdates)> {
182        let revealed = self.as_revealed_mut()?;
183        Some((revealed.root(), revealed.take_updates()))
184    }
185
186    /// Clears this trie, setting it to a blind state.
187    ///
188    /// If this instance was revealed, or was itself a `Blind` with a pre-allocated
189    /// [`RevealableSparseTrie`](SparseTrieTrait), this will set to `Blind` carrying a cleared
190    /// pre-allocated [`RevealableSparseTrie`](SparseTrieTrait).
191    #[inline]
192    pub fn clear(&mut self) {
193        *self = match core::mem::replace(self, Self::blind()) {
194            s @ Self::Blind(_) => s,
195            Self::Revealed(mut trie) => {
196                trie.clear();
197                Self::Blind(Some(trie))
198            }
199        };
200    }
201
202    /// Shrinks the capacity of the sparse trie's node storage.
203    /// Works for both revealed and blind tries with allocated storage.
204    pub fn shrink_nodes_to(&mut self, size: usize) {
205        match self {
206            Self::Blind(Some(trie)) | Self::Revealed(trie) => {
207                trie.shrink_nodes_to(size);
208            }
209            _ => {}
210        }
211    }
212
213    /// Shrinks the capacity of the sparse trie's value storage.
214    /// Works for both revealed and blind tries with allocated storage.
215    pub fn shrink_values_to(&mut self, size: usize) {
216        match self {
217            Self::Blind(Some(trie)) | Self::Revealed(trie) => {
218                trie.shrink_values_to(size);
219            }
220            _ => {}
221        }
222    }
223}
224
225impl RevealableSparseTrie {
226    /// Updates (or inserts) a leaf at the given key path with the specified RLP-encoded value.
227    ///
228    /// # Errors
229    ///
230    /// Returns an error if the trie is still blind, or if the update fails.
231    #[instrument(level = "trace", target = "trie::sparse", skip_all)]
232    pub fn update_leaf(&mut self, path: Nibbles, value: Vec<u8>) -> SparseTrieResult<()> {
233        let revealed = self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?;
234        revealed.update_leaf(path, value)?;
235        Ok(())
236    }
237
238    /// Removes a leaf node at the specified key path.
239    ///
240    /// # Errors
241    ///
242    /// Returns an error if the trie is still blind, or if the leaf cannot be removed.
243    #[instrument(level = "trace", target = "trie::sparse", skip_all)]
244    pub fn remove_leaf(&mut self, path: &Nibbles) -> SparseTrieResult<()> {
245        let revealed = self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?;
246        revealed.remove_leaf(path)?;
247        Ok(())
248    }
249}
250
251impl<T: SparseTrieTrait + Default> RevealableSparseTrie<T> {
252    /// Applies batch leaf updates to the sparse trie.
253    ///
254    /// For blind tries, all updates are kept in the map and proof targets are emitted
255    /// for every key (with `min_len = 0` since nothing is revealed).
256    ///
257    /// For revealed tries, delegates to the inner implementation which will:
258    /// - Apply updates where possible
259    /// - Keep blocked updates in the map
260    /// - Emit proof targets for blinded paths
261    pub fn update_leaves(
262        &mut self,
263        updates: &mut B256Map<LeafUpdate>,
264        mut proof_required_fn: impl FnMut(B256, u8),
265    ) -> SparseTrieResult<()> {
266        match self {
267            Self::Blind(_) => {
268                // Nothing is revealed - emit proof targets for all keys with min_len = 0
269                for key in updates.keys() {
270                    proof_required_fn(*key, 0);
271                }
272                // All updates remain in the map for retry after proofs are fetched
273                Ok(())
274            }
275            Self::Revealed(trie) => trie.update_leaves(updates, proof_required_fn),
276        }
277    }
278}
279
280/// Enum representing sparse trie node type.
281#[derive(Debug, Clone, Copy, PartialEq, Eq)]
282pub enum SparseNodeType {
283    /// Empty trie node.
284    Empty,
285    /// A placeholder that stores only the hash for a node that has not been fully revealed.
286    Hash,
287    /// Sparse leaf node.
288    Leaf,
289    /// Sparse extension node.
290    Extension {
291        /// A flag indicating whether the extension node should be stored in the database.
292        store_in_db_trie: Option<bool>,
293    },
294    /// Sparse branch node.
295    Branch {
296        /// A flag indicating whether the branch node should be stored in the database.
297        store_in_db_trie: Option<bool>,
298    },
299}
300
301impl SparseNodeType {
302    /// Returns true if the node is a hash node.
303    pub const fn is_hash(&self) -> bool {
304        matches!(self, Self::Hash)
305    }
306
307    /// Returns true if the node is a branch node.
308    pub const fn is_branch(&self) -> bool {
309        matches!(self, Self::Branch { .. })
310    }
311
312    /// Returns true if the node should be stored in the database.
313    pub const fn store_in_db_trie(&self) -> Option<bool> {
314        match *self {
315            Self::Extension { store_in_db_trie } | Self::Branch { store_in_db_trie } => {
316                store_in_db_trie
317            }
318            _ => None,
319        }
320    }
321}
322
323/// Enum representing trie nodes in sparse trie.
324#[derive(Debug, Clone, PartialEq, Eq)]
325pub enum SparseNode {
326    /// Empty trie node.
327    Empty,
328    /// Sparse leaf node with remaining key suffix.
329    Leaf {
330        /// Remaining key suffix for the leaf node.
331        key: Nibbles,
332        /// Tracker for the node's state, e.g. cached `RlpNode` tracking.
333        state: SparseNodeState,
334    },
335    /// Sparse extension node with key.
336    Extension {
337        /// The key slice stored by this extension node.
338        key: Nibbles,
339        /// Tracker for the node's state, e.g. cached `RlpNode` tracking.
340        state: SparseNodeState,
341    },
342    /// Sparse branch node with state mask.
343    Branch {
344        /// The bitmask representing children present in the branch node.
345        state_mask: TrieMask,
346        /// Tracker for the node's state, e.g. cached `RlpNode` tracking.
347        state: SparseNodeState,
348        /// The mask of the children that are blinded.
349        blinded_mask: TrieMask,
350        /// The hashes of the children that are blinded.
351        blinded_hashes: Box<[B256; 16]>,
352    },
353}
354
355impl SparseNode {
356    /// Create new [`SparseNode::Branch`] from state mask and blinded nodes.
357    #[cfg(test)]
358    pub fn new_branch(state_mask: TrieMask, blinded_children: &[(u8, B256)]) -> Self {
359        let mut blinded_mask = TrieMask::default();
360        let mut blinded_hashes = Box::new([B256::ZERO; 16]);
361
362        for (nibble, hash) in blinded_children {
363            blinded_mask.set_bit(*nibble);
364            blinded_hashes[*nibble as usize] = *hash;
365        }
366        Self::Branch { state_mask, state: SparseNodeState::Dirty, blinded_mask, blinded_hashes }
367    }
368
369    /// Create new [`SparseNode::Branch`] with two bits set.
370    pub fn new_split_branch(bit_a: u8, bit_b: u8) -> Self {
371        let state_mask = TrieMask::new(
372            // set bits for both children
373            (1u16 << bit_a) | (1u16 << bit_b),
374        );
375        Self::Branch {
376            state_mask,
377            state: SparseNodeState::Dirty,
378            blinded_mask: TrieMask::default(),
379            blinded_hashes: Box::new([B256::ZERO; 16]),
380        }
381    }
382
383    /// Create new [`SparseNode::Extension`] from the key slice.
384    pub const fn new_ext(key: Nibbles) -> Self {
385        Self::Extension { key, state: SparseNodeState::Dirty }
386    }
387
388    /// Create new [`SparseNode::Leaf`] from leaf key and value.
389    pub const fn new_leaf(key: Nibbles) -> Self {
390        Self::Leaf { key, state: SparseNodeState::Dirty }
391    }
392
393    /// Returns the cached [`RlpNode`] of the node, if it's available.
394    pub fn cached_rlp_node(&self) -> Option<Cow<'_, RlpNode>> {
395        match &self {
396            Self::Empty => None,
397            Self::Leaf { state, .. } |
398            Self::Extension { state, .. } |
399            Self::Branch { state, .. } => state.cached_rlp_node().map(Cow::Borrowed),
400        }
401    }
402
403    /// Returns the cached hash of the node, if it's available.
404    pub fn cached_hash(&self) -> Option<B256> {
405        match &self {
406            Self::Empty => None,
407            Self::Leaf { state, .. } |
408            Self::Extension { state, .. } |
409            Self::Branch { state, .. } => state.cached_hash(),
410        }
411    }
412
413    /// Sets the hash of the node for testing purposes.
414    ///
415    /// For [`SparseNode::Empty`] nodes, this method panics.
416    #[cfg(any(test, feature = "test-utils"))]
417    pub fn set_state(&mut self, new_state: SparseNodeState) {
418        match self {
419            Self::Empty => {
420                panic!("Cannot set hash for Empty or Hash nodes")
421            }
422            Self::Leaf { state, .. } |
423            Self::Extension { state, .. } |
424            Self::Branch { state, .. } => {
425                *state = new_state;
426            }
427        }
428    }
429
430    /// Sets the state of the node and returns a new node with the same state.
431    #[cfg(any(test, feature = "test-utils"))]
432    pub fn with_state(mut self, state: SparseNodeState) -> Self {
433        self.set_state(state);
434        self
435    }
436
437    /// Returns the memory size of this node in bytes.
438    pub const fn memory_size(&self) -> usize {
439        match self {
440            Self::Empty => core::mem::size_of::<Self>(),
441            Self::Branch { .. } => {
442                core::mem::size_of::<Self>() + core::mem::size_of::<[B256; 16]>()
443            }
444            Self::Leaf { key, .. } | Self::Extension { key, .. } => {
445                core::mem::size_of::<Self>() + key.len()
446            }
447        }
448    }
449}
450
451/// Tracks the current state of a node in the trie, specifically regarding whether it's been updated
452/// or not.
453#[derive(Debug, Clone, PartialEq, Eq)]
454pub enum SparseNodeState {
455    /// The node has been updated and its new `RlpNode` has not yet been calculated.
456    ///
457    /// If a node is dirty and has children (branches or extensions) then at least once child must
458    /// also be dirty.
459    Dirty,
460    /// The node has a cached `RlpNode`, either from being revealed or computed after an update.
461    Cached {
462        /// The RLP node which is used to represent this node in its parent. Usually this is the
463        /// RLP encoding of the node's hash, except for when the node RLP encodes to <32
464        /// bytes.
465        rlp_node: RlpNode,
466        /// Flag indicating if this node is cached in the database.
467        ///
468        /// NOTE for extension nodes this actually indicates the node's child branch is in the
469        /// database, not the extension itself.
470        store_in_db_trie: Option<bool>,
471    },
472}
473
474impl SparseNodeState {
475    /// Returns the cached [`RlpNode`] of the node, if it's available.
476    pub const fn cached_rlp_node(&self) -> Option<&RlpNode> {
477        match self {
478            Self::Cached { rlp_node, .. } => Some(rlp_node),
479            Self::Dirty => None,
480        }
481    }
482
483    /// Returns the cached hash of the node, if it's available.
484    pub fn cached_hash(&self) -> Option<B256> {
485        self.cached_rlp_node().and_then(|n| n.as_hash())
486    }
487
488    /// Returns whether or not this node is stored in the db, or None if it's not known.
489    pub const fn store_in_db_trie(&self) -> Option<bool> {
490        match self {
491            Self::Cached { store_in_db_trie, .. } => *store_in_db_trie,
492            Self::Dirty => None,
493        }
494    }
495}
496
497/// RLP node stack item.
498#[derive(Clone, PartialEq, Eq, Debug)]
499pub struct RlpNodeStackItem {
500    /// Path to the node.
501    pub path: Nibbles,
502    /// RLP node.
503    pub rlp_node: RlpNode,
504    /// Type of the node.
505    pub node_type: SparseNodeType,
506}
507
508impl SparseTrieUpdates {
509    /// Create new wiped sparse trie updates.
510    pub fn wiped() -> Self {
511        Self { wiped: true, ..Default::default() }
512    }
513
514    /// Clears the updates, but keeps the backing data structures allocated.
515    ///
516    /// Sets `wiped` to `false`.
517    pub fn clear(&mut self) {
518        self.updated_nodes.clear();
519        self.removed_nodes.clear();
520        self.wiped = false;
521    }
522
523    /// Extends the updates with another set of updates.
524    pub fn extend(&mut self, other: Self) {
525        self.updated_nodes.extend(other.updated_nodes);
526        self.removed_nodes.extend(other.removed_nodes);
527        self.wiped |= other.wiped;
528    }
529}