reth_trie/
node_iter.rs

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/// Represents a branch node in the trie.
9#[derive(Debug)]
10pub struct TrieBranchNode {
11    /// The key associated with the node.
12    pub key: Nibbles,
13    /// The value associated with the node.
14    pub value: B256,
15    /// Indicates whether children are in the trie.
16    pub children_are_in_trie: bool,
17}
18
19impl TrieBranchNode {
20    /// Creates a new `TrieBranchNode`.
21    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/// Represents variants of trie nodes returned by the iteration.
27#[derive(Debug)]
28pub enum TrieElement<Value> {
29    /// Branch node.
30    Branch(TrieBranchNode),
31    /// Leaf node.
32    Leaf(B256, Value),
33}
34
35/// Result of calling [`HashedCursor::seek`].
36#[derive(Debug)]
37struct SeekedHashedEntry<V> {
38    /// The key that was seeked.
39    seeked_key: B256,
40    /// The result of the seek.
41
42    /// If no entry was found for the provided key, this will be [`None`].
43    result: Option<(B256, V)>,
44}
45
46/// An iterator over existing intermediate branch nodes and updated leaf nodes.
47#[derive(Debug)]
48pub struct TrieNodeIter<C, H: HashedCursor> {
49    /// The walker over intermediate nodes.
50    pub walker: TrieWalker<C>,
51    /// The cursor for the hashed entries.
52    pub hashed_cursor: H,
53    /// The type of the trie.
54    trie_type: TrieType,
55    /// The previous hashed key. If the iteration was previously interrupted, this value can be
56    /// used to resume iterating from the last returned leaf node.
57    previous_hashed_key: Option<B256>,
58
59    /// Current hashed  entry.
60    current_hashed_entry: Option<(B256, H::Value)>,
61    /// Flag indicating whether we should check the current walker key.
62    should_check_walker_key: bool,
63
64    /// The last seeked hashed entry.
65    ///
66    /// We use it to not seek the same hashed entry twice, and instead reuse it.
67    last_seeked_hashed_entry: Option<SeekedHashedEntry<H::Value>>,
68
69    #[cfg(feature = "metrics")]
70    metrics: crate::metrics::TrieNodeIterMetrics,
71    /// Stores the result of the last successful [`Self::next_hashed_entry`], used to avoid a
72    /// redundant [`Self::seek_hashed_entry`] call if the walker points to the same key that
73    /// was just returned by `next()`.
74    last_next_result: Option<(B256, H::Value)>,
75}
76
77impl<C, H: HashedCursor> TrieNodeIter<C, H>
78where
79    H::Value: Copy,
80{
81    /// Creates a new [`TrieNodeIter`] for the state trie.
82    pub fn state_trie(walker: TrieWalker<C>, hashed_cursor: H) -> Self {
83        Self::new(walker, hashed_cursor, TrieType::State)
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(walker, hashed_cursor, TrieType::Storage)
89    }
90
91    /// Creates a new [`TrieNodeIter`].
92    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    /// Sets the last iterated hashed key and returns the modified [`TrieNodeIter`].
108    /// This is used to resume iteration from the last checkpoint.
109    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    /// Seeks the hashed cursor to the given key.
115    ///
116    /// If the key is the same as the last seeked key, the result of the last seek is returned.
117    ///
118    /// If `metrics` feature is enabled, also updates the metrics.
119    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; // Consume the cached value
124
125                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    /// 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
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    /// 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    #[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 the walker has a key...
199            if let Some(key) = self.walker.key() {
200                // Ensure that the current walker key shouldn't be checked and there's no previous
201                // hashed key
202                if !self.should_check_walker_key && self.previous_hashed_key.is_none() {
203                    // Make sure we check the next walker key, because we only know we can skip the
204                    // current one.
205                    self.should_check_walker_key = true;
206                    // If it's possible to skip the current node in the walker, return a branch node
207                    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 there's a hashed entry...
220            if let Some((hashed_key, value)) = self.current_hashed_entry.take() {
221                // Check if the walker's key is less than the key of the current hashed entry
222                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                // Set the next hashed entry as a leaf node and return
228                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            // Handle seeking and advancing based on the previous hashed key
237            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                    // Seek to the previous hashed key and get the next hashed entry
241                    self.seek_hashed_entry(hashed_key)?;
242                    self.current_hashed_entry = self.next_hashed_entry()?;
243                }
244                None => {
245                    // Get the seek key and set the current hashed entry based on walker's next
246                    // unprocessed key
247                    let (seek_key, seek_prefix) = match self.walker.next_unprocessed_key() {
248                        Some(key) => key,
249                        None => break, // no more keys
250                    };
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                    // We should get the iterator to return a branch node if we can skip the
268                    // current node and the tree flag for the current node is set.
269                    //
270                    // `can_skip_node` is already set when the hash flag is set, so we don't need
271                    // to check for the hash flag explicitly.
272                    //
273                    // It is possible that the branch node at the key `seek_key` is not stored in
274                    // the database, so the walker will advance to the branch node after it. Because
275                    // of this, we need to check that the current walker key has a prefix of the key
276                    // that we seeked to.
277                    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    /// Calculate the branch node stored in the database by feeding the provided state to the hash
332    /// builder and taking the trie updates.
333    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, // account nodes are always public
366                    );
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, // account nodes are always public
385            )))
386        }
387
388        reth_tracing::init_test_tracing();
389
390        // Extension (Key = 0x0000000000000000000000000000000000000000000000000000000000000)
391        // └── Branch (`branch_node_0`)
392        //     ├── 0 -> Branch (`branch_node_1`)
393        //     │      ├── 0 -> Leaf (`account_1`, Key = 0x0)
394        //     │      └── 1 -> Leaf (`account_2`, Key = 0x0)
395        //     ├── 1 -> Branch (`branch_node_2`)
396        //     │      ├── 0 -> Branch (`branch_node_3`)
397        //     │      │      ├── 0 -> Leaf (`account_3`, marked as changed)
398        //     │      │      └── 1 -> Leaf (`account_4`)
399        //     │      └── 1 -> Leaf (`account_5`, Key = 0x0)
400
401        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        // Mark the account 3 as changed.
467        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        // Walk the iterator until it's exhausted.
491        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                // Why do we always seek this key first?
518                KeyVisit {
519                    visit_type: KeyVisitType::SeekNonExact(account_1),
520                    visited_key: Some(account_1)
521                },
522                // Seek to the modified account.
523                KeyVisit {
524                    visit_type: KeyVisitType::SeekNonExact(account_3),
525                    visited_key: Some(account_3)
526                },
527                // Collect the siblings of the modified account
528                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}