Skip to main content

reth_trie_common/
prefix_set.rs

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