1use crate::{hashed_cursor::HashedCursor, trie_cursor::TrieCursor, walker::TrieWalker, Nibbles};
2use alloy_primitives::B256;
3use reth_storage_errors::db::DatabaseError;
4use tracing::trace;
5
6#[derive(Debug)]
8pub struct TrieBranchNode {
9 pub key: Nibbles,
11 pub value: B256,
13 pub children_are_in_trie: bool,
15}
16
17impl TrieBranchNode {
18 pub const fn new(key: Nibbles, value: B256, children_are_in_trie: bool) -> Self {
20 Self { key, value, children_are_in_trie }
21 }
22}
23
24#[derive(Debug)]
26pub enum TrieElement<Value> {
27 Branch(TrieBranchNode),
29 Leaf(B256, Value),
31}
32
33#[derive(Debug)]
35struct SeekedHashedEntry<V> {
36 seeked_key: B256,
38 result: Option<(B256, V)>,
42}
43
44#[derive(Debug)]
46pub struct TrieNodeIter<C, H: HashedCursor> {
47 pub walker: TrieWalker<C>,
49 pub hashed_cursor: H,
51 previous_hashed_key: Option<B256>,
54
55 current_hashed_entry: Option<(B256, H::Value)>,
57 should_check_walker_key: bool,
59
60 last_seeked_hashed_entry: Option<SeekedHashedEntry<H::Value>>,
64
65 #[cfg(feature = "metrics")]
66 metrics: crate::metrics::TrieNodeIterMetrics,
67 #[cfg(feature = "metrics")]
69 previously_advanced_to_key: Option<B256>,
70}
71
72impl<C, H: HashedCursor> TrieNodeIter<C, H>
73where
74 H::Value: Copy,
75{
76 pub fn state_trie(walker: TrieWalker<C>, hashed_cursor: H) -> Self {
78 Self::new(
79 walker,
80 hashed_cursor,
81 #[cfg(feature = "metrics")]
82 crate::TrieType::State,
83 )
84 }
85
86 pub fn storage_trie(walker: TrieWalker<C>, hashed_cursor: H) -> Self {
88 Self::new(
89 walker,
90 hashed_cursor,
91 #[cfg(feature = "metrics")]
92 crate::TrieType::Storage,
93 )
94 }
95
96 fn new(
98 walker: TrieWalker<C>,
99 hashed_cursor: H,
100 #[cfg(feature = "metrics")] trie_type: crate::TrieType,
101 ) -> Self {
102 Self {
103 walker,
104 hashed_cursor,
105 previous_hashed_key: None,
106 current_hashed_entry: None,
107 should_check_walker_key: false,
108 last_seeked_hashed_entry: None,
109 #[cfg(feature = "metrics")]
110 metrics: crate::metrics::TrieNodeIterMetrics::new(trie_type),
111 #[cfg(feature = "metrics")]
112 previously_advanced_to_key: None,
113 }
114 }
115
116 pub const fn with_last_hashed_key(mut self, previous_hashed_key: B256) -> Self {
119 self.previous_hashed_key = Some(previous_hashed_key);
120 self
121 }
122
123 fn seek_hashed_entry(&mut self, key: B256) -> Result<Option<(B256, H::Value)>, DatabaseError> {
129 if let Some(entry) = self
130 .last_seeked_hashed_entry
131 .as_ref()
132 .filter(|entry| entry.seeked_key == key)
133 .map(|entry| entry.result)
134 {
135 #[cfg(feature = "metrics")]
136 self.metrics.inc_leaf_nodes_same_seeked();
137 return Ok(entry);
138 }
139
140 let result = self.hashed_cursor.seek(key)?;
141 self.last_seeked_hashed_entry = Some(SeekedHashedEntry { seeked_key: key, result });
142
143 #[cfg(feature = "metrics")]
144 {
145 self.metrics.inc_leaf_nodes_seeked();
146
147 if Some(key) == self.previously_advanced_to_key {
148 self.metrics.inc_leaf_nodes_same_seeked_as_advanced();
149 }
150 }
151 Ok(result)
152 }
153
154 fn next_hashed_entry(&mut self) -> Result<Option<(B256, H::Value)>, DatabaseError> {
158 let result = self.hashed_cursor.next();
159 #[cfg(feature = "metrics")]
160 {
161 self.metrics.inc_leaf_nodes_advanced();
162
163 self.previously_advanced_to_key =
164 result.as_ref().ok().and_then(|result| result.as_ref().map(|(k, _)| *k));
165 }
166 result
167 }
168}
169
170impl<C, H> TrieNodeIter<C, H>
171where
172 C: TrieCursor,
173 H: HashedCursor,
174 H::Value: Copy,
175{
176 pub fn try_next(
188 &mut self,
189 ) -> Result<Option<TrieElement<<H as HashedCursor>::Value>>, DatabaseError> {
190 loop {
191 if let Some(key) = self.walker.key() {
193 if !self.should_check_walker_key && self.previous_hashed_key.is_none() {
196 self.should_check_walker_key = true;
199 if self.walker.can_skip_current_node {
201 #[cfg(feature = "metrics")]
202 self.metrics.inc_branch_nodes_returned();
203 return Ok(Some(TrieElement::Branch(TrieBranchNode::new(
204 key.clone(),
205 self.walker.hash().unwrap(),
206 self.walker.children_are_in_trie(),
207 ))))
208 }
209 }
210 }
211
212 if let Some((hashed_key, value)) = self.current_hashed_entry.take() {
214 if self.walker.key().is_some_and(|key| key < &Nibbles::unpack(hashed_key)) {
216 self.should_check_walker_key = false;
217 continue
218 }
219
220 trace!(target: "trie::node_iter", ?hashed_key, "next hashed entry");
222 self.current_hashed_entry = self.next_hashed_entry()?;
223
224 #[cfg(feature = "metrics")]
225 self.metrics.inc_leaf_nodes_returned();
226 return Ok(Some(TrieElement::Leaf(hashed_key, value)))
227 }
228
229 match self.previous_hashed_key.take() {
231 Some(hashed_key) => {
232 trace!(target: "trie::node_iter", ?hashed_key, "seeking to the previous hashed entry");
233 self.seek_hashed_entry(hashed_key)?;
235 self.current_hashed_entry = self.next_hashed_entry()?;
236 }
237 None => {
238 let (seek_key, seek_prefix) = match self.walker.next_unprocessed_key() {
241 Some(key) => key,
242 None => break, };
244
245 trace!(
246 target: "trie::node_iter",
247 ?seek_key,
248 can_skip_current_node = self.walker.can_skip_current_node,
249 last = ?self.walker.stack.last(),
250 "seeking to the next unprocessed hashed entry"
251 );
252 let can_skip_node = self.walker.can_skip_current_node;
253 self.walker.advance()?;
254 trace!(
255 target: "trie::node_iter",
256 last = ?self.walker.stack.last(),
257 "advanced walker"
258 );
259
260 if can_skip_node &&
271 self.walker.key().is_some_and(|key| key.has_prefix(&seek_prefix)) &&
272 self.walker.children_are_in_trie()
273 {
274 trace!(
275 target: "trie::node_iter",
276 ?seek_key,
277 walker_hash = ?self.walker.hash(),
278 "skipping hashed seek"
279 );
280
281 self.should_check_walker_key = false;
282 continue
283 }
284
285 self.current_hashed_entry = self.seek_hashed_entry(seek_key)?;
286 }
287 }
288 }
289
290 Ok(None)
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use crate::{
297 hashed_cursor::{
298 mock::MockHashedCursorFactory, noop::NoopHashedAccountCursor, HashedCursorFactory,
299 HashedPostStateAccountCursor,
300 },
301 mock::{KeyVisit, KeyVisitType},
302 trie_cursor::{
303 mock::MockTrieCursorFactory, noop::NoopAccountTrieCursor, TrieCursorFactory,
304 },
305 walker::TrieWalker,
306 };
307 use alloy_primitives::{
308 b256,
309 map::{B256Map, HashMap},
310 };
311 use alloy_trie::{
312 BranchNodeCompact, HashBuilder, Nibbles, TrieAccount, TrieMask, EMPTY_ROOT_HASH,
313 };
314 use itertools::Itertools;
315 use reth_primitives_traits::Account;
316 use reth_trie_common::{
317 prefix_set::PrefixSetMut, updates::TrieUpdates, BranchNode, HashedPostState, LeafNode,
318 RlpNode,
319 };
320 use std::collections::BTreeMap;
321
322 use super::{TrieElement, TrieNodeIter};
323
324 fn get_hash_builder_branch_nodes(
327 state: impl IntoIterator<Item = (Nibbles, Account)> + Clone,
328 ) -> HashMap<Nibbles, BranchNodeCompact> {
329 let mut hash_builder = HashBuilder::default().with_updates(true);
330
331 let mut prefix_set = PrefixSetMut::default();
332 prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles));
333 let walker = TrieWalker::new(NoopAccountTrieCursor, prefix_set.freeze());
334
335 let hashed_post_state = HashedPostState::default()
336 .with_accounts(state.into_iter().map(|(nibbles, account)| {
337 (nibbles.pack().into_inner().unwrap().into(), Some(account))
338 }))
339 .into_sorted();
340
341 let mut node_iter = TrieNodeIter::state_trie(
342 walker,
343 HashedPostStateAccountCursor::new(
344 NoopHashedAccountCursor::default(),
345 hashed_post_state.accounts(),
346 ),
347 );
348
349 while let Some(node) = node_iter.try_next().unwrap() {
350 match node {
351 TrieElement::Branch(branch) => {
352 hash_builder.add_branch(branch.key, branch.value, branch.children_are_in_trie);
353 }
354 TrieElement::Leaf(key, account) => {
355 hash_builder.add_leaf(
356 Nibbles::unpack(key),
357 &alloy_rlp::encode(account.into_trie_account(EMPTY_ROOT_HASH)),
358 );
359 }
360 }
361 }
362 hash_builder.root();
363
364 let mut trie_updates = TrieUpdates::default();
365 trie_updates.finalize(hash_builder, Default::default(), Default::default());
366
367 trie_updates.account_nodes
368 }
369
370 #[test]
371 fn test_trie_node_iter() {
372 fn empty_leaf_rlp_for_key(key: Nibbles) -> RlpNode {
373 RlpNode::from_rlp(&alloy_rlp::encode(LeafNode::new(
374 key,
375 alloy_rlp::encode(TrieAccount::default()),
376 )))
377 }
378
379 reth_tracing::init_test_tracing();
380
381 let account_1 = b256!("0x0000000000000000000000000000000000000000000000000000000000000000");
393 let account_2 = b256!("0x0000000000000000000000000000000000000000000000000000000000000010");
394 let account_3 = b256!("0x0000000000000000000000000000000000000000000000000000000000000100");
395 let account_4 = b256!("0x0000000000000000000000000000000000000000000000000000000000000101");
396 let account_5 = b256!("0x0000000000000000000000000000000000000000000000000000000000000110");
397 let empty_account = Account::default();
398
399 let hash_builder_branch_nodes = get_hash_builder_branch_nodes(vec![
400 (Nibbles::unpack(account_1), empty_account),
401 (Nibbles::unpack(account_2), empty_account),
402 (Nibbles::unpack(account_3), empty_account),
403 (Nibbles::unpack(account_4), empty_account),
404 (Nibbles::unpack(account_5), empty_account),
405 ]);
406
407 let branch_node_1_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
408 vec![
409 empty_leaf_rlp_for_key(Nibbles::from_nibbles([0])),
410 empty_leaf_rlp_for_key(Nibbles::from_nibbles([0])),
411 ],
412 TrieMask::new(0b11),
413 )));
414
415 let branch_node_3_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
416 vec![
417 empty_leaf_rlp_for_key(Nibbles::default()),
418 empty_leaf_rlp_for_key(Nibbles::default()),
419 ],
420 TrieMask::new(0b11),
421 )));
422
423 let branch_node_2 = (
424 Nibbles::from_nibbles([vec![0; 61], vec![1]].concat()),
425 BranchNodeCompact::new(
426 TrieMask::new(0b11),
427 TrieMask::new(0b00),
428 TrieMask::new(0b01),
429 vec![branch_node_3_rlp.as_hash().unwrap()],
430 None,
431 ),
432 );
433 let branch_node_2_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
434 vec![branch_node_3_rlp, empty_leaf_rlp_for_key(Nibbles::from_nibbles([0]))],
435 TrieMask::new(0b11),
436 )));
437 let branch_node_0 = (
438 Nibbles::from_nibbles([0; 61]),
439 BranchNodeCompact::new(
440 TrieMask::new(0b11),
441 TrieMask::new(0b10),
442 TrieMask::new(0b11),
443 vec![branch_node_1_rlp.as_hash().unwrap(), branch_node_2_rlp.as_hash().unwrap()],
444 None,
445 ),
446 );
447
448 let mock_trie_nodes = vec![branch_node_0.clone(), branch_node_2.clone()];
449 pretty_assertions::assert_eq!(
450 hash_builder_branch_nodes.into_iter().sorted().collect::<Vec<_>>(),
451 mock_trie_nodes,
452 );
453
454 let trie_cursor_factory =
455 MockTrieCursorFactory::new(mock_trie_nodes.into_iter().collect(), B256Map::default());
456
457 let mut prefix_set = PrefixSetMut::default();
459 prefix_set.insert(Nibbles::unpack(account_3));
460 let prefix_set = prefix_set.freeze();
461
462 let walker =
463 TrieWalker::new(trie_cursor_factory.account_trie_cursor().unwrap(), prefix_set);
464
465 let hashed_cursor_factory = MockHashedCursorFactory::new(
466 BTreeMap::from([
467 (account_1, empty_account),
468 (account_2, empty_account),
469 (account_3, empty_account),
470 (account_4, empty_account),
471 (account_5, empty_account),
472 ]),
473 B256Map::default(),
474 );
475
476 let mut iter = TrieNodeIter::state_trie(
477 walker,
478 hashed_cursor_factory.hashed_account_cursor().unwrap(),
479 );
480
481 while iter.try_next().unwrap().is_some() {}
483
484 pretty_assertions::assert_eq!(
485 *trie_cursor_factory.visited_account_keys(),
486 vec![
487 KeyVisit {
488 visit_type: KeyVisitType::SeekExact(Nibbles::default()),
489 visited_key: None
490 },
491 KeyVisit {
492 visit_type: KeyVisitType::SeekNonExact(Nibbles::from_nibbles([0x0])),
493 visited_key: Some(branch_node_0.0)
494 },
495 KeyVisit {
496 visit_type: KeyVisitType::SeekNonExact(branch_node_2.0.clone()),
497 visited_key: Some(branch_node_2.0)
498 },
499 KeyVisit {
500 visit_type: KeyVisitType::SeekNonExact(Nibbles::from_nibbles([0x1])),
501 visited_key: None
502 }
503 ]
504 );
505 pretty_assertions::assert_eq!(
506 *hashed_cursor_factory.visited_account_keys(),
507 vec![
508 KeyVisit {
510 visit_type: KeyVisitType::SeekNonExact(account_1),
511 visited_key: Some(account_1)
512 },
513 KeyVisit {
515 visit_type: KeyVisitType::SeekNonExact(account_3),
516 visited_key: Some(account_3)
517 },
518 KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_4) },
520 KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_5) },
521 KeyVisit {
524 visit_type: KeyVisitType::SeekNonExact(account_5),
525 visited_key: Some(account_5)
526 },
527 KeyVisit { visit_type: KeyVisitType::Next, visited_key: None },
528 ],
529 );
530 }
531}