Skip to main content

reth_trie/
walker.rs

1use crate::{
2    prefix_set::PrefixSet,
3    trie_cursor::{subnode::SubNodePosition, CursorSubNode, TrieCursor},
4    BranchNodeCompact, Nibbles,
5};
6use alloy_primitives::{map::HashSet, B256};
7use alloy_trie::proof::AddedRemovedKeys;
8use reth_storage_errors::db::DatabaseError;
9use tracing::{instrument, trace};
10
11#[cfg(test)]
12use crate::trie_cursor::{mock::MockTrieCursorFactory, TrieCursorFactory};
13
14#[cfg(test)]
15use alloy_primitives::map::B256Map;
16
17#[cfg(test)]
18use alloy_trie::TrieMask;
19
20#[cfg(test)]
21use std::collections::BTreeMap;
22
23#[cfg(feature = "metrics")]
24use crate::metrics::WalkerMetrics;
25
26/// Traverses the trie in lexicographic order.
27///
28/// This iterator depends on the ordering guarantees of [`TrieCursor`].
29#[derive(Debug)]
30pub struct TrieWalker<C, K = AddedRemovedKeys> {
31    /// A mutable reference to a trie cursor instance used for navigating the trie.
32    pub cursor: C,
33    /// A vector containing the trie nodes that have been visited.
34    pub stack: Vec<CursorSubNode>,
35    /// A flag indicating whether the current node can be skipped when traversing the trie. This
36    /// is determined by whether the current key's prefix is included in the prefix set and if the
37    /// hash flag is set.
38    pub can_skip_current_node: bool,
39    /// A `PrefixSet` representing the changes to be applied to the trie.
40    pub changes: PrefixSet,
41    /// When enabled, all children of a branch become unskippable if the branch path itself
42    /// matches the prefix set, even if a given child path does not.
43    walk_all_changed_branch_children: bool,
44    /// The retained trie node keys that need to be removed.
45    removed_keys: Option<HashSet<Nibbles>>,
46    /// Provided when it's necessary not to skip certain nodes during proof generation.
47    /// Specifically we don't skip certain branch nodes even when they are not in the `PrefixSet`,
48    /// when they might be required to support leaf removal.
49    added_removed_keys: Option<K>,
50    #[cfg(feature = "metrics")]
51    /// Walker metrics.
52    metrics: WalkerMetrics,
53}
54
55impl<C: TrieCursor, K: AsRef<AddedRemovedKeys>> TrieWalker<C, K> {
56    /// Constructs a new `TrieWalker` for the state trie from existing stack and a cursor.
57    pub fn state_trie_from_stack(cursor: C, stack: Vec<CursorSubNode>, changes: PrefixSet) -> Self {
58        Self::from_stack(
59            cursor,
60            stack,
61            changes,
62            #[cfg(feature = "metrics")]
63            crate::TrieType::State,
64        )
65    }
66
67    /// Constructs a new `TrieWalker` for the storage trie from existing stack and a cursor.
68    pub fn storage_trie_from_stack(
69        cursor: C,
70        stack: Vec<CursorSubNode>,
71        changes: PrefixSet,
72    ) -> Self {
73        Self::from_stack(
74            cursor,
75            stack,
76            changes,
77            #[cfg(feature = "metrics")]
78            crate::TrieType::Storage,
79        )
80    }
81
82    /// Constructs a new `TrieWalker` from existing stack and a cursor.
83    fn from_stack(
84        cursor: C,
85        stack: Vec<CursorSubNode>,
86        changes: PrefixSet,
87        #[cfg(feature = "metrics")] trie_type: crate::TrieType,
88    ) -> Self {
89        let mut this = Self {
90            cursor,
91            changes,
92            stack,
93            can_skip_current_node: false,
94            walk_all_changed_branch_children: false,
95            removed_keys: None,
96            added_removed_keys: None,
97            #[cfg(feature = "metrics")]
98            metrics: WalkerMetrics::new(trie_type),
99        };
100        this.update_skip_node();
101        this
102    }
103
104    /// Sets the flag whether the trie updates should be stored.
105    pub fn with_deletions_retained(mut self, retained: bool) -> Self {
106        if retained {
107            self.removed_keys = Some(HashSet::default());
108        }
109        self
110    }
111
112    /// Configures the walker to not skip certain branch nodes, even when they are not in the
113    /// `PrefixSet`, when they might be needed to support leaf removal.
114    pub fn with_added_removed_keys<K2>(self, added_removed_keys: Option<K2>) -> TrieWalker<C, K2> {
115        TrieWalker {
116            cursor: self.cursor,
117            stack: self.stack,
118            can_skip_current_node: self.can_skip_current_node,
119            changes: self.changes,
120            walk_all_changed_branch_children: self.walk_all_changed_branch_children,
121            removed_keys: self.removed_keys,
122            added_removed_keys,
123            #[cfg(feature = "metrics")]
124            metrics: self.metrics,
125        }
126    }
127
128    /// Configures the walker to treat every child of a matching branch path as unskippable.
129    pub const fn with_walk_all_changed_branch_children(mut self, enabled: bool) -> Self {
130        self.walk_all_changed_branch_children = enabled;
131        self
132    }
133
134    /// Split the walker into stack and trie updates.
135    pub fn split(mut self) -> (Vec<CursorSubNode>, HashSet<Nibbles>) {
136        let keys = self.take_removed_keys();
137        (self.stack, keys)
138    }
139
140    /// Take removed keys from the walker.
141    pub fn take_removed_keys(&mut self) -> HashSet<Nibbles> {
142        self.removed_keys.take().unwrap_or_default()
143    }
144
145    /// Prints the current stack of trie nodes.
146    pub fn print_stack(&self) {
147        println!("====================== STACK ======================");
148        for node in &self.stack {
149            println!("{node:?}");
150        }
151        println!("====================== END STACK ======================\n");
152    }
153
154    /// The current length of the removed keys.
155    pub fn removed_keys_len(&self) -> usize {
156        self.removed_keys.as_ref().map_or(0, |u| u.len())
157    }
158
159    /// Returns the current key in the trie.
160    pub fn key(&self) -> Option<&Nibbles> {
161        self.stack.last().map(|n| n.full_key())
162    }
163
164    /// Returns the current hash in the trie, if any.
165    pub fn hash(&self) -> Option<B256> {
166        self.stack.last().and_then(|n| n.hash())
167    }
168
169    /// Returns the current hash in the trie, if any.
170    ///
171    /// Differs from [`Self::hash`] in that it returns `None` if the subnode is positioned at the
172    /// child without a hash mask bit set. [`Self::hash`] panics in that case.
173    pub fn maybe_hash(&self) -> Option<B256> {
174        self.stack.last().and_then(|n| n.maybe_hash())
175    }
176
177    /// Indicates whether the children of the current node are present in the trie.
178    pub fn children_are_in_trie(&self) -> bool {
179        self.stack.last().is_some_and(|n| n.tree_flag())
180    }
181
182    /// Returns the next unprocessed key in the trie along with its raw [`Nibbles`] representation.
183    #[instrument(level = "trace", skip(self), ret)]
184    pub fn next_unprocessed_key(&self) -> Option<(B256, Nibbles)> {
185        self.key()
186            .and_then(|key| if self.can_skip_current_node { key.increment() } else { Some(*key) })
187            .map(|key| {
188                let mut packed = key.pack();
189                packed.resize(32, 0);
190                (B256::from_slice(packed.as_slice()), key)
191            })
192    }
193
194    /// Updates the skip node flag based on the walker's current state.
195    fn update_skip_node(&mut self) {
196        let old = self.can_skip_current_node;
197        self.can_skip_current_node = self.stack.last().is_some_and(|node| {
198            // If the current key is not removed according to the [`AddedRemovedKeys`], and all of
199            // its siblings are removed, then we don't want to skip it. This allows the
200            // `ProofRetainer` to include this node in the returned proofs. Required to support
201            // leaf removal.
202            let key_is_only_nonremoved_child =
203                self.added_removed_keys.as_ref().is_some_and(|added_removed_keys| {
204                    node.full_key_is_only_nonremoved_child(added_removed_keys.as_ref())
205                });
206
207            trace!(
208                target: "trie::walker",
209                ?key_is_only_nonremoved_child,
210                full_key=?node.full_key(),
211                "Checked for only non-removed child",
212            );
213
214            let branch_path_matches_prefix_set = self.walk_all_changed_branch_children &&
215                node.position().is_child() &&
216                self.changes.contains(&node.key);
217
218            !self.changes.contains(node.full_key()) &&
219                !branch_path_matches_prefix_set &&
220                node.hash_flag() &&
221                !key_is_only_nonremoved_child
222        });
223        trace!(
224            target: "trie::walker",
225            old,
226            new = self.can_skip_current_node,
227            last = ?self.stack.last(),
228            "updated skip node flag"
229        );
230    }
231
232    /// Constructs a new [`TrieWalker`] for the state trie.
233    pub fn state_trie(cursor: C, changes: PrefixSet) -> Self {
234        Self::new(
235            cursor,
236            changes,
237            #[cfg(feature = "metrics")]
238            crate::TrieType::State,
239        )
240    }
241
242    /// Constructs a new [`TrieWalker`] for the storage trie.
243    pub fn storage_trie(cursor: C, changes: PrefixSet) -> Self {
244        Self::new(
245            cursor,
246            changes,
247            #[cfg(feature = "metrics")]
248            crate::TrieType::Storage,
249        )
250    }
251
252    /// Constructs a new `TrieWalker`, setting up the initial state of the stack and cursor.
253    fn new(
254        cursor: C,
255        changes: PrefixSet,
256        #[cfg(feature = "metrics")] trie_type: crate::TrieType,
257    ) -> Self {
258        // Initialize the walker with a single empty stack element.
259        let mut this = Self {
260            cursor,
261            changes,
262            stack: vec![CursorSubNode::default()],
263            can_skip_current_node: false,
264            walk_all_changed_branch_children: false,
265            removed_keys: None,
266            added_removed_keys: Default::default(),
267            #[cfg(feature = "metrics")]
268            metrics: WalkerMetrics::new(trie_type),
269        };
270
271        // Set up the root node of the trie in the stack, if it exists.
272        if let Some((key, value)) = this.node(true).unwrap() {
273            this.stack[0] = CursorSubNode::new(key, Some(value));
274        }
275
276        // Update the skip state for the root node.
277        this.update_skip_node();
278        this
279    }
280
281    /// Advances the walker to the next trie node and updates the skip node flag.
282    /// The new key can then be obtained via `key()`.
283    ///
284    /// # Returns
285    ///
286    /// * `Result<(), Error>` - Unit on success or an error.
287    pub fn advance(&mut self) -> Result<(), DatabaseError> {
288        if let Some(last) = self.stack.last() {
289            if !self.can_skip_current_node && self.children_are_in_trie() {
290                trace!(
291                    target: "trie::walker",
292                    position = ?last.position(),
293                    "cannot skip current node and children are in the trie"
294                );
295                // If we can't skip the current node and the children are in the trie,
296                // either consume the next node or move to the next sibling.
297                match last.position() {
298                    SubNodePosition::ParentBranch => self.move_to_next_sibling(true)?,
299                    SubNodePosition::Child(_) => self.consume_node()?,
300                }
301            } else {
302                trace!(target: "trie::walker", "can skip current node");
303                // If we can skip the current node, move to the next sibling.
304                self.move_to_next_sibling(false)?;
305            }
306
307            // Update the skip node flag based on the new position in the trie.
308            self.update_skip_node();
309        }
310
311        Ok(())
312    }
313
314    /// Retrieves the current root node from the DB, seeking either the exact node or the next one.
315    fn node(&mut self, exact: bool) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
316        let key = self.key().expect("key must exist");
317        let entry = if exact { self.cursor.seek_exact(*key)? } else { self.cursor.seek(*key)? };
318        #[cfg(feature = "metrics")]
319        self.metrics.inc_branch_nodes_seeked();
320
321        if let Some((_, node)) = &entry {
322            assert!(!node.state_mask.is_empty());
323        }
324
325        Ok(entry)
326    }
327
328    /// Consumes the next node in the trie, updating the stack.
329    #[instrument(level = "trace", skip(self), ret)]
330    fn consume_node(&mut self) -> Result<(), DatabaseError> {
331        let Some((key, node)) = self.node(false)? else {
332            // If no next node is found, clear the stack.
333            self.stack.clear();
334            return Ok(())
335        };
336
337        // Overwrite the root node's first nibble
338        // We need to sync the stack with the trie structure when consuming a new node. This is
339        // necessary for proper traversal and accurately representing the trie in the stack.
340        if !key.is_empty() && !self.stack.is_empty() {
341            self.stack[0].set_nibble(key.get_unchecked(0));
342        }
343
344        // The current tree mask might have been set incorrectly.
345        // Sanity check that the newly retrieved trie node key is the child of the last item
346        // on the stack. If not, advance to the next sibling instead of adding the node to the
347        // stack.
348        if let Some(subnode) = self.stack.last() &&
349            !key.starts_with(subnode.full_key())
350        {
351            #[cfg(feature = "metrics")]
352            self.metrics.inc_out_of_order_subnode(1);
353            self.move_to_next_sibling(false)?;
354            return Ok(())
355        }
356
357        // Create a new CursorSubNode and push it to the stack.
358        let subnode = CursorSubNode::new(key, Some(node));
359        let position = subnode.position();
360        self.stack.push(subnode);
361        self.update_skip_node();
362
363        // Delete the current node if it's included in the prefix set or it doesn't contain the root
364        // hash.
365        if (!self.can_skip_current_node || position.is_child()) &&
366            let Some((keys, key)) = self.removed_keys.as_mut().zip(self.cursor.current()?)
367        {
368            keys.insert(key);
369        }
370
371        Ok(())
372    }
373
374    /// Moves to the next sibling node in the trie, updating the stack.
375    #[instrument(level = "trace", skip(self), ret)]
376    fn move_to_next_sibling(
377        &mut self,
378        allow_root_to_child_nibble: bool,
379    ) -> Result<(), DatabaseError> {
380        let Some(subnode) = self.stack.last_mut() else { return Ok(()) };
381
382        // Check if the walker needs to backtrack to the previous level in the trie during its
383        // traversal.
384        if subnode.position().is_last_child() ||
385            (subnode.position().is_parent() && !allow_root_to_child_nibble)
386        {
387            self.stack.pop();
388            self.move_to_next_sibling(false)?;
389            return Ok(())
390        }
391
392        subnode.inc_nibble();
393
394        if subnode.node.is_none() {
395            return self.consume_node()
396        }
397
398        // Find the next sibling with state.
399        loop {
400            let position = subnode.position();
401            if subnode.state_flag() {
402                trace!(target: "trie::walker", ?position, "found next sibling with state");
403                return Ok(())
404            }
405            if position.is_last_child() {
406                trace!(target: "trie::walker", ?position, "checked all siblings");
407                break
408            }
409            subnode.inc_nibble();
410        }
411
412        // Pop the current node and move to the next sibling.
413        self.stack.pop();
414        self.move_to_next_sibling(false)?;
415
416        Ok(())
417    }
418}
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423    use crate::prefix_set::PrefixSetMut;
424    use alloy_primitives::B256;
425
426    fn branch_node(state_mask: u16, tree_mask: u16, hash_mask: u16) -> BranchNodeCompact {
427        let hash_count = hash_mask.count_ones() as usize;
428        BranchNodeCompact::new(
429            TrieMask::new(state_mask),
430            TrieMask::new(tree_mask),
431            TrieMask::new(hash_mask),
432            vec![B256::ZERO; hash_count],
433            None,
434        )
435    }
436
437    fn root_branch_node(state_mask: u16, tree_mask: u16, hash_mask: u16) -> BranchNodeCompact {
438        let hash_count = hash_mask.count_ones() as usize;
439        BranchNodeCompact::new(
440            TrieMask::new(state_mask),
441            TrieMask::new(tree_mask),
442            TrieMask::new(hash_mask),
443            vec![B256::ZERO; hash_count],
444            Some(B256::ZERO),
445        )
446    }
447
448    fn walker_for_matching_branch_children_test(
449        walk_all_changed_branch_children: bool,
450    ) -> TrieWalker<crate::trie_cursor::mock::MockTrieCursor> {
451        let trie_nodes = BTreeMap::from([
452            (Nibbles::default(), root_branch_node(1 << 2, 1 << 2, 1 << 2)),
453            (
454                Nibbles::from_nibbles([0x2]),
455                branch_node((1 << 3) | (1 << 4), 0, (1 << 3) | (1 << 4)),
456            ),
457        ]);
458        let factory = MockTrieCursorFactory::new(trie_nodes, B256Map::default());
459
460        let mut prefix_set = PrefixSetMut::default();
461        prefix_set.insert(Nibbles::from_nibbles([0x2, 0x3, 0x1]));
462
463        TrieWalker::state_trie(factory.account_trie_cursor().unwrap(), prefix_set.freeze())
464            .with_walk_all_changed_branch_children(walk_all_changed_branch_children)
465    }
466
467    #[test]
468    fn branch_siblings_remain_skippable_by_default() {
469        let mut walker = walker_for_matching_branch_children_test(false);
470
471        assert_eq!(walker.key().copied(), Some(Nibbles::default()));
472        assert!(!walker.can_skip_current_node);
473
474        walker.advance().unwrap();
475        assert_eq!(walker.key().copied(), Some(Nibbles::from_nibbles([0x2])));
476        assert!(!walker.can_skip_current_node);
477
478        walker.advance().unwrap();
479        assert_eq!(walker.key().copied(), Some(Nibbles::from_nibbles([0x2, 0x3])));
480        assert_eq!(walker.stack.last().unwrap().position(), SubNodePosition::Child(0x3));
481        assert!(!walker.can_skip_current_node);
482
483        walker.advance().unwrap();
484        assert_eq!(walker.key().copied(), Some(Nibbles::from_nibbles([0x2, 0x4])));
485        assert!(walker.can_skip_current_node);
486    }
487
488    #[test]
489    fn matching_branch_path_can_make_all_children_unskippable() {
490        let mut walker = walker_for_matching_branch_children_test(true);
491
492        walker.advance().unwrap();
493        walker.advance().unwrap();
494        walker.advance().unwrap();
495        assert_eq!(walker.key().copied(), Some(Nibbles::from_nibbles([0x2, 0x4])));
496        assert!(!walker.can_skip_current_node);
497    }
498}