Skip to main content

reth_trie_sparse/
trie.rs

1use crate::{
2    ArenaParallelSparseTrie, LeafUpdate, SparseTrie as SparseTrieTrait, SparseTrieUpdates,
3    TrieNodeEpoch,
4};
5use alloc::boxed::Box;
6use alloy_primitives::{map::B256Map, B256};
7use reth_execution_errors::{SparseTrieErrorKind, SparseTrieResult};
8use reth_trie_common::{BranchNodeMasks, ProofTrieNodeV2, ProofV2TargetParent, TrieNodeV2};
9
10/// A sparse trie that is either in a "blind" state (no nodes are revealed, root node hash is
11/// unknown) or in a "revealed" state (root node has been revealed and the trie can be updated).
12///
13/// In blind mode the trie does not contain any decoded node data, which saves memory but
14/// prevents direct access to node contents. The revealed mode stores decoded nodes along
15/// with additional information such as values, allowing direct manipulation.
16///
17/// The sparse trie design is optimised for:
18/// 1. Memory efficiency - only revealed nodes are loaded into memory
19/// 2. Update tracking - changes to the trie structure can be tracked and selectively persisted
20/// 3. Incremental operations - nodes can be revealed as needed without loading the entire trie.
21///    This is what gives rise to the notion of a "sparse" trie.
22#[derive(PartialEq, Eq, Debug, Clone)]
23pub enum RevealableSparseTrie<T = ArenaParallelSparseTrie> {
24    /// The trie is blind -- no nodes have been revealed
25    ///
26    /// This is the default state. In this state, the trie cannot be directly queried or modified
27    /// until nodes are revealed.
28    ///
29    /// In this state the `RevealableSparseTrie` can optionally carry with it a cleared
30    /// sparse trie. This allows for reusing the trie's allocations between payload executions.
31    Blind(Option<Box<T>>),
32    /// Some nodes in the Trie have been revealed.
33    ///
34    /// In this state, the trie can be queried and modified for the parts
35    /// that have been revealed. Other parts remain blind and require revealing
36    /// before they can be accessed.
37    Revealed(Box<T>),
38}
39
40impl<T: Default> Default for RevealableSparseTrie<T> {
41    fn default() -> Self {
42        Self::Blind(None)
43    }
44}
45
46impl<T: SparseTrieTrait + Default> RevealableSparseTrie<T> {
47    /// Creates a new revealed but empty sparse trie.
48    pub fn revealed_empty() -> Self {
49        Self::Revealed(Box::default())
50    }
51
52    /// Reveals the root node, converting a blind trie into a revealed one.
53    ///
54    /// If the trie is blinded, its root node is replaced with `root`.
55    ///
56    /// The `masks` are used to determine how the node's children are stored.
57    /// The retention flag controls whether trie updates 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    /// Calculates the root hash of the trie.
161    ///
162    /// This will update any remaining dirty nodes before computing the root hash.
163    /// "dirty" nodes are nodes that need their hashes to be recomputed because one or more of their
164    /// children's hashes have changed.
165    ///
166    /// # Returns
167    ///
168    /// - `Some(B256)` with the calculated root hash if the trie is revealed.
169    /// - `None` if the trie is still blind.
170    pub fn root(&mut self, new_epoch: TrieNodeEpoch) -> Option<B256> {
171        Some(self.as_revealed_mut()?.root(new_epoch))
172    }
173
174    /// Returns true if the root node is cached and does not need any recomputation.
175    pub fn is_root_cached(&self) -> bool {
176        self.as_revealed_ref().is_some_and(|trie| trie.is_root_cached())
177    }
178
179    /// Returns the root hash along with any accumulated update information.
180    ///
181    /// This is useful for when you need both the root hash and information about
182    /// what nodes were modified, which can be used to efficiently update
183    /// an external database.
184    ///
185    /// # Returns
186    ///
187    /// An `Option` tuple consisting of:
188    ///  - The trie root hash (`B256`).
189    ///  - A [`SparseTrieUpdates`] structure containing information about updated nodes.
190    ///  - `None` if the trie is still blind.
191    pub fn root_with_updates(
192        &mut self,
193        new_epoch: TrieNodeEpoch,
194    ) -> Option<(B256, SparseTrieUpdates)> {
195        let revealed = self.as_revealed_mut()?;
196        Some((revealed.root(new_epoch), revealed.take_updates()))
197    }
198
199    /// Clears this trie, setting it to a blind state.
200    ///
201    /// If this instance was revealed, or was itself a `Blind` with a pre-allocated
202    /// [`RevealableSparseTrie`](SparseTrieTrait), this will set to `Blind` carrying a cleared
203    /// pre-allocated [`RevealableSparseTrie`](SparseTrieTrait).
204    #[inline]
205    pub fn clear(&mut self) {
206        *self = match core::mem::replace(self, Self::blind()) {
207            s @ Self::Blind(_) => s,
208            Self::Revealed(mut trie) => {
209                trie.clear();
210                Self::Blind(Some(trie))
211            }
212        };
213    }
214}
215
216impl<T: SparseTrieTrait + Default> RevealableSparseTrie<T> {
217    /// Applies batch leaf updates to the sparse trie.
218    ///
219    /// For blind tries, all updates are kept in the map and proof targets are emitted
220    /// for every key (with no known parent since nothing is revealed).
221    ///
222    /// For revealed tries, delegates to the inner implementation which will:
223    /// - Apply updates where possible
224    /// - Keep blocked updates in the map
225    /// - Emit proof targets for blinded paths
226    pub fn update_leaves(
227        &mut self,
228        updates: &mut B256Map<LeafUpdate>,
229        mut proof_required_fn: impl FnMut(B256, ProofV2TargetParent),
230    ) -> SparseTrieResult<()> {
231        match self {
232            Self::Blind(_) => {
233                // Nothing is revealed - emit proof targets for all keys without a known parent.
234                for key in updates.keys() {
235                    proof_required_fn(*key, ProofV2TargetParent::NONE);
236                }
237                // All updates remain in the map for retry after proofs are fetched
238                Ok(())
239            }
240            Self::Revealed(trie) => trie.update_leaves(updates, proof_required_fn),
241        }
242    }
243}
244
245impl SparseTrieUpdates {
246    /// Clears the updates, but keeps the backing data structures allocated.
247    pub fn clear(&mut self) {
248        self.updated_nodes.clear();
249        self.removed_nodes.clear();
250    }
251
252    /// Extends the updates with another set of updates.
253    pub fn extend(&mut self, other: Self) {
254        self.updated_nodes.extend(other.updated_nodes);
255        self.removed_nodes.extend(other.removed_nodes);
256    }
257}