Skip to main content

reth_trie_sparse/
trie.rs

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