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}