reth_trie/
node_iter.rs

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/// Represents a branch node in the trie.
7#[derive(Debug)]
8pub struct TrieBranchNode {
9    /// The key associated with the node.
10    pub key: Nibbles,
11    /// The value associated with the node.
12    pub value: B256,
13    /// Indicates whether children are in the trie.
14    pub children_are_in_trie: bool,
15}
16
17impl TrieBranchNode {
18    /// Creates a new `TrieBranchNode`.
19    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/// Represents variants of trie nodes returned by the iteration.
25#[derive(Debug)]
26pub enum TrieElement<Value> {
27    /// Branch node.
28    Branch(TrieBranchNode),
29    /// Leaf node.
30    Leaf(B256, Value),
31}
32
33/// Result of calling [`HashedCursor::seek`].
34#[derive(Debug)]
35struct SeekedHashedEntry<V> {
36    /// The key that was seeked.
37    seeked_key: B256,
38    /// The result of the seek.
39
40    /// If no entry was found for the provided key, this will be [`None`].
41    result: Option<(B256, V)>,
42}
43
44/// An iterator over existing intermediate branch nodes and updated leaf nodes.
45#[derive(Debug)]
46pub struct TrieNodeIter<C, H: HashedCursor> {
47    /// The walker over intermediate nodes.
48    pub walker: TrieWalker<C>,
49    /// The cursor for the hashed entries.
50    pub hashed_cursor: H,
51    /// The previous hashed key. If the iteration was previously interrupted, this value can be
52    /// used to resume iterating from the last returned leaf node.
53    previous_hashed_key: Option<B256>,
54
55    /// Current hashed  entry.
56    current_hashed_entry: Option<(B256, H::Value)>,
57    /// Flag indicating whether we should check the current walker key.
58    should_check_walker_key: bool,
59
60    /// The last seeked hashed entry.
61    ///
62    /// We use it to not seek the same hashed entry twice, and instead reuse it.
63    last_seeked_hashed_entry: Option<SeekedHashedEntry<H::Value>>,
64
65    #[cfg(feature = "metrics")]
66    metrics: crate::metrics::TrieNodeIterMetrics,
67    /// The key that the [`HashedCursor`] previously advanced to using [`HashedCursor::next`].
68    #[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    /// Creates a new [`TrieNodeIter`] for the state trie.
77    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    /// Creates a new [`TrieNodeIter`] for the storage trie.
87    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    /// Creates a new [`TrieNodeIter`].
97    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    /// Sets the last iterated hashed key and returns the modified [`TrieNodeIter`].
117    /// This is used to resume iteration from the last checkpoint.
118    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    /// Seeks the hashed cursor to the given key.
124    ///
125    /// If the key is the same as the last seeked key, the result of the last seek is returned.
126    ///
127    /// If `metrics` feature is enabled, also updates the metrics.
128    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    /// Advances the hashed cursor to the next entry.
155    ///
156    /// If `metrics` feature is enabled, also updates the metrics.
157    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    /// Return the next trie node to be added to the hash builder.
177    ///
178    /// Returns the nodes using this algorithm:
179    /// 1. Return the current intermediate branch node if it hasn't been updated.
180    /// 2. Advance the trie walker to the next intermediate branch node and retrieve next
181    ///    unprocessed key.
182    /// 3. Reposition the hashed cursor on the next unprocessed key.
183    /// 4. Return every hashed entry up to the key of the current intermediate branch node.
184    /// 5. Repeat.
185    ///
186    /// NOTE: The iteration will start from the key of the previous hashed entry if it was supplied.
187    pub fn try_next(
188        &mut self,
189    ) -> Result<Option<TrieElement<<H as HashedCursor>::Value>>, DatabaseError> {
190        loop {
191            // If the walker has a key...
192            if let Some(key) = self.walker.key() {
193                // Ensure that the current walker key shouldn't be checked and there's no previous
194                // hashed key
195                if !self.should_check_walker_key && self.previous_hashed_key.is_none() {
196                    // Make sure we check the next walker key, because we only know we can skip the
197                    // current one.
198                    self.should_check_walker_key = true;
199                    // If it's possible to skip the current node in the walker, return a branch node
200                    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 there's a hashed entry...
213            if let Some((hashed_key, value)) = self.current_hashed_entry.take() {
214                // Check if the walker's key is less than the key of the current hashed entry
215                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                // Set the next hashed entry as a leaf node and return
221                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            // Handle seeking and advancing based on the previous hashed key
230            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                    // Seek to the previous hashed key and get the next hashed entry
234                    self.seek_hashed_entry(hashed_key)?;
235                    self.current_hashed_entry = self.next_hashed_entry()?;
236                }
237                None => {
238                    // Get the seek key and set the current hashed entry based on walker's next
239                    // unprocessed key
240                    let (seek_key, seek_prefix) = match self.walker.next_unprocessed_key() {
241                        Some(key) => key,
242                        None => break, // no more keys
243                    };
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                    // We should get the iterator to return a branch node if we can skip the
261                    // current node and the tree flag for the current node is set.
262                    //
263                    // `can_skip_node` is already set when the hash flag is set, so we don't need
264                    // to check for the hash flag explicitly.
265                    //
266                    // It is possible that the branch node at the key `seek_key` is not stored in
267                    // the database, so the walker will advance to the branch node after it. Because
268                    // of this, we need to check that the current walker key has a prefix of the key
269                    // that we seeked to.
270                    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    /// Calculate the branch node stored in the database by feeding the provided state to the hash
325    /// builder and taking the trie updates.
326    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        // Extension (Key = 0x0000000000000000000000000000000000000000000000000000000000000)
382        // └── Branch (`branch_node_0`)
383        //     ├── 0 -> Branch (`branch_node_1`)
384        //     │      ├── 0 -> Leaf (`account_1`, Key = 0x0)
385        //     │      └── 1 -> Leaf (`account_2`, Key = 0x0)
386        //     ├── 1 -> Branch (`branch_node_2`)
387        //     │      ├── 0 -> Branch (`branch_node_3`)
388        //     │      │      ├── 0 -> Leaf (`account_3`, marked as changed)
389        //     │      │      └── 1 -> Leaf (`account_4`)
390        //     │      └── 1 -> Leaf (`account_5`, Key = 0x0)
391
392        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        // Mark the account 3 as changed.
458        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        // Walk the iterator until it's exhausted.
482        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                // Why do we always seek this key first?
509                KeyVisit {
510                    visit_type: KeyVisitType::SeekNonExact(account_1),
511                    visited_key: Some(account_1)
512                },
513                // Seek to the modified account.
514                KeyVisit {
515                    visit_type: KeyVisitType::SeekNonExact(account_3),
516                    visited_key: Some(account_3)
517                },
518                // Collect the siblings of the modified account
519                KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_4) },
520                KeyVisit { visit_type: KeyVisitType::Next, visited_key: Some(account_5) },
521                // We seek the account 5 because its hash is not in the branch node, but we already
522                // walked it before, so there should be no need for it.
523                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}