reth_provider/changesets_utils/
trie.rs

1use itertools::{merge_join_by, EitherOrBoth};
2use reth_db_api::DatabaseError;
3use reth_trie::{trie_cursor::TrieCursor, BranchNodeCompact, Nibbles};
4use std::cmp::{Ord, Ordering};
5
6/// Combines a sorted iterator of trie node paths and a storage trie cursor into a new
7/// iterator which produces the current values of all given paths in the same order.
8#[derive(Debug)]
9pub struct StorageTrieCurrentValuesIter<'cursor, P, C> {
10    /// Sorted iterator of node paths which we want the values of.
11    paths: P,
12    /// Storage trie cursor.
13    cursor: &'cursor mut C,
14    /// Current value at the cursor, allows us to treat the cursor as a peekable iterator.
15    cursor_current: Option<(Nibbles, BranchNodeCompact)>,
16}
17
18impl<'cursor, P, C> StorageTrieCurrentValuesIter<'cursor, P, C>
19where
20    P: Iterator<Item = Nibbles>,
21    C: TrieCursor,
22{
23    /// Instantiate a [`StorageTrieCurrentValuesIter`] from a sorted paths iterator and a cursor.
24    pub fn new(paths: P, cursor: &'cursor mut C) -> Result<Self, DatabaseError> {
25        let mut new_self = Self { paths, cursor, cursor_current: None };
26        new_self.seek_cursor(Nibbles::default())?;
27        Ok(new_self)
28    }
29
30    fn seek_cursor(&mut self, path: Nibbles) -> Result<(), DatabaseError> {
31        self.cursor_current = self.cursor.seek(path)?;
32        Ok(())
33    }
34}
35
36impl<'cursor, P, C> Iterator for StorageTrieCurrentValuesIter<'cursor, P, C>
37where
38    P: Iterator<Item = Nibbles>,
39    C: TrieCursor,
40{
41    type Item = Result<(Nibbles, Option<BranchNodeCompact>), DatabaseError>;
42
43    fn next(&mut self) -> Option<Self::Item> {
44        let Some(curr_path) = self.paths.next() else {
45            // If there are no more paths then there is no further possible output.
46            return None
47        };
48
49        // If the path is ahead of the cursor then seek the cursor forward to catch up. The cursor
50        // will seek either to `curr_path` or beyond it.
51        if self.cursor_current.as_ref().is_some_and(|(cursor_path, _)| curr_path > *cursor_path) &&
52            let Err(err) = self.seek_cursor(curr_path)
53        {
54            return Some(Err(err))
55        }
56
57        // If there is a path but the cursor is empty then that path has no node.
58        if self.cursor_current.is_none() {
59            return Some(Ok((curr_path, None)))
60        }
61
62        let (cursor_path, cursor_node) =
63            self.cursor_current.as_mut().expect("already checked for None");
64
65        // There is both a path and a cursor value, compare their paths.
66        match curr_path.cmp(cursor_path) {
67            Ordering::Less => {
68                // If the path is behind the cursor then there is no value for that
69                // path, produce None.
70                Some(Ok((curr_path, None)))
71            }
72            Ordering::Equal => {
73                // If the target path and cursor's path match then there is a value for that path,
74                // return the value. We don't seek the cursor here, that will be handled on the
75                // next call to `next` after checking that `paths` isn't None.
76                let cursor_node = core::mem::take(cursor_node);
77                Some(Ok((*cursor_path, Some(cursor_node))))
78            }
79            Ordering::Greater => {
80                panic!("cursor was seeked to {curr_path:?}, but produced a node at a lower path {cursor_path:?}")
81            }
82        }
83    }
84}
85
86/// Returns an iterator which produces the values to be inserted into the `StoragesTrieChangeSets`
87/// table for an account whose storage was wiped during a block. It is expected that this is called
88/// prior to inserting the block's trie updates.
89///
90/// ## Arguments
91///
92/// - `curr_values_of_changed` is an iterator over the current values of all trie nodes modified by
93///   the block, ordered by path.
94/// - `all_nodes` is an iterator over all existing trie nodes for the account, ordered by path.
95///
96/// ## Returns
97///
98/// An iterator of trie node paths and a `Some(node)` (indicating the node was wiped) or a `None`
99/// (indicating the node was modified in the block but didn't previously exist. The iterator's
100/// results will be ordered by path.
101pub fn storage_trie_wiped_changeset_iter(
102    curr_values_of_changed: impl Iterator<
103        Item = Result<(Nibbles, Option<BranchNodeCompact>), DatabaseError>,
104    >,
105    all_nodes: impl Iterator<Item = Result<(Nibbles, BranchNodeCompact), DatabaseError>>,
106) -> Result<
107    impl Iterator<Item = Result<(Nibbles, Option<BranchNodeCompact>), DatabaseError>>,
108    DatabaseError,
109> {
110    let all_nodes = all_nodes.map(|e| e.map(|(nibbles, node)| (nibbles, Some(node))));
111
112    let merged = merge_join_by(curr_values_of_changed, all_nodes, |a, b| match (a, b) {
113        (Err(_), _) => Ordering::Less,
114        (_, Err(_)) => Ordering::Greater,
115        (Ok(a), Ok(b)) => a.0.cmp(&b.0),
116    });
117
118    Ok(merged.map(|either_or| match either_or {
119        EitherOrBoth::Left(changed) => {
120            // A path of a changed node (given in `paths`) which was not found in the database (or
121            // there's an error). The current value of this path must be None, otherwise it would
122            // have also been returned by the `all_nodes` iter.
123            debug_assert!(
124                changed.as_ref().is_err() || changed.as_ref().is_ok_and(|(_, node)| node.is_none()),
125                "changed node is Some but wasn't returned by `all_nodes` iterator: {changed:?}",
126            );
127            changed
128        }
129        EitherOrBoth::Right(wiped) => {
130            // A node was found in the db (indicating it was wiped) but was not given in `paths`.
131            // Return it as-is.
132            wiped
133        }
134        EitherOrBoth::Both(changed, _wiped) => {
135            // A path of a changed node (given in `paths`) was found with a previous value in the
136            // database. The changed node must have a value which is equal to the one found by the
137            // `all_nodes` iterator. If the changed node had no previous value (None) it wouldn't
138            // be returned by `all_nodes` and so would be in the Left branch.
139            //
140            // Due to the ordering closure passed to `merge_join_by` it's not possible for either
141            // value to be an error here.
142            debug_assert!(changed.is_ok(), "unreachable error condition: {changed:?}");
143            debug_assert_eq!(changed, _wiped);
144            changed
145        }
146    }))
147}