reth_trie_common/
prefix_set.rs

1use crate::Nibbles;
2use alloc::{sync::Arc, vec::Vec};
3use alloy_primitives::map::{B256Map, B256Set};
4
5/// Collection of mutable prefix sets.
6#[derive(Clone, Default, Debug)]
7pub struct TriePrefixSetsMut {
8    /// A set of account prefixes that have changed.
9    pub account_prefix_set: PrefixSetMut,
10    /// A map containing storage changes with the hashed address as key and a set of storage key
11    /// prefixes as the value.
12    pub storage_prefix_sets: B256Map<PrefixSetMut>,
13    /// A set of hashed addresses of destroyed accounts.
14    pub destroyed_accounts: B256Set,
15}
16
17impl TriePrefixSetsMut {
18    /// Returns `true` if all prefix sets are empty.
19    pub fn is_empty(&self) -> bool {
20        self.account_prefix_set.is_empty() &&
21            self.storage_prefix_sets.is_empty() &&
22            self.destroyed_accounts.is_empty()
23    }
24
25    /// Extends prefix sets with contents of another prefix set.
26    pub fn extend(&mut self, other: Self) {
27        self.account_prefix_set.extend(other.account_prefix_set);
28        for (hashed_address, prefix_set) in other.storage_prefix_sets {
29            self.storage_prefix_sets.entry(hashed_address).or_default().extend(prefix_set);
30        }
31        self.destroyed_accounts.extend(other.destroyed_accounts);
32    }
33
34    /// Returns a `TriePrefixSets` with the same elements as these sets.
35    ///
36    /// If not yet sorted, the elements will be sorted and deduplicated.
37    pub fn freeze(self) -> TriePrefixSets {
38        TriePrefixSets {
39            account_prefix_set: self.account_prefix_set.freeze(),
40            storage_prefix_sets: self
41                .storage_prefix_sets
42                .into_iter()
43                .map(|(hashed_address, prefix_set)| (hashed_address, prefix_set.freeze()))
44                .collect(),
45            destroyed_accounts: self.destroyed_accounts,
46        }
47    }
48
49    /// Clears the prefix sets and destroyed accounts map.
50    pub fn clear(&mut self) {
51        self.destroyed_accounts.clear();
52        self.storage_prefix_sets.clear();
53        self.account_prefix_set.clear();
54    }
55}
56
57/// Collection of trie prefix sets.
58#[derive(Default, Debug, Clone)]
59pub struct TriePrefixSets {
60    /// A set of account prefixes that have changed.
61    pub account_prefix_set: PrefixSet,
62    /// A map containing storage changes with the hashed address as key and a set of storage key
63    /// prefixes as the value.
64    pub storage_prefix_sets: B256Map<PrefixSet>,
65    /// A set of hashed addresses of destroyed accounts.
66    pub destroyed_accounts: B256Set,
67}
68
69/// A container for efficiently storing and checking for the presence of key prefixes.
70///
71/// This data structure stores a set of `Nibbles` and provides methods to insert
72/// new elements and check whether any existing element has a given prefix.
73///
74/// Internally, this implementation stores keys in an unsorted `Vec<Nibbles>` together with an
75/// `all` flag. The `all` flag indicates that every entry should be considered changed and that
76/// individual keys can be ignored.
77///
78/// Sorting and deduplication do not happen during insertion or membership checks on this mutable
79/// structure. Instead, keys are sorted and deduplicated when converting into the immutable
80/// `PrefixSet` via `freeze()`. The immutable `PrefixSet` provides `contains` and relies on the
81/// sorted and unique keys produced by `freeze()`; it does not perform additional sorting or
82/// deduplication.
83///
84/// This guarantees that a `PrefixSet` constructed from a `PrefixSetMut` is always sorted and
85/// deduplicated.
86/// # Examples
87///
88/// ```
89/// use reth_trie_common::{prefix_set::PrefixSetMut, Nibbles};
90///
91/// let mut prefix_set_mut = PrefixSetMut::default();
92/// prefix_set_mut.insert(Nibbles::from_nibbles_unchecked(&[0xa, 0xb]));
93/// prefix_set_mut.insert(Nibbles::from_nibbles_unchecked(&[0xa, 0xb, 0xc]));
94/// let mut prefix_set = prefix_set_mut.freeze();
95/// assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([0xa, 0xb])));
96/// assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([0xa, 0xb, 0xc])));
97/// ```
98#[derive(PartialEq, Eq, Clone, Default, Debug)]
99pub struct PrefixSetMut {
100    /// Flag indicating that any entry should be considered changed.
101    /// If set, the keys will be discarded.
102    all: bool,
103    keys: Vec<Nibbles>,
104}
105
106impl<I> From<I> for PrefixSetMut
107where
108    I: IntoIterator<Item = Nibbles>,
109{
110    fn from(value: I) -> Self {
111        Self { all: false, keys: value.into_iter().collect() }
112    }
113}
114
115impl PrefixSetMut {
116    /// Create [`PrefixSetMut`] with pre-allocated capacity.
117    pub fn with_capacity(capacity: usize) -> Self {
118        Self { all: false, keys: Vec::with_capacity(capacity) }
119    }
120
121    /// Create [`PrefixSetMut`] that considers all key changed.
122    pub const fn all() -> Self {
123        Self { all: true, keys: Vec::new() }
124    }
125
126    /// Inserts the given `nibbles` into the set.
127    pub fn insert(&mut self, nibbles: Nibbles) {
128        self.keys.push(nibbles);
129    }
130
131    /// Extend prefix set with contents of another prefix set.
132    pub fn extend(&mut self, other: Self) {
133        self.all |= other.all;
134        self.keys.extend(other.keys);
135    }
136
137    /// Extend prefix set keys with contents of provided iterator.
138    pub fn extend_keys<I>(&mut self, keys: I)
139    where
140        I: IntoIterator<Item = Nibbles>,
141    {
142        self.keys.extend(keys);
143    }
144
145    /// Returns the number of elements in the set.
146    pub const fn len(&self) -> usize {
147        self.keys.len()
148    }
149
150    /// Returns `true` if the set is empty.
151    pub const fn is_empty(&self) -> bool {
152        self.keys.is_empty()
153    }
154
155    /// Clears the inner vec for reuse, setting `all` to `false`.
156    pub fn clear(&mut self) {
157        self.all = false;
158        self.keys.clear();
159    }
160
161    /// Returns a `PrefixSet` with the same elements as this set.
162    ///
163    /// If not yet sorted, the elements will be sorted and deduplicated.
164    pub fn freeze(mut self) -> PrefixSet {
165        if self.all {
166            PrefixSet { index: 0, all: true, keys: Arc::new(Vec::new()) }
167        } else {
168            self.keys.sort_unstable();
169            self.keys.dedup();
170            // Shrink after deduplication to release unused capacity.
171            self.keys.shrink_to_fit();
172            PrefixSet { index: 0, all: false, keys: Arc::new(self.keys) }
173        }
174    }
175}
176
177/// A sorted prefix set that has an immutable _sorted_ list of unique keys.
178///
179/// See also [`PrefixSetMut::freeze`].
180#[derive(Debug, Default, Clone)]
181pub struct PrefixSet {
182    /// Flag indicating that any entry should be considered changed.
183    all: bool,
184    index: usize,
185    keys: Arc<Vec<Nibbles>>,
186}
187
188impl PrefixSet {
189    /// Returns `true` if any of the keys in the set has the given prefix
190    ///
191    /// # Note on Mutability
192    ///
193    /// This method requires `&mut self` (unlike typical `contains` methods) because it maintains an
194    /// internal position tracker (`self.index`) between calls. This enables significant performance
195    /// optimization for sequential lookups in sorted order, which is common during trie traversal.
196    ///
197    /// The `index` field allows subsequent searches to start where previous ones left off,
198    /// avoiding repeated full scans of the prefix array when keys are accessed in nearby ranges.
199    ///
200    /// This optimization was inspired by Silkworm's implementation and significantly improves
201    /// incremental state root calculation performance
202    /// ([see PR #2417](https://github.com/paradigmxyz/reth/pull/2417)).
203    #[inline]
204    pub fn contains(&mut self, prefix: &Nibbles) -> bool {
205        if self.all {
206            return true
207        }
208
209        while self.index > 0 && &self.keys[self.index] > prefix {
210            self.index -= 1;
211        }
212
213        for (idx, key) in self.keys[self.index..].iter().enumerate() {
214            if key.starts_with(prefix) {
215                self.index += idx;
216                return true
217            }
218
219            if key > prefix {
220                self.index += idx;
221                return false
222            }
223        }
224
225        false
226    }
227
228    /// Returns an iterator over reference to _all_ nibbles regardless of cursor position.
229    pub fn iter(&self) -> core::slice::Iter<'_, Nibbles> {
230        self.keys.iter()
231    }
232
233    /// Returns true if every entry should be considered changed.
234    pub const fn all(&self) -> bool {
235        self.all
236    }
237
238    /// Returns the number of elements in the set.
239    pub fn len(&self) -> usize {
240        self.keys.len()
241    }
242
243    /// Returns `true` if the set is empty.
244    pub fn is_empty(&self) -> bool {
245        self.keys.is_empty()
246    }
247}
248
249impl<'a> IntoIterator for &'a PrefixSet {
250    type Item = &'a Nibbles;
251    type IntoIter = core::slice::Iter<'a, Nibbles>;
252    fn into_iter(self) -> Self::IntoIter {
253        self.iter()
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn test_contains_with_multiple_inserts_and_duplicates() {
263        let mut prefix_set_mut = PrefixSetMut::default();
264        prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 3]));
265        prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 4]));
266        prefix_set_mut.insert(Nibbles::from_nibbles([4, 5, 6]));
267        prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 3])); // Duplicate
268
269        let mut prefix_set = prefix_set_mut.freeze();
270        assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([1, 2])));
271        assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([4, 5])));
272        assert!(!prefix_set.contains(&Nibbles::from_nibbles_unchecked([7, 8])));
273        assert_eq!(prefix_set.len(), 3); // Length should be 3 (excluding duplicate)
274    }
275
276    #[test]
277    fn test_freeze_shrinks_capacity() {
278        let mut prefix_set_mut = PrefixSetMut::default();
279        prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 3]));
280        prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 4]));
281        prefix_set_mut.insert(Nibbles::from_nibbles([4, 5, 6]));
282        prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 3])); // Duplicate
283
284        assert_eq!(prefix_set_mut.keys.len(), 4); // Length is 4 (before deduplication)
285        assert_eq!(prefix_set_mut.keys.capacity(), 4); // Capacity is 4 (before deduplication)
286
287        let mut prefix_set = prefix_set_mut.freeze();
288        assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([1, 2])));
289        assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([4, 5])));
290        assert!(!prefix_set.contains(&Nibbles::from_nibbles_unchecked([7, 8])));
291        assert_eq!(prefix_set.keys.len(), 3); // Length should be 3 (excluding duplicate)
292        assert_eq!(prefix_set.keys.capacity(), 3); // Capacity should be 3 after shrinking
293    }
294
295    #[test]
296    fn test_freeze_shrinks_existing_capacity() {
297        // do the above test but with preallocated capacity
298        let mut prefix_set_mut = PrefixSetMut::with_capacity(101);
299        prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 3]));
300        prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 4]));
301        prefix_set_mut.insert(Nibbles::from_nibbles([4, 5, 6]));
302        prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 3])); // Duplicate
303
304        assert_eq!(prefix_set_mut.keys.len(), 4); // Length is 4 (before deduplication)
305        assert_eq!(prefix_set_mut.keys.capacity(), 101); // Capacity is 101 (before deduplication)
306
307        let mut prefix_set = prefix_set_mut.freeze();
308        assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([1, 2])));
309        assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([4, 5])));
310        assert!(!prefix_set.contains(&Nibbles::from_nibbles_unchecked([7, 8])));
311        assert_eq!(prefix_set.keys.len(), 3); // Length should be 3 (excluding duplicate)
312        assert_eq!(prefix_set.keys.capacity(), 3); // Capacity should be 3 after shrinking
313    }
314
315    #[test]
316    fn test_prefix_set_all_extend() {
317        let mut prefix_set_mut = PrefixSetMut::default();
318        prefix_set_mut.extend(PrefixSetMut::all());
319        assert!(prefix_set_mut.all);
320    }
321}