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#[derive(Debug)]
30pub struct TrieWalker<C, K = AddedRemovedKeys> {
31 pub cursor: C,
33 pub stack: Vec<CursorSubNode>,
35 pub can_skip_current_node: bool,
39 pub changes: PrefixSet,
41 walk_all_changed_branch_children: bool,
44 removed_keys: Option<HashSet<Nibbles>>,
46 added_removed_keys: Option<K>,
50 #[cfg(feature = "metrics")]
51 metrics: WalkerMetrics,
53}
54
55impl<C: TrieCursor, K: AsRef<AddedRemovedKeys>> TrieWalker<C, K> {
56 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 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 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 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 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 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 pub fn split(mut self) -> (Vec<CursorSubNode>, HashSet<Nibbles>) {
136 let keys = self.take_removed_keys();
137 (self.stack, keys)
138 }
139
140 pub fn take_removed_keys(&mut self) -> HashSet<Nibbles> {
142 self.removed_keys.take().unwrap_or_default()
143 }
144
145 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 pub fn removed_keys_len(&self) -> usize {
156 self.removed_keys.as_ref().map_or(0, |u| u.len())
157 }
158
159 pub fn key(&self) -> Option<&Nibbles> {
161 self.stack.last().map(|n| n.full_key())
162 }
163
164 pub fn hash(&self) -> Option<B256> {
166 self.stack.last().and_then(|n| n.hash())
167 }
168
169 pub fn maybe_hash(&self) -> Option<B256> {
174 self.stack.last().and_then(|n| n.maybe_hash())
175 }
176
177 pub fn children_are_in_trie(&self) -> bool {
179 self.stack.last().is_some_and(|n| n.tree_flag())
180 }
181
182 #[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 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 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 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 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 fn new(
254 cursor: C,
255 changes: PrefixSet,
256 #[cfg(feature = "metrics")] trie_type: crate::TrieType,
257 ) -> Self {
258 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 if let Some((key, value)) = this.node(true).unwrap() {
273 this.stack[0] = CursorSubNode::new(key, Some(value));
274 }
275
276 this.update_skip_node();
278 this
279 }
280
281 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 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 self.move_to_next_sibling(false)?;
305 }
306
307 self.update_skip_node();
309 }
310
311 Ok(())
312 }
313
314 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 #[instrument(level = "trace", skip(self), ret)]
330 fn consume_node(&mut self) -> Result<(), DatabaseError> {
331 let Some((key, node)) = self.node(false)? else {
332 self.stack.clear();
334 return Ok(())
335 };
336
337 if !key.is_empty() && !self.stack.is_empty() {
341 self.stack[0].set_nibble(key.get_unchecked(0));
342 }
343
344 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 let subnode = CursorSubNode::new(key, Some(node));
359 let position = subnode.position();
360 self.stack.push(subnode);
361 self.update_skip_node();
362
363 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 #[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 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 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 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}