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, ProofTrieNodeV2, 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    /// Reveals a batch of V2 proof nodes into this trie.
84    ///
85    /// If `nodes` contains a node at the empty path it is used to reveal the root (transitioning
86    /// the trie from blind to revealed). Otherwise the trie must already be revealed.
87    ///
88    /// Reserves capacity for the expected number of nodes (including branch children) before
89    /// revealing them.
90    pub fn reveal_v2_proof_nodes(
91        &mut self,
92        nodes: &mut [ProofTrieNodeV2],
93        retain_updates: bool,
94    ) -> SparseTrieResult<()> {
95        let capacity = estimate_v2_proof_capacity(nodes);
96
97        let trie = if let Some(root_node) = nodes.iter().find(|n| n.path.is_empty()) {
98            self.reveal_root(root_node.node.clone(), root_node.masks, retain_updates)?
99        } else {
100            self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?
101        };
102        trie.reserve_nodes(capacity);
103        trie.reveal_nodes(nodes)?;
104
105        Ok(())
106    }
107}
108
109/// Calculates capacity estimation for V2 proof nodes.
110///
111/// This counts nodes and their children (for branch nodes) to provide proper capacity hints for
112/// `reserve_nodes`.
113fn estimate_v2_proof_capacity(nodes: &[ProofTrieNodeV2]) -> usize {
114    let mut capacity = nodes.len();
115
116    for node in nodes {
117        if let TrieNodeV2::Branch(branch) = &node.node {
118            capacity += branch.state_mask.count_ones() as usize;
119        }
120    }
121
122    capacity
123}
124
125impl<T: SparseTrieTrait> RevealableSparseTrie<T> {
126    /// Creates a new blind sparse trie.
127    ///
128    /// # Examples
129    ///
130    /// ```
131    /// use reth_trie_sparse::RevealableSparseTrie;
132    ///
133    /// let trie = <RevealableSparseTrie>::blind();
134    /// assert!(trie.is_blind());
135    /// let trie = <RevealableSparseTrie>::default();
136    /// assert!(trie.is_blind());
137    /// ```
138    pub const fn blind() -> Self {
139        Self::Blind(None)
140    }
141
142    /// Creates a new blind sparse trie, clearing and later reusing the given
143    /// [`RevealableSparseTrie`](SparseTrieTrait).
144    pub fn blind_from(mut trie: T) -> Self {
145        trie.clear();
146        Self::Blind(Some(Box::new(trie)))
147    }
148
149    /// Returns `true` if the sparse trie has no revealed nodes.
150    pub const fn is_blind(&self) -> bool {
151        matches!(self, Self::Blind(_))
152    }
153
154    /// Returns `true` if the sparse trie is revealed.
155    pub const fn is_revealed(&self) -> bool {
156        matches!(self, Self::Revealed(_))
157    }
158
159    /// Returns an immutable reference to the underlying revealed sparse trie.
160    ///
161    /// Returns `None` if the trie is blinded.
162    pub const fn as_revealed_ref(&self) -> Option<&T> {
163        if let Self::Revealed(revealed) = self {
164            Some(revealed)
165        } else {
166            None
167        }
168    }
169
170    /// Returns a mutable reference to the underlying revealed sparse trie.
171    ///
172    /// Returns `None` if the trie is blinded.
173    pub fn as_revealed_mut(&mut self) -> Option<&mut T> {
174        if let Self::Revealed(revealed) = self {
175            Some(revealed)
176        } else {
177            None
178        }
179    }
180
181    /// Wipes the trie by removing all nodes and values,
182    /// and resetting the trie to only contain an empty root node.
183    ///
184    /// Note: This method will error if the trie is blinded.
185    pub fn wipe(&mut self) -> SparseTrieResult<()> {
186        let revealed = self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?;
187        revealed.wipe();
188        Ok(())
189    }
190
191    /// Calculates the root hash of the trie.
192    ///
193    /// This will update any remaining dirty nodes before computing the root hash.
194    /// "dirty" nodes are nodes that need their hashes to be recomputed because one or more of their
195    /// children's hashes have changed.
196    ///
197    /// # Returns
198    ///
199    /// - `Some(B256)` with the calculated root hash if the trie is revealed.
200    /// - `None` if the trie is still blind.
201    pub fn root(&mut self) -> Option<B256> {
202        Some(self.as_revealed_mut()?.root())
203    }
204
205    /// Returns true if the root node is cached and does not need any recomputation.
206    pub fn is_root_cached(&self) -> bool {
207        self.as_revealed_ref().is_some_and(|trie| trie.is_root_cached())
208    }
209
210    /// Returns the root hash along with any accumulated update information.
211    ///
212    /// This is useful for when you need both the root hash and information about
213    /// what nodes were modified, which can be used to efficiently update
214    /// an external database.
215    ///
216    /// # Returns
217    ///
218    /// An `Option` tuple consisting of:
219    ///  - The trie root hash (`B256`).
220    ///  - A [`SparseTrieUpdates`] structure containing information about updated nodes.
221    ///  - `None` if the trie is still blind.
222    pub fn root_with_updates(&mut self) -> Option<(B256, SparseTrieUpdates)> {
223        let revealed = self.as_revealed_mut()?;
224        Some((revealed.root(), revealed.take_updates()))
225    }
226
227    /// Clears this trie, setting it to a blind state.
228    ///
229    /// If this instance was revealed, or was itself a `Blind` with a pre-allocated
230    /// [`RevealableSparseTrie`](SparseTrieTrait), this will set to `Blind` carrying a cleared
231    /// pre-allocated [`RevealableSparseTrie`](SparseTrieTrait).
232    #[inline]
233    pub fn clear(&mut self) {
234        *self = match core::mem::replace(self, Self::blind()) {
235            s @ Self::Blind(_) => s,
236            Self::Revealed(mut trie) => {
237                trie.clear();
238                Self::Blind(Some(trie))
239            }
240        };
241    }
242
243    /// Shrinks the capacity of the sparse trie's node storage.
244    /// Works for both revealed and blind tries with allocated storage.
245    pub fn shrink_nodes_to(&mut self, size: usize) {
246        match self {
247            Self::Blind(Some(trie)) | Self::Revealed(trie) => {
248                trie.shrink_nodes_to(size);
249            }
250            _ => {}
251        }
252    }
253
254    /// Shrinks the capacity of the sparse trie's value storage.
255    /// Works for both revealed and blind tries with allocated storage.
256    pub fn shrink_values_to(&mut self, size: usize) {
257        match self {
258            Self::Blind(Some(trie)) | Self::Revealed(trie) => {
259                trie.shrink_values_to(size);
260            }
261            _ => {}
262        }
263    }
264}
265
266impl RevealableSparseTrie {
267    /// Updates (or inserts) a leaf at the given key path with the specified RLP-encoded value.
268    ///
269    /// # Errors
270    ///
271    /// Returns an error if the trie is still blind, or if the update fails.
272    #[instrument(level = "trace", target = "trie::sparse", skip_all)]
273    pub fn update_leaf(&mut self, path: Nibbles, value: Vec<u8>) -> SparseTrieResult<()> {
274        let revealed = self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?;
275        revealed.update_leaf(path, value)?;
276        Ok(())
277    }
278
279    /// Removes a leaf node at the specified key path.
280    ///
281    /// # Errors
282    ///
283    /// Returns an error if the trie is still blind, or if the leaf cannot be removed.
284    #[instrument(level = "trace", target = "trie::sparse", skip_all)]
285    pub fn remove_leaf(&mut self, path: &Nibbles) -> SparseTrieResult<()> {
286        let revealed = self.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?;
287        revealed.remove_leaf(path)?;
288        Ok(())
289    }
290}
291
292impl<T: SparseTrieTrait + Default> RevealableSparseTrie<T> {
293    /// Applies batch leaf updates to the sparse trie.
294    ///
295    /// For blind tries, all updates are kept in the map and proof targets are emitted
296    /// for every key (with `min_len = 0` since nothing is revealed).
297    ///
298    /// For revealed tries, delegates to the inner implementation which will:
299    /// - Apply updates where possible
300    /// - Keep blocked updates in the map
301    /// - Emit proof targets for blinded paths
302    pub fn update_leaves(
303        &mut self,
304        updates: &mut B256Map<LeafUpdate>,
305        mut proof_required_fn: impl FnMut(B256, u8),
306    ) -> SparseTrieResult<()> {
307        match self {
308            Self::Blind(_) => {
309                // Nothing is revealed - emit proof targets for all keys with min_len = 0
310                for key in updates.keys() {
311                    proof_required_fn(*key, 0);
312                }
313                // All updates remain in the map for retry after proofs are fetched
314                Ok(())
315            }
316            Self::Revealed(trie) => trie.update_leaves(updates, proof_required_fn),
317        }
318    }
319}
320
321/// Enum representing sparse trie node type.
322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323pub enum SparseNodeType {
324    /// Empty trie node.
325    Empty,
326    /// A placeholder that stores only the hash for a node that has not been fully revealed.
327    Hash,
328    /// Sparse leaf node.
329    Leaf,
330    /// Sparse extension node.
331    Extension {
332        /// A flag indicating whether the extension node should be stored in the database.
333        store_in_db_trie: Option<bool>,
334    },
335    /// Sparse branch node.
336    Branch {
337        /// A flag indicating whether the branch node should be stored in the database.
338        store_in_db_trie: Option<bool>,
339    },
340}
341
342impl SparseNodeType {
343    /// Returns true if the node is a hash node.
344    pub const fn is_hash(&self) -> bool {
345        matches!(self, Self::Hash)
346    }
347
348    /// Returns true if the node is a branch node.
349    pub const fn is_branch(&self) -> bool {
350        matches!(self, Self::Branch { .. })
351    }
352
353    /// Returns true if the node should be stored in the database.
354    pub const fn store_in_db_trie(&self) -> Option<bool> {
355        match *self {
356            Self::Extension { store_in_db_trie } | Self::Branch { store_in_db_trie } => {
357                store_in_db_trie
358            }
359            _ => None,
360        }
361    }
362}
363
364/// Enum representing trie nodes in sparse trie.
365#[derive(Debug, Clone, PartialEq, Eq)]
366pub enum SparseNode {
367    /// Empty trie node.
368    Empty,
369    /// Sparse leaf node with remaining key suffix.
370    Leaf {
371        /// Remaining key suffix for the leaf node.
372        key: Nibbles,
373        /// Tracker for the node's state, e.g. cached `RlpNode` tracking.
374        state: SparseNodeState,
375    },
376    /// Sparse extension node with key.
377    Extension {
378        /// The key slice stored by this extension node.
379        key: Nibbles,
380        /// Tracker for the node's state, e.g. cached `RlpNode` tracking.
381        state: SparseNodeState,
382    },
383    /// Sparse branch node with state mask.
384    Branch {
385        /// The bitmask representing children present in the branch node.
386        state_mask: TrieMask,
387        /// Tracker for the node's state, e.g. cached `RlpNode` tracking.
388        state: SparseNodeState,
389        /// The mask of the children that are blinded.
390        blinded_mask: TrieMask,
391        /// The hashes of the children that are blinded.
392        blinded_hashes: Box<[B256; 16]>,
393    },
394}
395
396impl SparseNode {
397    /// Create new [`SparseNode::Branch`] from state mask and blinded nodes.
398    #[cfg(test)]
399    pub fn new_branch(state_mask: TrieMask, blinded_children: &[(u8, B256)]) -> Self {
400        let mut blinded_mask = TrieMask::default();
401        let mut blinded_hashes = Box::new([B256::ZERO; 16]);
402
403        for (nibble, hash) in blinded_children {
404            blinded_mask.set_bit(*nibble);
405            blinded_hashes[*nibble as usize] = *hash;
406        }
407        Self::Branch { state_mask, state: SparseNodeState::Dirty, blinded_mask, blinded_hashes }
408    }
409
410    /// Create new [`SparseNode::Branch`] with two bits set.
411    pub fn new_split_branch(bit_a: u8, bit_b: u8) -> Self {
412        let state_mask = TrieMask::new(
413            // set bits for both children
414            (1u16 << bit_a) | (1u16 << bit_b),
415        );
416        Self::Branch {
417            state_mask,
418            state: SparseNodeState::Dirty,
419            blinded_mask: TrieMask::default(),
420            blinded_hashes: Box::new([B256::ZERO; 16]),
421        }
422    }
423
424    /// Create new [`SparseNode::Extension`] from the key slice.
425    pub const fn new_ext(key: Nibbles) -> Self {
426        Self::Extension { key, state: SparseNodeState::Dirty }
427    }
428
429    /// Create new [`SparseNode::Leaf`] from leaf key and value.
430    pub const fn new_leaf(key: Nibbles) -> Self {
431        Self::Leaf { key, state: SparseNodeState::Dirty }
432    }
433
434    /// Returns the cached [`RlpNode`] of the node, if it's available.
435    pub fn cached_rlp_node(&self) -> Option<Cow<'_, RlpNode>> {
436        match &self {
437            Self::Empty => None,
438            Self::Leaf { state, .. } |
439            Self::Extension { state, .. } |
440            Self::Branch { state, .. } => state.cached_rlp_node().map(Cow::Borrowed),
441        }
442    }
443
444    /// Returns the cached hash of the node, if it's available.
445    pub fn cached_hash(&self) -> Option<B256> {
446        match &self {
447            Self::Empty => None,
448            Self::Leaf { state, .. } |
449            Self::Extension { state, .. } |
450            Self::Branch { state, .. } => state.cached_hash(),
451        }
452    }
453
454    /// Sets the hash of the node for testing purposes.
455    ///
456    /// For [`SparseNode::Empty`] nodes, this method panics.
457    #[cfg(any(test, feature = "test-utils"))]
458    pub fn set_state(&mut self, new_state: SparseNodeState) {
459        match self {
460            Self::Empty => {
461                panic!("Cannot set hash for Empty or Hash nodes")
462            }
463            Self::Leaf { state, .. } |
464            Self::Extension { state, .. } |
465            Self::Branch { state, .. } => {
466                *state = new_state;
467            }
468        }
469    }
470
471    /// Sets the state of the node and returns a new node with the same state.
472    #[cfg(any(test, feature = "test-utils"))]
473    pub fn with_state(mut self, state: SparseNodeState) -> Self {
474        self.set_state(state);
475        self
476    }
477
478    /// Returns the memory size of this node in bytes.
479    pub const fn memory_size(&self) -> usize {
480        match self {
481            Self::Empty => core::mem::size_of::<Self>(),
482            Self::Branch { .. } => {
483                core::mem::size_of::<Self>() + core::mem::size_of::<[B256; 16]>()
484            }
485            Self::Leaf { key, .. } | Self::Extension { key, .. } => {
486                core::mem::size_of::<Self>() + key.len()
487            }
488        }
489    }
490}
491
492/// Tracks the current state of a node in the trie, specifically regarding whether it's been updated
493/// or not.
494#[derive(Debug, Clone, PartialEq, Eq)]
495pub enum SparseNodeState {
496    /// The node has been updated and its new `RlpNode` has not yet been calculated.
497    ///
498    /// If a node is dirty and has children (branches or extensions) then at least once child must
499    /// also be dirty.
500    Dirty,
501    /// The node has a cached `RlpNode`, either from being revealed or computed after an update.
502    Cached {
503        /// The RLP node which is used to represent this node in its parent. Usually this is the
504        /// RLP encoding of the node's hash, except for when the node RLP encodes to <32
505        /// bytes.
506        rlp_node: RlpNode,
507        /// Flag indicating if this node is cached in the database.
508        ///
509        /// NOTE for extension nodes this actually indicates the node's child branch is in the
510        /// database, not the extension itself.
511        store_in_db_trie: Option<bool>,
512    },
513}
514
515impl SparseNodeState {
516    /// Returns the cached [`RlpNode`] of the node, if it's available.
517    pub const fn cached_rlp_node(&self) -> Option<&RlpNode> {
518        match self {
519            Self::Cached { rlp_node, .. } => Some(rlp_node),
520            Self::Dirty => None,
521        }
522    }
523
524    /// Returns the cached hash of the node, if it's available.
525    pub fn cached_hash(&self) -> Option<B256> {
526        self.cached_rlp_node().and_then(|n| n.as_hash())
527    }
528
529    /// Returns whether or not this node is stored in the db, or None if it's not known.
530    pub const fn store_in_db_trie(&self) -> Option<bool> {
531        match self {
532            Self::Cached { store_in_db_trie, .. } => *store_in_db_trie,
533            Self::Dirty => None,
534        }
535    }
536}
537
538/// RLP node stack item.
539#[derive(Clone, PartialEq, Eq, Debug)]
540pub struct RlpNodeStackItem {
541    /// Path to the node.
542    pub path: Nibbles,
543    /// RLP node.
544    pub rlp_node: RlpNode,
545    /// Type of the node.
546    pub node_type: SparseNodeType,
547}
548
549impl SparseTrieUpdates {
550    /// Create new wiped sparse trie updates.
551    pub fn wiped() -> Self {
552        Self { wiped: true, ..Default::default() }
553    }
554
555    /// Clears the updates, but keeps the backing data structures allocated.
556    ///
557    /// Sets `wiped` to `false`.
558    pub fn clear(&mut self) {
559        self.updated_nodes.clear();
560        self.removed_nodes.clear();
561        self.wiped = false;
562    }
563
564    /// Extends the updates with another set of updates.
565    pub fn extend(&mut self, other: Self) {
566        self.updated_nodes.extend(other.updated_nodes);
567        self.removed_nodes.extend(other.removed_nodes);
568        self.wiped |= other.wiped;
569    }
570}