1use crate::{
2 hashed_cursor::HashedCursor, trie_cursor::TrieCursor, walker::TrieWalker, Nibbles, TrieType,
3};
4use alloy_primitives::B256;
5use reth_storage_errors::db::DatabaseError;
6use tracing::{instrument, trace};
7
8#[derive(Debug)]
10pub struct TrieBranchNode {
11 pub key: Nibbles,
13 pub value: B256,
15 pub children_are_in_trie: bool,
17}
18
19impl TrieBranchNode {
20 pub const fn new(key: Nibbles, value: B256, children_are_in_trie: bool) -> Self {
22 Self { key, value, children_are_in_trie }
23 }
24}
25
26#[derive(Debug)]
28pub enum TrieElement<Value> {
29 Branch(TrieBranchNode),
31 Leaf(B256, Value),
33}
34
35#[derive(Debug)]
37struct SeekedHashedEntry<V> {
38 seeked_key: B256,
40 result: Option<(B256, V)>,
44}
45
46#[derive(Debug)]
48pub struct TrieNodeIter<C, H: HashedCursor> {
49 pub walker: TrieWalker<C>,
51 pub hashed_cursor: H,
53 trie_type: TrieType,
55 previous_hashed_key: Option<B256>,
58
59 current_hashed_entry: Option<(B256, H::Value)>,
61 should_check_walker_key: bool,
63
64 last_seeked_hashed_entry: Option<SeekedHashedEntry<H::Value>>,
68
69 #[cfg(feature = "metrics")]
70 metrics: crate::metrics::TrieNodeIterMetrics,
71 last_next_result: Option<(B256, H::Value)>,
75}
76
77impl<C, H: HashedCursor> TrieNodeIter<C, H>
78where
79 H::Value: Copy,
80{
81 pub fn state_trie(walker: TrieWalker<C>, hashed_cursor: H) -> Self {
83 Self::new(walker, hashed_cursor, TrieType::State)
84 }
85
86 pub fn storage_trie(walker: TrieWalker<C>, hashed_cursor: H) -> Self {
88 Self::new(walker, hashed_cursor, TrieType::Storage)
89 }
90
91 fn new(walker: TrieWalker<C>, hashed_cursor: H, trie_type: TrieType) -> Self {
93 Self {
94 walker,
95 hashed_cursor,
96 trie_type,
97 previous_hashed_key: None,
98 current_hashed_entry: None,
99 should_check_walker_key: false,
100 last_seeked_hashed_entry: None,
101 #[cfg(feature = "metrics")]
102 metrics: crate::metrics::TrieNodeIterMetrics::new(trie_type),
103 last_next_result: None,
104 }
105 }
106
107 pub const fn with_last_hashed_key(mut self, previous_hashed_key: B256) -> Self {
110 self.previous_hashed_key = Some(previous_hashed_key);
111 self
112 }
113
114 fn seek_hashed_entry(&mut self, key: B256) -> Result<Option<(B256, H::Value)>, DatabaseError> {
120 if let Some((last_key, last_value)) = self.last_next_result {
121 if last_key == key {
122 trace!(target: "trie::node_iter", seek_key = ?key, "reusing result from last next() call instead of seeking");
123 self.last_next_result = None; let result = Some((last_key, last_value));
126 self.last_seeked_hashed_entry = Some(SeekedHashedEntry { seeked_key: key, result });
127
128 return Ok(result);
129 }
130 }
131
132 if let Some(entry) = self
133 .last_seeked_hashed_entry
134 .as_ref()
135 .filter(|entry| entry.seeked_key == key)
136 .map(|entry| entry.result)
137 {
138 #[cfg(feature = "metrics")]
139 self.metrics.inc_leaf_nodes_same_seeked();
140 return Ok(entry);
141 }
142
143 trace!(target: "trie::node_iter", ?key, "performing hashed cursor seek");
144 let result = self.hashed_cursor.seek(key)?;
145 self.last_seeked_hashed_entry = Some(SeekedHashedEntry { seeked_key: key, result });
146
147 #[cfg(feature = "metrics")]
148 {
149 self.metrics.inc_leaf_nodes_seeked();
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
160 self.last_next_result = result.clone()?;
161
162 #[cfg(feature = "metrics")]
163 {
164 self.metrics.inc_leaf_nodes_advanced();
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 #[instrument(
188 level = "trace",
189 target = "trie::node_iter",
190 skip_all,
191 fields(trie_type = ?self.trie_type),
192 ret
193 )]
194 pub fn try_next(
195 &mut self,
196 ) -> Result<Option<TrieElement<<H as HashedCursor>::Value>>, DatabaseError> {
197 loop {
198 if let Some(key) = self.walker.key() {
200 if !self.should_check_walker_key && self.previous_hashed_key.is_none() {
203 self.should_check_walker_key = true;
206 if self.walker.can_skip_current_node {
208 #[cfg(feature = "metrics")]
209 self.metrics.inc_branch_nodes_returned();
210 return Ok(Some(TrieElement::Branch(TrieBranchNode::new(
211 key.clone(),
212 self.walker.hash().unwrap(),
213 self.walker.children_are_in_trie(),
214 ))));
215 }
216 }
217 }
218
219 if let Some((hashed_key, value)) = self.current_hashed_entry.take() {
221 if self.walker.key().is_some_and(|key| key < &Nibbles::unpack(hashed_key)) {
223 self.should_check_walker_key = false;
224 continue
225 }
226
227 trace!(target: "trie::node_iter", ?hashed_key, "next hashed entry");
229 self.current_hashed_entry = self.next_hashed_entry()?;
230
231 #[cfg(feature = "metrics")]
232 self.metrics.inc_leaf_nodes_returned();
233 return Ok(Some(TrieElement::Leaf(hashed_key, value)))
234 }
235
236 match self.previous_hashed_key.take() {
238 Some(hashed_key) => {
239 trace!(target: "trie::node_iter", ?hashed_key, "seeking to the previous hashed entry");
240 self.seek_hashed_entry(hashed_key)?;
242 self.current_hashed_entry = self.next_hashed_entry()?;
243 }
244 None => {
245 let (seek_key, seek_prefix) = match self.walker.next_unprocessed_key() {
248 Some(key) => key,
249 None => break, };
251
252 trace!(
253 target: "trie::node_iter",
254 ?seek_key,
255 can_skip_current_node = self.walker.can_skip_current_node,
256 last = ?self.walker.stack.last(),
257 "seeking to the next unprocessed hashed entry"
258 );
259 let can_skip_node = self.walker.can_skip_current_node;
260 self.walker.advance()?;
261 trace!(
262 target: "trie::node_iter",
263 last = ?self.walker.stack.last(),
264 "advanced walker"
265 );
266
267 if can_skip_node &&
278 self.walker.key().is_some_and(|key| key.has_prefix(&seek_prefix)) &&
279 self.walker.children_are_in_trie()
280 {
281 trace!(
282 target: "trie::node_iter",
283 ?seek_key,
284 walker_hash = ?self.walker.maybe_hash(),
285 "skipping hashed seek"
286 );
287
288 self.should_check_walker_key = false;
289 continue
290 }
291
292 self.current_hashed_entry = self.seek_hashed_entry(seek_key)?;
293 }
294 }
295 }
296
297 Ok(None)
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use crate::{
304 hashed_cursor::{
305 mock::MockHashedCursorFactory, noop::NoopHashedAccountCursor, HashedCursorFactory,
306 HashedPostStateAccountCursor,
307 },
308 mock::{KeyVisit, KeyVisitType},
309 trie_cursor::{
310 mock::MockTrieCursorFactory, noop::NoopAccountTrieCursor, TrieCursorFactory,
311 },
312 walker::TrieWalker,
313 };
314 use alloy_primitives::{
315 b256,
316 map::{B256Map, HashMap},
317 };
318 use alloy_trie::{
319 BranchNodeCompact, HashBuilder, Nibbles, TrieAccount, TrieMask, EMPTY_ROOT_HASH,
320 };
321 use itertools::Itertools;
322 use reth_primitives_traits::Account;
323 use reth_trie_common::{
324 prefix_set::PrefixSetMut, updates::TrieUpdates, BranchNode, HashedPostState, LeafNode,
325 RlpNode,
326 };
327 use std::collections::BTreeMap;
328
329 use super::{TrieElement, TrieNodeIter};
330
331 fn get_hash_builder_branch_nodes(
334 state: impl IntoIterator<Item = (Nibbles, Account)> + Clone,
335 ) -> HashMap<Nibbles, BranchNodeCompact> {
336 let mut hash_builder = HashBuilder::default().with_updates(true);
337
338 let mut prefix_set = PrefixSetMut::default();
339 prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles));
340 let walker = TrieWalker::state_trie(NoopAccountTrieCursor, prefix_set.freeze());
341
342 let hashed_post_state = HashedPostState::default()
343 .with_accounts(state.into_iter().map(|(nibbles, account)| {
344 (nibbles.pack().into_inner().unwrap().into(), Some(account))
345 }))
346 .into_sorted();
347
348 let mut node_iter = TrieNodeIter::state_trie(
349 walker,
350 HashedPostStateAccountCursor::new(
351 NoopHashedAccountCursor::default(),
352 hashed_post_state.accounts(),
353 ),
354 );
355
356 while let Some(node) = node_iter.try_next().unwrap() {
357 match node {
358 TrieElement::Branch(branch) => {
359 hash_builder.add_branch(branch.key, branch.value, branch.children_are_in_trie);
360 }
361 TrieElement::Leaf(key, account) => {
362 hash_builder.add_leaf(
363 Nibbles::unpack(key),
364 &alloy_rlp::encode(account.into_trie_account(EMPTY_ROOT_HASH)),
365 false, );
367 }
368 }
369 }
370 hash_builder.root();
371
372 let mut trie_updates = TrieUpdates::default();
373 trie_updates.finalize(hash_builder, Default::default(), Default::default());
374
375 trie_updates.account_nodes
376 }
377
378 #[test]
379 fn test_trie_node_iter() {
380 fn empty_leaf_rlp_for_key(key: Nibbles) -> RlpNode {
381 RlpNode::from_rlp(&alloy_rlp::encode(LeafNode::new(
382 key,
383 alloy_rlp::encode(TrieAccount::default()),
384 false, )))
386 }
387
388 reth_tracing::init_test_tracing();
389
390 let account_1 = b256!("0x0000000000000000000000000000000000000000000000000000000000000000");
402 let account_2 = b256!("0x0000000000000000000000000000000000000000000000000000000000000010");
403 let account_3 = b256!("0x0000000000000000000000000000000000000000000000000000000000000100");
404 let account_4 = b256!("0x0000000000000000000000000000000000000000000000000000000000000101");
405 let account_5 = b256!("0x0000000000000000000000000000000000000000000000000000000000000110");
406 let empty_account = Account::default();
407
408 let hash_builder_branch_nodes = get_hash_builder_branch_nodes(vec![
409 (Nibbles::unpack(account_1), empty_account),
410 (Nibbles::unpack(account_2), empty_account),
411 (Nibbles::unpack(account_3), empty_account),
412 (Nibbles::unpack(account_4), empty_account),
413 (Nibbles::unpack(account_5), empty_account),
414 ]);
415
416 let branch_node_1_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
417 vec![
418 empty_leaf_rlp_for_key(Nibbles::from_nibbles([0])),
419 empty_leaf_rlp_for_key(Nibbles::from_nibbles([0])),
420 ],
421 TrieMask::new(0b11),
422 )));
423
424 let branch_node_3_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
425 vec![
426 empty_leaf_rlp_for_key(Nibbles::default()),
427 empty_leaf_rlp_for_key(Nibbles::default()),
428 ],
429 TrieMask::new(0b11),
430 )));
431
432 let branch_node_2 = (
433 Nibbles::from_nibbles([vec![0; 61], vec![1]].concat()),
434 BranchNodeCompact::new(
435 TrieMask::new(0b11),
436 TrieMask::new(0b00),
437 TrieMask::new(0b01),
438 vec![branch_node_3_rlp.as_hash().unwrap()],
439 None,
440 ),
441 );
442 let branch_node_2_rlp = RlpNode::from_rlp(&alloy_rlp::encode(BranchNode::new(
443 vec![branch_node_3_rlp, empty_leaf_rlp_for_key(Nibbles::from_nibbles([0]))],
444 TrieMask::new(0b11),
445 )));
446 let branch_node_0 = (
447 Nibbles::from_nibbles([0; 61]),
448 BranchNodeCompact::new(
449 TrieMask::new(0b11),
450 TrieMask::new(0b10),
451 TrieMask::new(0b11),
452 vec![branch_node_1_rlp.as_hash().unwrap(), branch_node_2_rlp.as_hash().unwrap()],
453 None,
454 ),
455 );
456
457 let mock_trie_nodes = vec![branch_node_0.clone(), branch_node_2.clone()];
458 pretty_assertions::assert_eq!(
459 hash_builder_branch_nodes.into_iter().sorted().collect::<Vec<_>>(),
460 mock_trie_nodes,
461 );
462
463 let trie_cursor_factory =
464 MockTrieCursorFactory::new(mock_trie_nodes.into_iter().collect(), B256Map::default());
465
466 let mut prefix_set = PrefixSetMut::default();
468 prefix_set.insert(Nibbles::unpack(account_3));
469 let prefix_set = prefix_set.freeze();
470
471 let walker =
472 TrieWalker::state_trie(trie_cursor_factory.account_trie_cursor().unwrap(), prefix_set);
473
474 let hashed_cursor_factory = MockHashedCursorFactory::new(
475 BTreeMap::from([
476 (account_1, empty_account),
477 (account_2, empty_account),
478 (account_3, empty_account),
479 (account_4, empty_account),
480 (account_5, empty_account),
481 ]),
482 B256Map::default(),
483 );
484
485 let mut iter = TrieNodeIter::state_trie(
486 walker,
487 hashed_cursor_factory.hashed_account_cursor().unwrap(),
488 );
489
490 while iter.try_next().unwrap().is_some() {}
492
493 pretty_assertions::assert_eq!(
494 *trie_cursor_factory.visited_account_keys(),
495 vec![
496 KeyVisit {
497 visit_type: KeyVisitType::SeekExact(Nibbles::default()),
498 visited_key: None
499 },
500 KeyVisit {
501 visit_type: KeyVisitType::SeekNonExact(Nibbles::from_nibbles([0x0])),
502 visited_key: Some(branch_node_0.0)
503 },
504 KeyVisit {
505 visit_type: KeyVisitType::SeekNonExact(branch_node_2.0.clone()),
506 visited_key: Some(branch_node_2.0)
507 },
508 KeyVisit {
509 visit_type: KeyVisitType::SeekNonExact(Nibbles::from_nibbles([0x1])),
510 visited_key: None
511 }
512 ]
513 );
514 pretty_assertions::assert_eq!(
515 *hashed_cursor_factory.visited_account_keys(),
516 vec![
517 KeyVisit {
519 visit_type: KeyVisitType::SeekNonExact(account_1),
520 visited_key: Some(account_1)
521 },
522 KeyVisit {
524 visit_type: KeyVisitType::SeekNonExact(account_3),
525 visited_key: Some(account_3)
526 },
527 KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_4) },
529 KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_5) },
530 KeyVisit { visit_type: KeyVisitType::Next, visited_key: None },
531 ],
532 );
533 }
534}