reth_trie/
walker.rs

1use crate::{
2    prefix_set::PrefixSet,
3    trie_cursor::{CursorSubNode, TrieCursor},
4    BranchNodeCompact, Nibbles,
5};
6use alloy_primitives::{map::HashSet, B256};
7use reth_storage_errors::db::DatabaseError;
8
9#[cfg(feature = "metrics")]
10use crate::metrics::WalkerMetrics;
11
12/// `TrieWalker` is a structure that enables traversal of a Merkle trie.
13/// It allows moving through the trie in a depth-first manner, skipping certain branches
14/// if they have not changed.
15#[derive(Debug)]
16pub struct TrieWalker<C> {
17    /// A mutable reference to a trie cursor instance used for navigating the trie.
18    pub cursor: C,
19    /// A vector containing the trie nodes that have been visited.
20    pub stack: Vec<CursorSubNode>,
21    /// A flag indicating whether the current node can be skipped when traversing the trie. This
22    /// is determined by whether the current key's prefix is included in the prefix set and if the
23    /// hash flag is set.
24    pub can_skip_current_node: bool,
25    /// A `PrefixSet` representing the changes to be applied to the trie.
26    pub changes: PrefixSet,
27    /// The retained trie node keys that need to be removed.
28    removed_keys: Option<HashSet<Nibbles>>,
29    #[cfg(feature = "metrics")]
30    /// Walker metrics.
31    metrics: WalkerMetrics,
32}
33
34impl<C> TrieWalker<C> {
35    /// Constructs a new `TrieWalker` from existing stack and a cursor.
36    pub fn from_stack(cursor: C, stack: Vec<CursorSubNode>, changes: PrefixSet) -> Self {
37        let mut this = Self {
38            cursor,
39            changes,
40            stack,
41            can_skip_current_node: false,
42            removed_keys: None,
43            #[cfg(feature = "metrics")]
44            metrics: WalkerMetrics::default(),
45        };
46        this.update_skip_node();
47        this
48    }
49
50    /// Sets the flag whether the trie updates should be stored.
51    pub fn with_deletions_retained(mut self, retained: bool) -> Self {
52        if retained {
53            self.removed_keys = Some(HashSet::default());
54        }
55        self
56    }
57
58    /// Split the walker into stack and trie updates.
59    pub fn split(mut self) -> (Vec<CursorSubNode>, HashSet<Nibbles>) {
60        let keys = self.take_removed_keys();
61        (self.stack, keys)
62    }
63
64    /// Take removed keys from the walker.
65    pub fn take_removed_keys(&mut self) -> HashSet<Nibbles> {
66        self.removed_keys.take().unwrap_or_default()
67    }
68
69    /// Prints the current stack of trie nodes.
70    pub fn print_stack(&self) {
71        println!("====================== STACK ======================");
72        for node in &self.stack {
73            println!("{node:?}");
74        }
75        println!("====================== END STACK ======================\n");
76    }
77
78    /// The current length of the removed keys.
79    pub fn removed_keys_len(&self) -> usize {
80        self.removed_keys.as_ref().map_or(0, |u| u.len())
81    }
82
83    /// Returns the current key in the trie.
84    pub fn key(&self) -> Option<&Nibbles> {
85        self.stack.last().map(|n| n.full_key())
86    }
87
88    /// Returns the current hash in the trie if any.
89    pub fn hash(&self) -> Option<B256> {
90        self.stack.last().and_then(|n| n.hash())
91    }
92
93    /// Indicates whether the children of the current node are present in the trie.
94    pub fn children_are_in_trie(&self) -> bool {
95        self.stack.last().is_some_and(|n| n.tree_flag())
96    }
97
98    /// Returns the next unprocessed key in the trie.
99    pub fn next_unprocessed_key(&self) -> Option<B256> {
100        self.key()
101            .and_then(|key| {
102                if self.can_skip_current_node {
103                    key.increment().map(|inc| inc.pack())
104                } else {
105                    Some(key.pack())
106                }
107            })
108            .map(|mut key| {
109                key.resize(32, 0);
110                B256::from_slice(key.as_slice())
111            })
112    }
113
114    /// Updates the skip node flag based on the walker's current state.
115    fn update_skip_node(&mut self) {
116        self.can_skip_current_node = self
117            .stack
118            .last()
119            .is_some_and(|node| !self.changes.contains(node.full_key()) && node.hash_flag());
120    }
121}
122
123impl<C: TrieCursor> TrieWalker<C> {
124    /// Constructs a new `TrieWalker`, setting up the initial state of the stack and cursor.
125    pub fn new(cursor: C, changes: PrefixSet) -> Self {
126        // Initialize the walker with a single empty stack element.
127        let mut this = Self {
128            cursor,
129            changes,
130            stack: vec![CursorSubNode::default()],
131            can_skip_current_node: false,
132            removed_keys: None,
133            #[cfg(feature = "metrics")]
134            metrics: WalkerMetrics::default(),
135        };
136
137        // Set up the root node of the trie in the stack, if it exists.
138        if let Some((key, value)) = this.node(true).unwrap() {
139            this.stack[0] = CursorSubNode::new(key, Some(value));
140        }
141
142        // Update the skip state for the root node.
143        this.update_skip_node();
144        this
145    }
146
147    /// Advances the walker to the next trie node and updates the skip node flag.
148    /// The new key can then be obtained via `key()`.
149    ///
150    /// # Returns
151    ///
152    /// * `Result<(), Error>` - Unit on success or an error.
153    pub fn advance(&mut self) -> Result<(), DatabaseError> {
154        if let Some(last) = self.stack.last() {
155            if !self.can_skip_current_node && self.children_are_in_trie() {
156                // If we can't skip the current node and the children are in the trie,
157                // either consume the next node or move to the next sibling.
158                match last.nibble() {
159                    -1 => self.move_to_next_sibling(true)?,
160                    _ => self.consume_node()?,
161                }
162            } else {
163                // If we can skip the current node, move to the next sibling.
164                self.move_to_next_sibling(false)?;
165            }
166
167            // Update the skip node flag based on the new position in the trie.
168            self.update_skip_node();
169        }
170
171        Ok(())
172    }
173
174    /// Retrieves the current root node from the DB, seeking either the exact node or the next one.
175    fn node(&mut self, exact: bool) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
176        let key = self.key().expect("key must exist").clone();
177        let entry = if exact { self.cursor.seek_exact(key)? } else { self.cursor.seek(key)? };
178
179        if let Some((_, node)) = &entry {
180            assert!(!node.state_mask.is_empty());
181        }
182
183        Ok(entry)
184    }
185
186    /// Consumes the next node in the trie, updating the stack.
187    fn consume_node(&mut self) -> Result<(), DatabaseError> {
188        let Some((key, node)) = self.node(false)? else {
189            // If no next node is found, clear the stack.
190            self.stack.clear();
191            return Ok(())
192        };
193
194        // Overwrite the root node's first nibble
195        // We need to sync the stack with the trie structure when consuming a new node. This is
196        // necessary for proper traversal and accurately representing the trie in the stack.
197        if !key.is_empty() && !self.stack.is_empty() {
198            self.stack[0].set_nibble(key[0] as i8);
199        }
200
201        // The current tree mask might have been set incorrectly.
202        // Sanity check that the newly retrieved trie node key is the child of the last item
203        // on the stack. If not, advance to the next sibling instead of adding the node to the
204        // stack.
205        if let Some(subnode) = self.stack.last() {
206            if !key.starts_with(subnode.full_key()) {
207                #[cfg(feature = "metrics")]
208                self.metrics.inc_out_of_order_subnode(1);
209                self.move_to_next_sibling(false)?;
210                return Ok(())
211            }
212        }
213
214        // Create a new CursorSubNode and push it to the stack.
215        let subnode = CursorSubNode::new(key, Some(node));
216        let nibble = subnode.nibble();
217        self.stack.push(subnode);
218        self.update_skip_node();
219
220        // Delete the current node if it's included in the prefix set or it doesn't contain the root
221        // hash.
222        if !self.can_skip_current_node || nibble != -1 {
223            if let Some((keys, key)) = self.removed_keys.as_mut().zip(self.cursor.current()?) {
224                keys.insert(key);
225            }
226        }
227
228        Ok(())
229    }
230
231    /// Moves to the next sibling node in the trie, updating the stack.
232    fn move_to_next_sibling(
233        &mut self,
234        allow_root_to_child_nibble: bool,
235    ) -> Result<(), DatabaseError> {
236        let Some(subnode) = self.stack.last_mut() else { return Ok(()) };
237
238        // Check if the walker needs to backtrack to the previous level in the trie during its
239        // traversal.
240        if subnode.nibble() >= 0xf || (subnode.nibble() < 0 && !allow_root_to_child_nibble) {
241            self.stack.pop();
242            self.move_to_next_sibling(false)?;
243            return Ok(())
244        }
245
246        subnode.inc_nibble();
247
248        if subnode.node.is_none() {
249            return self.consume_node()
250        }
251
252        // Find the next sibling with state.
253        loop {
254            if subnode.state_flag() {
255                return Ok(())
256            }
257            if subnode.nibble() == 0xf {
258                break
259            }
260            subnode.inc_nibble();
261        }
262
263        // Pop the current node and move to the next sibling.
264        self.stack.pop();
265        self.move_to_next_sibling(false)?;
266
267        Ok(())
268    }
269}