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}