1use crate::{
2 hashed_cursor::HashedCursor, trie_cursor::TrieCursor, walker::TrieWalker, Nibbles, TrieType,
3};
4use alloy_primitives::B256;
5use alloy_trie::proof::AddedRemovedKeys;
6use reth_storage_errors::db::DatabaseError;
7use tracing::{instrument, trace};
8
9#[derive(Debug)]
11pub struct TrieBranchNode {
12 pub key: Nibbles,
14 pub value: B256,
16 pub children_are_in_trie: bool,
18}
19
20impl TrieBranchNode {
21 pub const fn new(key: Nibbles, value: B256, children_are_in_trie: bool) -> Self {
23 Self { key, value, children_are_in_trie }
24 }
25}
26
27#[derive(Debug)]
29pub enum TrieElement<Value> {
30 Branch(TrieBranchNode),
32 Leaf(B256, Value),
34}
35
36#[derive(Debug)]
38struct SeekedHashedEntry<V> {
39 seeked_key: B256,
41 result: Option<(B256, V)>,
45}
46
47#[derive(Debug)]
52pub struct TrieNodeIter<C, H: HashedCursor, K> {
53 pub walker: TrieWalker<C, K>,
55 pub hashed_cursor: H,
57 trie_type: TrieType,
59 previous_hashed_key: Option<B256>,
62
63 current_hashed_entry: Option<(B256, H::Value)>,
65 should_check_walker_key: bool,
67
68 last_seeked_hashed_entry: Option<SeekedHashedEntry<H::Value>>,
72
73 #[cfg(feature = "metrics")]
74 metrics: crate::metrics::TrieNodeIterMetrics,
75 last_next_result: Option<(B256, H::Value)>,
79}
80
81impl<C, H: HashedCursor, K> TrieNodeIter<C, H, K>
82where
83 H::Value: Copy,
84 K: AsRef<AddedRemovedKeys>,
85{
86 pub fn state_trie(walker: TrieWalker<C, K>, hashed_cursor: H) -> Self {
88 Self::new(walker, hashed_cursor, TrieType::State)
89 }
90
91 pub fn storage_trie(walker: TrieWalker<C, K>, hashed_cursor: H) -> Self {
93 Self::new(walker, hashed_cursor, TrieType::Storage)
94 }
95
96 fn new(walker: TrieWalker<C, K>, hashed_cursor: H, trie_type: TrieType) -> Self {
98 Self {
99 walker,
100 hashed_cursor,
101 trie_type,
102 previous_hashed_key: None,
103 current_hashed_entry: None,
104 should_check_walker_key: false,
105 last_seeked_hashed_entry: None,
106 #[cfg(feature = "metrics")]
107 metrics: crate::metrics::TrieNodeIterMetrics::new(trie_type),
108 last_next_result: None,
109 }
110 }
111
112 pub const fn with_last_hashed_key(mut self, previous_hashed_key: B256) -> Self {
115 self.previous_hashed_key = Some(previous_hashed_key);
116 self
117 }
118
119 fn seek_hashed_entry(&mut self, key: B256) -> Result<Option<(B256, H::Value)>, DatabaseError> {
125 if let Some((last_key, last_value)) = self.last_next_result &&
126 last_key == key
127 {
128 trace!(target: "trie::node_iter", seek_key = ?key, "reusing result from last next() call instead of seeking");
129 self.last_next_result = None; let result = Some((last_key, last_value));
132 self.last_seeked_hashed_entry = Some(SeekedHashedEntry { seeked_key: key, result });
133
134 return Ok(result);
135 }
136
137 if let Some(entry) = self
138 .last_seeked_hashed_entry
139 .as_ref()
140 .filter(|entry| entry.seeked_key == key)
141 .map(|entry| entry.result)
142 {
143 #[cfg(feature = "metrics")]
144 self.metrics.inc_leaf_nodes_same_seeked();
145 return Ok(entry);
146 }
147
148 trace!(target: "trie::node_iter", ?key, "performing hashed cursor seek");
149 let result = self.hashed_cursor.seek(key)?;
150 self.last_seeked_hashed_entry = Some(SeekedHashedEntry { seeked_key: key, result });
151
152 #[cfg(feature = "metrics")]
153 {
154 self.metrics.inc_leaf_nodes_seeked();
155 }
156 Ok(result)
157 }
158
159 fn next_hashed_entry(&mut self) -> Result<Option<(B256, H::Value)>, DatabaseError> {
163 let result = self.hashed_cursor.next();
164
165 self.last_next_result = result.clone()?;
166
167 #[cfg(feature = "metrics")]
168 {
169 self.metrics.inc_leaf_nodes_advanced();
170 }
171 result
172 }
173}
174
175impl<C, H, K> TrieNodeIter<C, H, K>
176where
177 C: TrieCursor,
178 H: HashedCursor,
179 H::Value: Copy,
180 K: AsRef<AddedRemovedKeys>,
181{
182 #[instrument(
194 level = "trace",
195 target = "trie::node_iter",
196 skip_all,
197 fields(trie_type = ?self.trie_type),
198 ret
199 )]
200 pub fn try_next(
201 &mut self,
202 ) -> Result<Option<TrieElement<<H as HashedCursor>::Value>>, DatabaseError> {
203 loop {
204 if let Some(key) = self.walker.key() {
206 if !self.should_check_walker_key && self.previous_hashed_key.is_none() {
209 self.should_check_walker_key = true;
212 if self.walker.can_skip_current_node {
214 #[cfg(feature = "metrics")]
215 self.metrics.inc_branch_nodes_returned();
216 return Ok(Some(TrieElement::Branch(TrieBranchNode::new(
217 *key,
218 self.walker.hash().unwrap(),
219 self.walker.children_are_in_trie(),
220 ))))
221 }
222 }
223 }
224
225 if let Some((hashed_key, value)) = self.current_hashed_entry.take() {
227 if self.walker.key().is_some_and(|key| key < &Nibbles::unpack(hashed_key)) {
229 self.should_check_walker_key = false;
230 continue
231 }
232
233 trace!(target: "trie::node_iter", ?hashed_key, "next hashed entry");
235 self.current_hashed_entry = self.next_hashed_entry()?;
236
237 #[cfg(feature = "metrics")]
238 self.metrics.inc_leaf_nodes_returned();
239 return Ok(Some(TrieElement::Leaf(hashed_key, value)))
240 }
241
242 match self.previous_hashed_key.take() {
244 Some(hashed_key) => {
245 trace!(target: "trie::node_iter", ?hashed_key, "seeking to the previous hashed entry");
246 self.seek_hashed_entry(hashed_key)?;
248 self.current_hashed_entry = self.next_hashed_entry()?;
249 }
250 None => {
251 let (seek_key, seek_prefix) = match self.walker.next_unprocessed_key() {
254 Some(key) => key,
255 None => break, };
257
258 trace!(
259 target: "trie::node_iter",
260 ?seek_key,
261 can_skip_current_node = self.walker.can_skip_current_node,
262 last = ?self.walker.stack.last(),
263 "seeking to the next unprocessed hashed entry"
264 );
265 let can_skip_node = self.walker.can_skip_current_node;
266 self.walker.advance()?;
267 trace!(
268 target: "trie::node_iter",
269 last = ?self.walker.stack.last(),
270 "advanced walker"
271 );
272
273 if can_skip_node &&
284 self.walker.key().is_some_and(|key| key.starts_with(&seek_prefix)) &&
285 self.walker.children_are_in_trie()
286 {
287 trace!(
288 target: "trie::node_iter",
289 ?seek_key,
290 walker_hash = ?self.walker.maybe_hash(),
291 "skipping hashed seek"
292 );
293
294 self.should_check_walker_key = false;
295 continue
296 }
297
298 self.current_hashed_entry = self.seek_hashed_entry(seek_key)?;
299 }
300 }
301 }
302
303 Ok(None)
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::{TrieElement, TrieNodeIter};
310 use crate::{
311 hashed_cursor::{
312 mock::MockHashedCursorFactory, noop::NoopHashedCursor, HashedCursorFactory,
313 HashedPostStateCursor,
314 },
315 mock::{KeyVisit, KeyVisitType},
316 trie_cursor::{
317 mock::MockTrieCursorFactory, noop::NoopAccountTrieCursor, TrieCursorFactory,
318 },
319 walker::TrieWalker,
320 };
321 use alloy_primitives::{
322 b256,
323 map::{B256Map, HashMap},
324 };
325 use alloy_trie::{
326 BranchNodeCompact, HashBuilder, Nibbles, TrieAccount, TrieMask, EMPTY_ROOT_HASH,
327 };
328 use itertools::Itertools;
329 use reth_primitives_traits::Account;
330 use reth_trie_common::{
331 prefix_set::PrefixSetMut, updates::TrieUpdates, BranchNode, HashedPostState, LeafNode,
332 RlpNode,
333 };
334 use std::collections::BTreeMap;
335
336 fn get_hash_builder_branch_nodes(
339 state: impl IntoIterator<Item = (Nibbles, Account)> + Clone,
340 ) -> HashMap<Nibbles, BranchNodeCompact> {
341 let mut hash_builder = HashBuilder::default().with_updates(true);
342
343 let mut prefix_set = PrefixSetMut::default();
344 prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles));
345 let walker = TrieWalker::<_>::state_trie(NoopAccountTrieCursor, prefix_set.freeze());
346
347 let hashed_post_state = HashedPostState::default()
348 .with_accounts(state.into_iter().map(|(nibbles, account)| {
349 (nibbles.pack().into_inner().unwrap().into(), Some(account))
350 }))
351 .into_sorted();
352
353 let mut node_iter = TrieNodeIter::state_trie(
354 walker,
355 HashedPostStateCursor::new_account(
356 NoopHashedCursor::<Account>::default(),
357 &hashed_post_state,
358 ),
359 );
360
361 while let Some(node) = node_iter.try_next().unwrap() {
362 match node {
363 TrieElement::Branch(branch) => {
364 hash_builder.add_branch(branch.key, branch.value, branch.children_are_in_trie);
365 }
366 TrieElement::Leaf(key, account) => {
367 hash_builder.add_leaf(
368 Nibbles::unpack(key),
369 &alloy_rlp::encode(account.into_trie_account(EMPTY_ROOT_HASH)),
370 );
371 }
372 }
373 }
374 hash_builder.root();
375
376 let mut trie_updates = TrieUpdates::default();
377 trie_updates.finalize(hash_builder, Default::default(), Default::default());
378
379 trie_updates.account_nodes
380 }
381
382 #[test]
383 fn test_trie_node_iter() {
384 fn empty_leaf_rlp_for_key(key: Nibbles) -> RlpNode {
385 RlpNode::from_rlp(&alloy_rlp::encode(LeafNode::new(
386 key,
387 alloy_rlp::encode(TrieAccount::default()),
388 )))
389 }
390
391 reth_tracing::init_test_tracing();
392
393 let account_1 = b256!("0x0000000000000000000000000000000000000000000000000000000000000000");
405 let account_2 = b256!("0x0000000000000000000000000000000000000000000000000000000000000010");
406 let account_3 = b256!("0x0000000000000000000000000000000000000000000000000000000000000100");
407 let account_4 = b256!("0x0000000000000000000000000000000000000000000000000000000000000101");
408 let account_5 = b256!("0x0000000000000000000000000000000000000000000000000000000000000110");
409 let empty_account = Account::default();
410
411 let hash_builder_branch_nodes = get_hash_builder_branch_nodes(vec![
412 (Nibbles::unpack(account_1), empty_account),
413 (Nibbles::unpack(account_2), empty_account),
414 (Nibbles::unpack(account_3), empty_account),
415 (Nibbles::unpack(account_4), empty_account),
416 (Nibbles::unpack(account_5), empty_account),
417 ]);
418
419 let branch_node_1_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
420 vec![
421 empty_leaf_rlp_for_key(Nibbles::from_nibbles([0])),
422 empty_leaf_rlp_for_key(Nibbles::from_nibbles([0])),
423 ],
424 TrieMask::new(0b11),
425 )));
426
427 let branch_node_3_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
428 vec![
429 empty_leaf_rlp_for_key(Nibbles::default()),
430 empty_leaf_rlp_for_key(Nibbles::default()),
431 ],
432 TrieMask::new(0b11),
433 )));
434
435 let branch_node_2 = (
436 Nibbles::from_nibbles([vec![0; 61], vec![1]].concat()),
437 BranchNodeCompact::new(
438 TrieMask::new(0b11),
439 TrieMask::new(0b00),
440 TrieMask::new(0b01),
441 vec![branch_node_3_rlp.as_hash().unwrap()],
442 None,
443 ),
444 );
445 let branch_node_2_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
446 vec![branch_node_3_rlp, empty_leaf_rlp_for_key(Nibbles::from_nibbles([0]))],
447 TrieMask::new(0b11),
448 )));
449 let branch_node_0 = (
450 Nibbles::from_nibbles([0; 61]),
451 BranchNodeCompact::new(
452 TrieMask::new(0b11),
453 TrieMask::new(0b10),
454 TrieMask::new(0b11),
455 vec![branch_node_1_rlp.as_hash().unwrap(), branch_node_2_rlp.as_hash().unwrap()],
456 None,
457 ),
458 );
459
460 let mock_trie_nodes = vec![branch_node_0.clone(), branch_node_2.clone()];
461 pretty_assertions::assert_eq!(
462 hash_builder_branch_nodes.into_iter().sorted().collect::<Vec<_>>(),
463 mock_trie_nodes,
464 );
465
466 let trie_cursor_factory =
467 MockTrieCursorFactory::new(mock_trie_nodes.into_iter().collect(), B256Map::default());
468
469 let mut prefix_set = PrefixSetMut::default();
471 prefix_set.insert(Nibbles::unpack(account_3));
472 let prefix_set = prefix_set.freeze();
473
474 let walker = TrieWalker::<_>::state_trie(
475 trie_cursor_factory.account_trie_cursor().unwrap(),
476 prefix_set,
477 );
478
479 let hashed_cursor_factory = MockHashedCursorFactory::new(
480 BTreeMap::from([
481 (account_1, empty_account),
482 (account_2, empty_account),
483 (account_3, empty_account),
484 (account_4, empty_account),
485 (account_5, empty_account),
486 ]),
487 B256Map::default(),
488 );
489
490 let mut iter = TrieNodeIter::state_trie(
491 walker,
492 hashed_cursor_factory.hashed_account_cursor().unwrap(),
493 );
494
495 while iter.try_next().unwrap().is_some() {}
497
498 pretty_assertions::assert_eq!(
499 *trie_cursor_factory.visited_account_keys(),
500 vec![
501 KeyVisit {
502 visit_type: KeyVisitType::SeekExact(Nibbles::default()),
503 visited_key: None
504 },
505 KeyVisit {
506 visit_type: KeyVisitType::SeekNonExact(Nibbles::from_nibbles([0x0])),
507 visited_key: Some(branch_node_0.0)
508 },
509 KeyVisit {
510 visit_type: KeyVisitType::SeekNonExact(branch_node_2.0),
511 visited_key: Some(branch_node_2.0)
512 },
513 KeyVisit {
514 visit_type: KeyVisitType::SeekNonExact(Nibbles::from_nibbles([0x1])),
515 visited_key: None
516 }
517 ]
518 );
519 pretty_assertions::assert_eq!(
520 *hashed_cursor_factory.visited_account_keys(),
521 vec![
522 KeyVisit {
524 visit_type: KeyVisitType::SeekNonExact(account_1),
525 visited_key: Some(account_1)
526 },
527 KeyVisit {
529 visit_type: KeyVisitType::SeekNonExact(account_3),
530 visited_key: Some(account_3)
531 },
532 KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_4) },
534 KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_5) },
535 KeyVisit { visit_type: KeyVisitType::Next, visited_key: None },
536 ],
537 );
538 }
539}