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