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: Clone,
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 #[allow(clippy::missing_const_for_fn)]
98 fn new(walker: TrieWalker<C, K>, hashed_cursor: H, trie_type: TrieType) -> Self {
99 Self {
100 walker,
101 hashed_cursor,
102 trie_type,
103 previous_hashed_key: None,
104 current_hashed_entry: None,
105 should_check_walker_key: false,
106 last_seeked_hashed_entry: None,
107 #[cfg(feature = "metrics")]
108 metrics: crate::metrics::TrieNodeIterMetrics::new(trie_type),
109 last_next_result: None,
110 }
111 }
112
113 pub const fn with_last_hashed_key(mut self, previous_hashed_key: B256) -> Self {
116 self.previous_hashed_key = Some(previous_hashed_key);
117 self
118 }
119
120 fn seek_hashed_entry(&mut self, key: B256) -> Result<Option<(B256, H::Value)>, DatabaseError> {
126 if let Some((last_key, _)) = self.last_next_result.as_ref() &&
127 *last_key == key
128 {
129 trace!(target: "trie::node_iter", seek_key = ?key, "reusing result from last next() call instead of seeking");
130 let result = self.last_next_result.take();
131 self.last_seeked_hashed_entry =
132 Some(SeekedHashedEntry { seeked_key: key, result: result.clone() });
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.clone())
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 =
151 Some(SeekedHashedEntry { seeked_key: key, result: result.clone() });
152
153 #[cfg(feature = "metrics")]
154 {
155 self.metrics.inc_leaf_nodes_seeked();
156 }
157 Ok(result)
158 }
159
160 fn next_hashed_entry(&mut self) -> Result<Option<(B256, H::Value)>, DatabaseError> {
164 let next = self.hashed_cursor.next()?;
165
166 self.last_next_result = next.clone();
167
168 #[cfg(feature = "metrics")]
169 {
170 self.metrics.inc_leaf_nodes_advanced();
171 }
172 Ok(next)
173 }
174}
175
176impl<C, H, K> TrieNodeIter<C, H, K>
177where
178 C: TrieCursor,
179 H: HashedCursor,
180 H::Value: Clone,
181 K: AsRef<AddedRemovedKeys>,
182{
183 #[instrument(
195 level = "trace",
196 target = "trie::node_iter",
197 skip_all,
198 fields(trie_type = ?self.trie_type),
199 ret
200 )]
201 pub fn try_next(
202 &mut self,
203 ) -> Result<Option<TrieElement<<H as HashedCursor>::Value>>, DatabaseError> {
204 loop {
205 if let Some(key) = self.walker.key() {
207 if !self.should_check_walker_key && self.previous_hashed_key.is_none() {
210 self.should_check_walker_key = true;
213 if self.walker.can_skip_current_node {
215 #[cfg(feature = "metrics")]
216 self.metrics.inc_branch_nodes_returned();
217 return Ok(Some(TrieElement::Branch(TrieBranchNode::new(
218 *key,
219 self.walker.hash().unwrap(),
220 self.walker.children_are_in_trie(),
221 ))))
222 }
223 }
224 }
225
226 if let Some((hashed_key, value)) = self.current_hashed_entry.take() {
228 if self.walker.key().is_some_and(|key| key < &Nibbles::unpack(hashed_key)) {
230 self.should_check_walker_key = false;
231 continue
232 }
233
234 trace!(target: "trie::node_iter", ?hashed_key, "next hashed entry");
236 self.current_hashed_entry = self.next_hashed_entry()?;
237
238 #[cfg(feature = "metrics")]
239 self.metrics.inc_leaf_nodes_returned();
240 return Ok(Some(TrieElement::Leaf(hashed_key, value)))
241 }
242
243 match self.previous_hashed_key.take() {
245 Some(hashed_key) => {
246 trace!(target: "trie::node_iter", ?hashed_key, "seeking to the previous hashed entry");
247 self.seek_hashed_entry(hashed_key)?;
249 self.current_hashed_entry = self.next_hashed_entry()?;
250 }
251 None => {
252 let (seek_key, seek_prefix) = match self.walker.next_unprocessed_key() {
255 Some(key) => key,
256 None => break, };
258
259 trace!(
260 target: "trie::node_iter",
261 ?seek_key,
262 can_skip_current_node = self.walker.can_skip_current_node,
263 last = ?self.walker.stack.last(),
264 "seeking to the next unprocessed hashed entry"
265 );
266 let can_skip_node = self.walker.can_skip_current_node;
267 self.walker.advance()?;
268 trace!(
269 target: "trie::node_iter",
270 last = ?self.walker.stack.last(),
271 "advanced walker"
272 );
273
274 if can_skip_node &&
285 self.walker.key().is_some_and(|key| key.starts_with(&seek_prefix)) &&
286 self.walker.children_are_in_trie()
287 {
288 trace!(
289 target: "trie::node_iter",
290 ?seek_key,
291 walker_hash = ?self.walker.maybe_hash(),
292 "skipping hashed seek"
293 );
294
295 self.should_check_walker_key = false;
296 continue
297 }
298
299 self.current_hashed_entry = self.seek_hashed_entry(seek_key)?;
300 }
301 }
302 }
303
304 Ok(None)
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::{TrieElement, TrieNodeIter};
311 use crate::{
312 hashed_cursor::{
313 mock::MockHashedCursorFactory, noop::NoopHashedCursor, HashedCursorFactory,
314 HashedPostStateCursor,
315 },
316 mock::{KeyVisit, KeyVisitType},
317 trie_cursor::{
318 mock::MockTrieCursorFactory, noop::NoopAccountTrieCursor, TrieCursorFactory,
319 },
320 walker::TrieWalker,
321 };
322 use alloy_primitives::{
323 b256,
324 map::{B256Map, HashMap},
325 B256,
326 };
327 use alloy_trie::{
328 BranchNodeCompact, HashBuilder, Nibbles, TrieAccount, TrieMask, EMPTY_ROOT_HASH,
329 };
330 use itertools::Itertools;
331 use reth_primitives_traits::Account;
332 use reth_trie_common::{
333 prefix_set::PrefixSetMut, updates::TrieUpdates, BranchNode, HashedPostState, LeafNode,
334 RlpNode,
335 };
336 use std::collections::BTreeMap;
337
338 fn get_hash_builder_branch_nodes(
341 state: impl IntoIterator<Item = (Nibbles, Account)> + Clone,
342 ) -> HashMap<Nibbles, BranchNodeCompact> {
343 let mut hash_builder = HashBuilder::default().with_updates(true);
344
345 let mut prefix_set = PrefixSetMut::default();
346 prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles));
347 let walker =
348 TrieWalker::<_>::state_trie(NoopAccountTrieCursor, prefix_set.freeze()).unwrap();
349
350 let hashed_post_state = HashedPostState::default()
351 .with_accounts(state.into_iter().map(|(nibbles, account)| {
352 (nibbles.pack().into_inner().unwrap().into(), Some(account))
353 }))
354 .into_sorted();
355
356 let mut node_iter = TrieNodeIter::state_trie(
357 walker,
358 HashedPostStateCursor::new_account(
359 NoopHashedCursor::<Account>::default(),
360 &hashed_post_state,
361 ),
362 );
363
364 while let Some(node) = node_iter.try_next().unwrap() {
365 match node {
366 TrieElement::Branch(branch) => {
367 hash_builder.add_branch(branch.key, branch.value, branch.children_are_in_trie);
368 }
369 TrieElement::Leaf(key, account) => {
370 hash_builder.add_leaf(
371 Nibbles::unpack(key),
372 &alloy_rlp::encode(account.into_trie_account(EMPTY_ROOT_HASH)),
373 );
374 }
375 }
376 }
377 hash_builder.root();
378
379 let mut trie_updates = TrieUpdates::default();
380 trie_updates.finalize(hash_builder, Default::default(), Default::default());
381
382 trie_updates.account_nodes
383 }
384
385 #[test]
386 fn test_trie_node_iter() {
387 fn empty_leaf_rlp_for_key(key: Nibbles) -> RlpNode {
388 RlpNode::from_rlp(&alloy_rlp::encode(LeafNode::new(
389 key,
390 alloy_rlp::encode(TrieAccount::default()),
391 )))
392 }
393
394 reth_tracing::init_test_tracing();
395
396 let account_1 = B256::ZERO;
408 let account_2 = B256::with_last_byte(0x10);
409 let account_3 = b256!("0x0000000000000000000000000000000000000000000000000000000000000100");
410 let account_4 = b256!("0x0000000000000000000000000000000000000000000000000000000000000101");
411 let account_5 = b256!("0x0000000000000000000000000000000000000000000000000000000000000110");
412
413 let hash_builder_branch_nodes = get_hash_builder_branch_nodes(vec![
414 (Nibbles::unpack(account_1), Account::default()),
415 (Nibbles::unpack(account_2), Account::default()),
416 (Nibbles::unpack(account_3), Account::default()),
417 (Nibbles::unpack(account_4), Account::default()),
418 (Nibbles::unpack(account_5), Account::default()),
419 ]);
420
421 let branch_node_1_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
422 vec![
423 empty_leaf_rlp_for_key(Nibbles::from_nibbles([0])),
424 empty_leaf_rlp_for_key(Nibbles::from_nibbles([0])),
425 ],
426 TrieMask::new(0b11),
427 )));
428
429 let branch_node_3_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
430 vec![
431 empty_leaf_rlp_for_key(Nibbles::default()),
432 empty_leaf_rlp_for_key(Nibbles::default()),
433 ],
434 TrieMask::new(0b11),
435 )));
436
437 let branch_node_2 = (
438 Nibbles::from_nibbles([vec![0; 61], vec![1]].concat()),
439 BranchNodeCompact::new(
440 TrieMask::new(0b11),
441 TrieMask::new(0b00),
442 TrieMask::new(0b01),
443 vec![branch_node_3_rlp.as_hash().unwrap()],
444 None,
445 ),
446 );
447 let branch_node_2_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
448 vec![branch_node_3_rlp, empty_leaf_rlp_for_key(Nibbles::from_nibbles([0]))],
449 TrieMask::new(0b11),
450 )));
451 let branch_node_0 = (
452 Nibbles::from_nibbles([0; 61]),
453 BranchNodeCompact::new(
454 TrieMask::new(0b11),
455 TrieMask::new(0b10),
456 TrieMask::new(0b11),
457 vec![branch_node_1_rlp.as_hash().unwrap(), branch_node_2_rlp.as_hash().unwrap()],
458 None,
459 ),
460 );
461
462 let mock_trie_nodes = vec![branch_node_0.clone(), branch_node_2.clone()];
463 pretty_assertions::assert_eq!(
464 hash_builder_branch_nodes.into_iter().sorted().collect::<Vec<_>>(),
465 mock_trie_nodes,
466 );
467
468 let trie_cursor_factory =
469 MockTrieCursorFactory::new(mock_trie_nodes.into_iter().collect(), B256Map::default());
470
471 let mut prefix_set = PrefixSetMut::default();
473 prefix_set.insert(Nibbles::unpack(account_3));
474 let prefix_set = prefix_set.freeze();
475
476 let walker = TrieWalker::<_>::state_trie(
477 trie_cursor_factory.account_trie_cursor().unwrap(),
478 prefix_set,
479 )
480 .unwrap();
481
482 let hashed_cursor_factory = MockHashedCursorFactory::new(
483 BTreeMap::from([
484 (account_1, Account::default()),
485 (account_2, Account::default()),
486 (account_3, Account::default()),
487 (account_4, Account::default()),
488 (account_5, Account::default()),
489 ]),
490 B256Map::default(),
491 );
492
493 let mut iter = TrieNodeIter::state_trie(
494 walker,
495 hashed_cursor_factory.hashed_account_cursor().unwrap(),
496 );
497
498 while iter.try_next().unwrap().is_some() {}
500
501 pretty_assertions::assert_eq!(
502 *trie_cursor_factory.visited_account_keys(),
503 vec![
504 KeyVisit {
505 visit_type: KeyVisitType::SeekExact(Nibbles::default()),
506 visited_key: None
507 },
508 KeyVisit {
509 visit_type: KeyVisitType::SeekNonExact(Nibbles::from_nibbles([0x0])),
510 visited_key: Some(branch_node_0.0)
511 },
512 KeyVisit {
513 visit_type: KeyVisitType::SeekNonExact(branch_node_2.0),
514 visited_key: Some(branch_node_2.0)
515 },
516 KeyVisit {
517 visit_type: KeyVisitType::SeekNonExact(Nibbles::from_nibbles([0x1])),
518 visited_key: None
519 }
520 ]
521 );
522 pretty_assertions::assert_eq!(
523 *hashed_cursor_factory.visited_account_keys(),
524 vec![
525 KeyVisit {
527 visit_type: KeyVisitType::SeekNonExact(account_1),
528 visited_key: Some(account_1)
529 },
530 KeyVisit {
532 visit_type: KeyVisitType::SeekNonExact(account_3),
533 visited_key: Some(account_3)
534 },
535 KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_4) },
537 KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_5) },
538 KeyVisit { visit_type: KeyVisitType::Next, visited_key: None },
539 ],
540 );
541 }
542}