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