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 };
326 use alloy_trie::{
327 BranchNodeCompact, HashBuilder, Nibbles, TrieAccount, TrieMask, EMPTY_ROOT_HASH,
328 };
329 use itertools::Itertools;
330 use reth_primitives_traits::Account;
331 use reth_trie_common::{
332 prefix_set::PrefixSetMut, updates::TrieUpdates, BranchNode, HashedPostState, LeafNode,
333 RlpNode,
334 };
335 use std::collections::BTreeMap;
336
337 fn get_hash_builder_branch_nodes(
340 state: impl IntoIterator<Item = (Nibbles, Account)> + Clone,
341 ) -> HashMap<Nibbles, BranchNodeCompact> {
342 let mut hash_builder = HashBuilder::default().with_updates(true);
343
344 let mut prefix_set = PrefixSetMut::default();
345 prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles));
346 let walker = TrieWalker::<_>::state_trie(NoopAccountTrieCursor, prefix_set.freeze());
347
348 let hashed_post_state = HashedPostState::default()
349 .with_accounts(state.into_iter().map(|(nibbles, account)| {
350 (nibbles.pack().into_inner().unwrap().into(), Some(account))
351 }))
352 .into_sorted();
353
354 let mut node_iter = TrieNodeIter::state_trie(
355 walker,
356 HashedPostStateCursor::new_account(
357 NoopHashedCursor::<Account>::default(),
358 &hashed_post_state,
359 ),
360 );
361
362 while let Some(node) = node_iter.try_next().unwrap() {
363 match node {
364 TrieElement::Branch(branch) => {
365 hash_builder.add_branch(branch.key, branch.value, branch.children_are_in_trie);
366 }
367 TrieElement::Leaf(key, account) => {
368 hash_builder.add_leaf(
369 Nibbles::unpack(key),
370 &alloy_rlp::encode(account.into_trie_account(EMPTY_ROOT_HASH)),
371 );
372 }
373 }
374 }
375 hash_builder.root();
376
377 let mut trie_updates = TrieUpdates::default();
378 trie_updates.finalize(hash_builder, Default::default(), Default::default());
379
380 trie_updates.account_nodes
381 }
382
383 #[test]
384 fn test_trie_node_iter() {
385 fn empty_leaf_rlp_for_key(key: Nibbles) -> RlpNode {
386 RlpNode::from_rlp(&alloy_rlp::encode(LeafNode::new(
387 key,
388 alloy_rlp::encode(TrieAccount::default()),
389 )))
390 }
391
392 reth_tracing::init_test_tracing();
393
394 let account_1 = b256!("0x0000000000000000000000000000000000000000000000000000000000000000");
406 let account_2 = b256!("0x0000000000000000000000000000000000000000000000000000000000000010");
407 let account_3 = b256!("0x0000000000000000000000000000000000000000000000000000000000000100");
408 let account_4 = b256!("0x0000000000000000000000000000000000000000000000000000000000000101");
409 let account_5 = b256!("0x0000000000000000000000000000000000000000000000000000000000000110");
410
411 let hash_builder_branch_nodes = get_hash_builder_branch_nodes(vec![
412 (Nibbles::unpack(account_1), Account::default()),
413 (Nibbles::unpack(account_2), Account::default()),
414 (Nibbles::unpack(account_3), Account::default()),
415 (Nibbles::unpack(account_4), Account::default()),
416 (Nibbles::unpack(account_5), Account::default()),
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, Account::default()),
482 (account_2, Account::default()),
483 (account_3, Account::default()),
484 (account_4, Account::default()),
485 (account_5, Account::default()),
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}