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}