Skip to main content

reth_trie_sparse/
state.rs

1#[cfg(feature = "trie-debug")]
2use crate::debug_recorder::TrieDebugRecorder;
3use crate::{
4    lfu::BucketedLfu, traits::SparseTrie as SparseTrieTrait, ArenaParallelSparseTrie,
5    RevealableSparseTrie,
6};
7use alloc::vec::Vec;
8use alloy_primitives::{map::B256Map, B256};
9use either::Either;
10use reth_execution_errors::{SparseStateTrieResult, SparseTrieErrorKind};
11use reth_trie_common::{
12    updates::{StorageTrieUpdates, TrieUpdates},
13    DecodedMultiProof, MultiProof, Nibbles, ProofTrieNodeV2,
14};
15#[cfg(feature = "std")]
16use tracing::debug;
17use tracing::instrument;
18
19/// Holds data that should be dropped after any locks are released.
20///
21/// This is used to defer expensive deallocations (like proof node buffers) until after final state
22/// root is calculated
23#[derive(Debug, Default)]
24pub struct DeferredDrops {
25    /// Each nodes reveal operation creates a new buffer, uses it, and pushes it here.
26    pub proof_nodes_bufs: Vec<Vec<ProofTrieNodeV2>>,
27}
28
29#[derive(Debug)]
30/// Sparse state trie representing lazy-loaded Ethereum state trie.
31pub struct SparseStateTrie<
32    A = ArenaParallelSparseTrie, // Account trie implementation
33    S = ArenaParallelSparseTrie, // Storage trie implementation
34> {
35    /// Sparse account trie.
36    state: RevealableSparseTrie<A>,
37    /// State related to storage tries.
38    storage: StorageTries<S>,
39    /// Flag indicating whether trie updates should be retained.
40    retain_updates: bool,
41    /// Holds data that should be dropped after final state root is calculated.
42    deferred_drops: DeferredDrops,
43    /// Global LFU tracker for hot `(address, slot)` storage entries.
44    hot_slots_lfu: BucketedLfu<HotSlotKey>,
45    /// Global LFU tracker for hot account entries.
46    hot_accounts_lfu: BucketedLfu<B256>,
47    /// Metrics for the sparse state trie.
48    #[cfg(feature = "metrics")]
49    metrics: crate::metrics::SparseStateTrieMetrics,
50}
51
52impl<A, S> Default for SparseStateTrie<A, S>
53where
54    A: Default,
55    S: Default,
56{
57    fn default() -> Self {
58        Self {
59            state: Default::default(),
60            storage: Default::default(),
61            retain_updates: false,
62            deferred_drops: DeferredDrops::default(),
63            hot_slots_lfu: BucketedLfu::default(),
64            hot_accounts_lfu: BucketedLfu::default(),
65            #[cfg(feature = "metrics")]
66            metrics: Default::default(),
67        }
68    }
69}
70
71#[cfg(test)]
72impl SparseStateTrie {
73    /// Create state trie from state trie.
74    pub fn from_state(state: RevealableSparseTrie) -> Self {
75        Self { state, ..Default::default() }
76    }
77}
78
79impl<A, S> SparseStateTrie<A, S> {
80    /// Set the retention of branch node updates and deletions.
81    pub const fn set_updates(&mut self, retain_updates: bool) {
82        self.retain_updates = retain_updates;
83    }
84
85    /// Set the retention of branch node updates and deletions.
86    pub const fn with_updates(mut self, retain_updates: bool) -> Self {
87        self.set_updates(retain_updates);
88        self
89    }
90
91    /// Seeds the hot account/storage LFU caches with their configured capacities.
92    ///
93    /// This must happen before the first `record_*_touch` call, otherwise touches are ignored while
94    /// the LFUs still have zero capacity.
95    pub fn set_hot_cache_capacities(&mut self, max_hot_slots: usize, max_hot_accounts: usize) {
96        self.hot_slots_lfu.decay_and_evict(max_hot_slots);
97        self.hot_accounts_lfu.decay_and_evict(max_hot_accounts);
98    }
99
100    /// Seeds the hot account/storage LFU caches with their configured capacities.
101    pub fn with_hot_cache_capacities(
102        mut self,
103        max_hot_slots: usize,
104        max_hot_accounts: usize,
105    ) -> Self {
106        self.set_hot_cache_capacities(max_hot_slots, max_hot_accounts);
107        self
108    }
109
110    /// Set the accounts trie to the given `RevealableSparseTrie`.
111    pub fn set_accounts_trie(&mut self, trie: RevealableSparseTrie<A>) {
112        self.state = trie;
113    }
114
115    /// Set the accounts trie to the given `RevealableSparseTrie`.
116    pub fn with_accounts_trie(mut self, trie: RevealableSparseTrie<A>) -> Self {
117        self.set_accounts_trie(trie);
118        self
119    }
120
121    /// Set the default trie which will be cloned when creating new storage
122    /// [`RevealableSparseTrie`]s.
123    pub fn set_default_storage_trie(&mut self, trie: RevealableSparseTrie<S>) {
124        self.storage.default_trie = trie;
125    }
126
127    /// Set the default trie which will be cloned when creating new storage
128    /// [`RevealableSparseTrie`]s.
129    pub fn with_default_storage_trie(mut self, trie: RevealableSparseTrie<S>) -> Self {
130        self.set_default_storage_trie(trie);
131        self
132    }
133
134    /// Takes the data structures for deferred dropping.
135    ///
136    /// This allows the caller to drop the buffers later, avoiding expensive deallocations while
137    /// calculating the state root.
138    pub fn take_deferred_drops(&mut self) -> DeferredDrops {
139        core::mem::take(&mut self.deferred_drops)
140    }
141}
142
143impl SparseStateTrie {
144    /// Create new [`SparseStateTrie`] with the default trie implementation.
145    pub fn new() -> Self {
146        Self::default()
147    }
148}
149
150impl<A: SparseTrieTrait, S: SparseTrieTrait> SparseStateTrie<A, S> {
151    /// Takes all debug recorders from the account trie and all revealed storage tries.
152    ///
153    /// Returns a vec of `(Option<B256>, TrieDebugRecorder)` where `None` is the account trie
154    /// key, and `Some(address)` are storage trie keys.
155    #[cfg(feature = "trie-debug")]
156    pub fn take_debug_recorders(&mut self) -> alloc::vec::Vec<(Option<B256>, TrieDebugRecorder)> {
157        let mut recorders = alloc::vec::Vec::new();
158        if let Some(trie) = self.state.as_revealed_mut() {
159            recorders.push((None, trie.take_debug_recorder()));
160        }
161        for (address, trie) in &mut self.storage.tries {
162            if let Some(trie) = trie.as_revealed_mut() {
163                recorders.push((Some(*address), trie.take_debug_recorder()));
164            }
165        }
166        recorders
167    }
168}
169
170impl<A, S> SparseStateTrie<A, S>
171where
172    A: SparseTrieTrait + Default,
173    S: SparseTrieTrait + Default + Clone,
174{
175    /// Returns mutable reference to account trie.
176    pub const fn trie_mut(&mut self) -> &mut RevealableSparseTrie<A> {
177        &mut self.state
178    }
179
180    /// Returns `true` if the account path has been revealed in the sparse trie.
181    pub fn is_account_revealed(&self, account: B256) -> bool {
182        let path = Nibbles::unpack(account);
183        let trie = match self.state_trie_ref() {
184            Some(t) => t,
185            None => return false,
186        };
187
188        trie.find_leaf(&path, None).is_ok()
189    }
190
191    /// Was the storage-slot witness for (`address`,`slot`) complete?
192    pub fn check_valid_storage_witness(&self, address: B256, slot: B256) -> bool {
193        let path = Nibbles::unpack(slot);
194        let trie = match self.storage_trie_ref(&address) {
195            Some(t) => t,
196            None => return false,
197        };
198
199        trie.find_leaf(&path, None).is_ok()
200    }
201
202    /// Records a storage slot access/update in the global LFU tracker.
203    #[inline]
204    pub fn record_slot_touch(&mut self, account: B256, slot: B256) {
205        self.hot_slots_lfu.touch(HotSlotKey { address: account, slot });
206    }
207
208    /// Records an account access/update in the global LFU tracker.
209    #[inline]
210    pub fn record_account_touch(&mut self, account: B256) {
211        self.hot_accounts_lfu.touch(account);
212    }
213
214    /// Returns reference to bytes representing leaf value for the target account.
215    pub fn get_account_value(&self, account: &B256) -> Option<&Vec<u8>> {
216        self.state.as_revealed_ref()?.get_leaf_value(&Nibbles::unpack(account))
217    }
218
219    /// Returns reference to bytes representing leaf value for the target account and storage slot.
220    pub fn get_storage_slot_value(&self, account: &B256, slot: &B256) -> Option<&Vec<u8>> {
221        self.storage.tries.get(account)?.as_revealed_ref()?.get_leaf_value(&Nibbles::unpack(slot))
222    }
223
224    /// Returns reference to state trie if it was revealed.
225    pub const fn state_trie_ref(&self) -> Option<&A> {
226        self.state.as_revealed_ref()
227    }
228
229    /// Returns reference to storage trie if it was revealed.
230    pub fn storage_trie_ref(&self, address: &B256) -> Option<&S> {
231        self.storage.tries.get(address).and_then(|e| e.as_revealed_ref())
232    }
233
234    /// Returns mutable reference to storage sparse trie if it was revealed.
235    pub fn storage_trie_mut(&mut self, address: &B256) -> Option<&mut S> {
236        self.storage.tries.get_mut(address).and_then(|e| e.as_revealed_mut())
237    }
238
239    /// Returns mutable reference to storage tries.
240    pub const fn storage_tries_mut(&mut self) -> &mut B256Map<RevealableSparseTrie<S>> {
241        &mut self.storage.tries
242    }
243
244    /// Takes the storage trie for the provided address.
245    pub fn take_storage_trie(&mut self, address: &B256) -> Option<RevealableSparseTrie<S>> {
246        self.storage.tries.remove(address)
247    }
248
249    /// Takes the storage trie for the provided address, creating a blind one if it doesn't exist.
250    pub fn take_or_create_storage_trie(&mut self, address: &B256) -> RevealableSparseTrie<S> {
251        self.storage.tries.remove(address).unwrap_or_else(|| {
252            self.storage.cleared_tries.pop().unwrap_or_else(|| self.storage.default_trie.clone())
253        })
254    }
255
256    /// Inserts storage trie for the provided address.
257    pub fn insert_storage_trie(&mut self, address: B256, storage_trie: RevealableSparseTrie<S>) {
258        self.storage.tries.insert(address, storage_trie);
259    }
260
261    /// Returns mutable reference to storage sparse trie, creating a blind one if it doesn't exist.
262    pub fn get_or_create_storage_trie_mut(
263        &mut self,
264        address: B256,
265    ) -> &mut RevealableSparseTrie<S> {
266        self.storage.get_or_create_trie_mut(address)
267    }
268
269    /// Reveal unknown trie paths from multiproof.
270    /// NOTE: This method does not extensively validate the proof.
271    pub fn reveal_multiproof(&mut self, multiproof: MultiProof) -> SparseStateTrieResult<()> {
272        // first decode the multiproof
273        let decoded_multiproof = multiproof.try_into()?;
274
275        // then reveal the decoded multiproof
276        self.reveal_decoded_multiproof(decoded_multiproof)
277    }
278
279    /// Reveal unknown trie paths from decoded multiproof.
280    /// NOTE: This method does not extensively validate the proof.
281    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
282    pub fn reveal_decoded_multiproof(
283        &mut self,
284        multiproof: DecodedMultiProof,
285    ) -> SparseStateTrieResult<()> {
286        self.reveal_decoded_multiproof_v2(multiproof.into())
287    }
288
289    /// Reveals a V2 decoded multiproof.
290    ///
291    /// V2 multiproofs use a simpler format where proof nodes are stored as vectors rather than
292    /// hashmaps, with masks already included in the `ProofTrieNode` structure.
293    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
294    pub fn reveal_decoded_multiproof_v2(
295        &mut self,
296        multiproof: reth_trie_common::DecodedMultiProofV2,
297    ) -> SparseStateTrieResult<()> {
298        let reth_trie_common::DecodedMultiProofV2 { account_proofs, mut storage_proofs, .. } =
299            multiproof;
300
301        // Collect `(trie, proof_nodes)` pairs for both the account trie and every storage trie
302        // touched by this multiproof.
303        let mut targets = Vec::with_capacity(storage_proofs.len() + 1);
304
305        if !account_proofs.is_empty() {
306            #[cfg(feature = "metrics")]
307            self.metrics.increment_total_account_nodes(account_proofs.len() as u64);
308            targets.push((Either::Left(&mut self.state), account_proofs));
309        }
310
311        // Ensure a storage trie exists for every address whose proofs we're about to reveal
312        for &account in storage_proofs.keys() {
313            let _ = self.storage.get_or_create_trie_mut(account);
314        }
315
316        for (account, trie) in &mut self.storage.tries {
317            if let Some(nodes) = storage_proofs.remove(account) {
318                #[cfg(feature = "metrics")]
319                self.metrics.increment_total_storage_nodes(nodes.len() as u64);
320                targets.push((Either::Right(trie), nodes));
321            }
322        }
323
324        let retain_updates = self.retain_updates;
325
326        #[cfg(not(feature = "std"))]
327        let results: Vec<_> = targets
328            .into_iter()
329            .map(|(target, mut nodes)| {
330                let result = match target {
331                    Either::Left(trie) => trie.reveal_v2_proof_nodes(&mut nodes, retain_updates),
332                    Either::Right(trie) => trie.reveal_v2_proof_nodes(&mut nodes, retain_updates),
333                };
334                (result, nodes)
335            })
336            .collect();
337
338        #[cfg(feature = "std")]
339        let results: Vec<_> = {
340            use rayon::iter::ParallelIterator;
341            use reth_primitives_traits::ParallelBridgeBuffered;
342
343            targets
344                .into_iter()
345                .par_bridge_buffered()
346                .map(|(target, mut nodes)| {
347                    let result = match target {
348                        Either::Left(trie) => {
349                            trie.reveal_v2_proof_nodes(&mut nodes, retain_updates)
350                        }
351                        Either::Right(trie) => {
352                            trie.reveal_v2_proof_nodes(&mut nodes, retain_updates)
353                        }
354                    };
355                    (result, nodes)
356                })
357                .collect()
358        };
359
360        // Accumulate the first error and defer dropping the proof node buffers.
361        let mut any_err = Ok(());
362        for (result, nodes) in results {
363            if result.is_err() && any_err.is_ok() {
364                any_err = result.map_err(Into::into);
365            }
366            self.deferred_drops.proof_nodes_bufs.push(nodes);
367        }
368
369        any_err
370    }
371
372    /// Wipe the storage trie at the provided address.
373    pub fn wipe_storage(&mut self, address: B256) -> SparseStateTrieResult<()> {
374        if let Some(trie) = self.storage.tries.get_mut(&address) {
375            trie.wipe()?;
376        }
377        Ok(())
378    }
379
380    /// Calculates the hashes of subtries.
381    ///
382    /// If the trie has not been revealed, this function does nothing.
383    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
384    pub fn calculate_subtries(&mut self) {
385        if let RevealableSparseTrie::Revealed(trie) = &mut self.state {
386            trie.update_subtrie_hashes();
387        }
388    }
389
390    /// Returns storage sparse trie root if the trie has been revealed.
391    pub fn storage_root(&mut self, account: &B256) -> Option<B256> {
392        self.storage.tries.get_mut(account).and_then(|trie| trie.root())
393    }
394
395    /// Returns mutable reference to the revealed account sparse trie.
396    fn revealed_trie_mut(&mut self) -> SparseStateTrieResult<&mut A> {
397        self.state.as_revealed_mut().ok_or_else(|| SparseTrieErrorKind::Blind.into())
398    }
399
400    /// Returns sparse trie root.
401    pub fn root(&mut self) -> SparseStateTrieResult<B256> {
402        // record revealed node metrics
403        #[cfg(feature = "metrics")]
404        self.metrics.record();
405
406        Ok(self.revealed_trie_mut()?.root())
407    }
408
409    /// Returns sparse trie root and trie updates.
410    ///
411    /// Returns an error if the account trie is still blind.
412    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
413    pub fn root_with_updates(&mut self) -> SparseStateTrieResult<(B256, TrieUpdates)> {
414        // record revealed node metrics
415        #[cfg(feature = "metrics")]
416        self.metrics.record();
417
418        let storage_tries = self.storage_trie_updates();
419        let revealed = self.revealed_trie_mut()?;
420
421        let (root, updates) = (revealed.root(), revealed.take_updates());
422        let updates = TrieUpdates {
423            account_nodes: updates.updated_nodes,
424            removed_nodes: updates.removed_nodes,
425            storage_tries,
426        };
427        Ok((root, updates))
428    }
429
430    /// Returns storage trie updates for tries that have been revealed.
431    ///
432    /// Panics if any of the storage tries are not revealed.
433    pub fn storage_trie_updates(&mut self) -> B256Map<StorageTrieUpdates> {
434        self.storage
435            .tries
436            .iter_mut()
437            .map(|(address, trie)| {
438                let trie = trie.as_revealed_mut().unwrap();
439                let updates = trie.take_updates();
440                let updates = StorageTrieUpdates {
441                    is_deleted: updates.wiped,
442                    storage_nodes: updates.updated_nodes,
443                    removed_nodes: updates.removed_nodes,
444                };
445                (*address, updates)
446            })
447            .filter(|(_, updates)| !updates.is_empty())
448            .collect()
449    }
450
451    /// Returns [`TrieUpdates`] by taking the updates from the revealed sparse tries.
452    ///
453    /// Returns `None` if the accounts trie is not revealed.
454    pub fn take_trie_updates(&mut self) -> Option<TrieUpdates> {
455        let storage_tries = self.storage_trie_updates();
456        self.state.as_revealed_mut().map(|state| {
457            let updates = state.take_updates();
458            TrieUpdates {
459                account_nodes: updates.updated_nodes,
460                removed_nodes: updates.removed_nodes,
461                storage_tries,
462            }
463        })
464    }
465}
466
467impl<A, S> SparseStateTrie<A, S>
468where
469    A: SparseTrieTrait + Default,
470    S: SparseTrieTrait + Default + Clone,
471{
472    /// Clears all trie data while preserving allocations for reuse.
473    ///
474    /// This resets the trie to an empty state but keeps the underlying memory allocations,
475    /// which can significantly reduce allocation overhead when the trie is reused.
476    pub fn clear(&mut self) {
477        self.state.clear();
478        self.storage.clear();
479    }
480
481    /// Returns a heuristic for the total in-memory size of this state trie in bytes.
482    ///
483    /// This aggregates the memory usage of the account trie, all revealed storage tries
484    /// (including cleared ones retained for allocation reuse), and auxiliary data structures.
485    pub fn memory_size(&self) -> usize {
486        let mut size = core::mem::size_of::<Self>();
487
488        size += match &self.state {
489            RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
490                t.memory_size()
491            }
492            RevealableSparseTrie::Blind(None) => 0,
493        };
494
495        for trie in self.storage.tries.values() {
496            size += match trie {
497                RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
498                    t.memory_size()
499                }
500                RevealableSparseTrie::Blind(None) => 0,
501            };
502        }
503        for trie in &self.storage.cleared_tries {
504            size += match trie {
505                RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
506                    t.memory_size()
507                }
508                RevealableSparseTrie::Blind(None) => 0,
509            };
510        }
511
512        size
513    }
514
515    /// Returns the number of storage tries currently retained (active + cleared).
516    pub fn retained_storage_tries_count(&self) -> usize {
517        self.storage.tries.len() + self.storage.cleared_tries.len()
518    }
519
520    /// Prunes account/storage tries according to global LFU retention.
521    ///
522    /// - Top LFU `(address, slot)` entries are retained up to `max_hot_slots`.
523    ///
524    /// - Top LFU `(address, slot)` entries are retained in storage tries.
525    /// - Account trie retains only paths for accounts tracked by the account LFU.
526    /// - Storage tries retain only paths needed for retained slots.
527    /// - All other revealed paths are pruned to hash stubs or fully evicted.
528    ///
529    /// # Preconditions
530    ///
531    /// All revealed account and storage tries must already have computed hashes via `root()`
532    /// / `storage_root()` for their current state. Pruning a dirty revealed trie is a hard
533    /// error and may panic.
534    #[cfg(feature = "std")]
535    #[instrument(
536        level = "debug",
537        name = "SparseStateTrie::prune",
538        target = "trie::sparse",
539        skip_all,
540        fields(%max_hot_slots, %max_hot_accounts)
541    )]
542    pub fn prune(&mut self, max_hot_slots: usize, max_hot_accounts: usize) {
543        self.hot_slots_lfu.decay_and_evict(max_hot_slots);
544        self.hot_accounts_lfu.decay_and_evict(max_hot_accounts);
545        let retained = self.hot_slots_lfu.retained_slots_by_address();
546
547        let retained_account_paths: Vec<Nibbles> =
548            self.hot_accounts_lfu.keys().map(|k| Nibbles::unpack(*k)).collect();
549
550        let retained_accounts = retained_account_paths.len();
551        let retained_storage_tries = retained.len();
552        let total_storage_tries_before = self.storage.tries.len();
553
554        // Prune account and storage tries in parallel using the same LFU-selected set.
555        let (account_nodes_pruned, storage_tries_evicted) = rayon::join(
556            || {
557                self.state
558                    .as_revealed_mut()
559                    .map(|trie| trie.prune(&retained_account_paths))
560                    .unwrap_or(0)
561            },
562            || self.storage.prune_by_retained_slots(retained),
563        );
564
565        debug!(
566            target: "trie::sparse",
567            retained_accounts,
568            retained_storage_tries,
569            account_nodes_pruned,
570            storage_tries_evicted,
571            storage_tries_after = total_storage_tries_before - storage_tries_evicted,
572            "SparseStateTrie::prune completed"
573        );
574    }
575}
576
577/// The fields of [`SparseStateTrie`] related to storage tries. This is kept separate from the rest
578/// of [`SparseStateTrie`] to help enforce allocation re-use.
579#[derive(Debug, Default)]
580struct StorageTries<S = ArenaParallelSparseTrie> {
581    /// Sparse storage tries.
582    tries: B256Map<RevealableSparseTrie<S>>,
583    /// Cleared storage tries, kept for re-use.
584    cleared_tries: Vec<RevealableSparseTrie<S>>,
585    /// A default cleared trie instance, which will be cloned when creating new tries.
586    default_trie: RevealableSparseTrie<S>,
587}
588
589#[cfg(feature = "std")]
590impl<S: SparseTrieTrait> StorageTries<S> {
591    /// Prunes storage tries using LFU-retained slots.
592    ///
593    /// Tries without retained slots are evicted entirely. Tries with retained slots are pruned to
594    /// those slots.
595    fn prune_by_retained_slots(&mut self, retained_slots: B256Map<Vec<Nibbles>>) -> usize {
596        // Parallel pass: prune retained tries and clear evicted ones in place.
597        {
598            use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
599            self.tries.par_iter_mut().for_each(|(address, trie)| {
600                if let Some(slots) = retained_slots.get(address) {
601                    if let Some(t) = trie.as_revealed_mut() {
602                        t.prune(slots);
603                    }
604                } else {
605                    trie.clear();
606                }
607            });
608        }
609
610        // Cheap sequential drain: move already-cleared tries into the reuse pool.
611        let addresses_to_evict: Vec<B256> = self
612            .tries
613            .keys()
614            .filter(|address| !retained_slots.contains_key(*address))
615            .copied()
616            .collect();
617
618        let evicted = addresses_to_evict.len();
619        self.cleared_tries.reserve(evicted);
620        for address in &addresses_to_evict {
621            if let Some(trie) = self.tries.remove(address) {
622                self.cleared_tries.push(trie);
623            }
624        }
625
626        evicted
627    }
628}
629
630impl<S: SparseTrieTrait> StorageTries<S> {
631    /// Returns all fields to a cleared state, equivalent to the default state, keeping cleared
632    /// collections for re-use later when possible.
633    fn clear(&mut self) {
634        self.cleared_tries.extend(self.tries.drain().map(|(_, mut trie)| {
635            trie.clear();
636            trie
637        }));
638    }
639}
640
641impl<S: SparseTrieTrait + Clone> StorageTries<S> {
642    // Returns mutable reference to storage sparse trie, creating a blind one if it doesn't exist.
643    fn get_or_create_trie_mut(&mut self, address: B256) -> &mut RevealableSparseTrie<S> {
644        self.tries.entry(address).or_insert_with(|| {
645            self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone())
646        })
647    }
648}
649
650/// Key for identifying a storage slot in the global LFU cache.
651#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
652struct HotSlotKey {
653    address: B256,
654    slot: B256,
655}
656
657/// Slot-specific helpers for the `BucketedLfu<HotSlotKey>`.
658#[cfg(feature = "std")]
659impl BucketedLfu<HotSlotKey> {
660    /// Returns retained slots grouped by address.
661    fn retained_slots_by_address(&self) -> B256Map<Vec<Nibbles>> {
662        let mut grouped =
663            B256Map::<Vec<Nibbles>>::with_capacity_and_hasher(self.len(), Default::default());
664        for key in self.keys() {
665            grouped.entry(key.address).or_default().push(Nibbles::unpack(key.slot));
666        }
667
668        for slots in grouped.values_mut() {
669            slots.sort_unstable();
670            slots.dedup();
671        }
672
673        grouped
674    }
675}
676
677#[cfg(test)]
678mod tests {
679    use super::*;
680    use crate::{ArenaParallelSparseTrie, LeafLookup, LeafUpdate};
681    use alloy_primitives::{
682        b256,
683        map::{HashMap, HashSet},
684        U256,
685    };
686    use arbitrary::Arbitrary;
687    use rand::{rngs::StdRng, Rng, SeedableRng};
688    use reth_execution_errors::{SparseStateTrieErrorKind, SparseTrieErrorKind};
689    use reth_primitives_traits::Account;
690    use reth_trie::{updates::StorageTrieUpdates, HashBuilder, MultiProof, EMPTY_ROOT_HASH};
691    use reth_trie_common::{
692        proof::{ProofNodes, ProofRetainer},
693        BranchNodeMasks, BranchNodeMasksMap, BranchNodeV2, LeafNode, RlpNode, StorageMultiProof,
694        TrieAccount, TrieMask, TrieNodeV2,
695    };
696
697    /// Create a leaf key (suffix) with given nibbles padded with zeros to reach `total_len`.
698    fn leaf_key(suffix: impl AsRef<[u8]>, total_len: usize) -> Nibbles {
699        let suffix = suffix.as_ref();
700        let mut nibbles = Nibbles::from_nibbles(suffix);
701        nibbles.extend(&Nibbles::from_nibbles_unchecked(vec![0; total_len - suffix.len()]));
702        nibbles
703    }
704
705    fn apply_account_update(sparse: &mut SparseStateTrie, address: B256, update: LeafUpdate) {
706        let mut updates = B256Map::from_iter([(address, update)]);
707        sparse.trie_mut().update_leaves(&mut updates, |_, _| {}).unwrap();
708        assert!(updates.is_empty());
709    }
710
711    #[test]
712    fn reveal_account_path_twice() {
713        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
714
715        // Full 64-nibble paths
716        let full_path_0 = leaf_key([0x0], 64);
717        let _full_path_1 = leaf_key([0x1], 64);
718
719        let leaf_value = alloy_rlp::encode(TrieAccount::default());
720        // Leaf key is 63 nibbles (suffix after 1-nibble node path)
721        let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
722            leaf_key([], 63),
723            leaf_value.clone(),
724        )));
725        let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
726            leaf_key([], 63),
727            leaf_value.clone(),
728        )));
729
730        let multiproof = MultiProof {
731            account_subtree: ProofNodes::from_iter([
732                (
733                    Nibbles::default(),
734                    alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
735                        key: Nibbles::default(),
736                        stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
737                        state_mask: TrieMask::new(0b11),
738                        branch_rlp_node: None,
739                    }))
740                    .into(),
741                ),
742                (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
743                (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
744            ]),
745            ..Default::default()
746        };
747
748        // Reveal multiproof and check that the state trie contains the leaf node and value
749        sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
750        assert!(matches!(
751            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
752            Ok(LeafLookup::Exists)
753        ));
754        assert_eq!(
755            sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
756            Some(&leaf_value)
757        );
758
759        // Remove the leaf node and check that the state trie does not contain the leaf node and
760        // value
761        apply_account_update(&mut sparse, B256::ZERO, LeafUpdate::Changed(Vec::new()));
762        assert!(matches!(
763            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
764            Ok(LeafLookup::NonExistent)
765        ));
766        assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
767    }
768
769    #[test]
770    fn reveal_storage_path_twice() {
771        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
772
773        // Full 64-nibble path
774        let full_path_0 = leaf_key([0x0], 64);
775
776        let leaf_value = alloy_rlp::encode(TrieAccount::default());
777        let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
778            leaf_key([], 63),
779            leaf_value.clone(),
780        )));
781        let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
782            leaf_key([], 63),
783            leaf_value.clone(),
784        )));
785
786        let multiproof = MultiProof {
787            storages: HashMap::from_iter([(
788                B256::ZERO,
789                StorageMultiProof {
790                    root: B256::ZERO,
791                    subtree: ProofNodes::from_iter([
792                        (
793                            Nibbles::default(),
794                            alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
795                                key: Nibbles::default(),
796                                stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
797                                state_mask: TrieMask::new(0b11),
798                                branch_rlp_node: None,
799                            }))
800                            .into(),
801                        ),
802                        (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
803                        (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
804                    ]),
805                    branch_node_masks: Default::default(),
806                },
807            )]),
808            ..Default::default()
809        };
810
811        // Reveal multiproof and check that the storage trie contains the leaf node and value
812        sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
813        assert!(matches!(
814            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
815            Ok(LeafLookup::Exists)
816        ));
817        assert_eq!(
818            sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
819            Some(&leaf_value)
820        );
821
822        // Remove the leaf node and check that the storage trie does not contain the leaf node and
823        // value
824        let mut updates = B256Map::from_iter([(B256::ZERO, LeafUpdate::Changed(Vec::new()))]);
825        sparse
826            .storage_trie_mut(&B256::ZERO)
827            .unwrap()
828            .update_leaves(&mut updates, |_, _| {})
829            .unwrap();
830        assert!(updates.is_empty());
831        assert!(matches!(
832            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
833            Ok(LeafLookup::NonExistent)
834        ));
835        assert!(sparse
836            .storage_trie_ref(&B256::ZERO)
837            .unwrap()
838            .get_leaf_value(&full_path_0)
839            .is_none());
840    }
841
842    #[test]
843    fn seeded_hot_cache_capacities_preserve_first_cycle_touches() {
844        let account = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
845        let slot = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
846        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
847
848        sparse.set_hot_cache_capacities(1, 1);
849        sparse.record_account_touch(account);
850        sparse.record_slot_touch(account, slot);
851        sparse.prune(1, 1);
852
853        assert_eq!(sparse.hot_accounts_lfu.keys().copied().collect::<Vec<_>>(), vec![account]);
854        assert_eq!(
855            sparse.hot_slots_lfu.keys().copied().collect::<Vec<_>>(),
856            vec![HotSlotKey { address: account, slot }]
857        );
858    }
859
860    #[test]
861    fn reveal_v2_proof_nodes() {
862        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
863
864        // Full 64-nibble path
865        let full_path_0 = leaf_key([0x0], 64);
866
867        let leaf_value = alloy_rlp::encode(TrieAccount::default());
868        let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
869        let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
870
871        let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
872            key: Nibbles::default(),
873            stack: vec![
874                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
875                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
876            ],
877            state_mask: TrieMask::new(0b11),
878            branch_rlp_node: None,
879        });
880
881        // Create V2 proof nodes with masks already included
882        let v2_proof_nodes = vec![
883            ProofTrieNodeV2 {
884                path: Nibbles::default(),
885                node: branch_node,
886                masks: Some(BranchNodeMasks {
887                    hash_mask: TrieMask::default(),
888                    tree_mask: TrieMask::default(),
889                }),
890            },
891            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
892            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
893        ];
894
895        // Reveal V2 proof nodes
896        sparse
897            .reveal_decoded_multiproof_v2(reth_trie_common::DecodedMultiProofV2 {
898                account_proofs: v2_proof_nodes,
899                ..Default::default()
900            })
901            .unwrap();
902
903        // Check that the state trie contains the leaf node and value
904        assert!(matches!(
905            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
906            Ok(LeafLookup::Exists)
907        ));
908        assert_eq!(
909            sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
910            Some(&leaf_value)
911        );
912
913        // Remove the leaf node
914        apply_account_update(&mut sparse, B256::ZERO, LeafUpdate::Changed(Vec::new()));
915        assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
916    }
917
918    #[test]
919    fn reveal_storage_v2_proof_nodes() {
920        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
921
922        // Full 64-nibble path
923        let full_path_0 = leaf_key([0x0], 64);
924
925        let storage_value: Vec<u8> = alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec();
926        let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
927        let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
928
929        let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
930            key: Nibbles::default(),
931            stack: vec![
932                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
933                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
934            ],
935            state_mask: TrieMask::new(0b11),
936            branch_rlp_node: None,
937        });
938
939        let v2_proof_nodes = vec![
940            ProofTrieNodeV2 { path: Nibbles::default(), node: branch_node, masks: None },
941            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
942            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
943        ];
944
945        // Reveal V2 storage proof nodes for account
946        sparse
947            .reveal_decoded_multiproof_v2(reth_trie_common::DecodedMultiProofV2 {
948                storage_proofs: B256Map::from_iter([(B256::ZERO, v2_proof_nodes)]),
949                ..Default::default()
950            })
951            .unwrap();
952
953        // Check that the storage trie contains the leaf node and value
954        assert!(matches!(
955            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
956            Ok(LeafLookup::Exists)
957        ));
958        assert_eq!(
959            sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
960            Some(&storage_value)
961        );
962
963        // Remove the leaf node
964        let mut updates = B256Map::from_iter([(B256::ZERO, LeafUpdate::Changed(Vec::new()))]);
965        sparse
966            .storage_trie_mut(&B256::ZERO)
967            .unwrap()
968            .update_leaves(&mut updates, |_, _| {})
969            .unwrap();
970        assert!(updates.is_empty());
971        assert!(sparse
972            .storage_trie_ref(&B256::ZERO)
973            .unwrap()
974            .get_leaf_value(&full_path_0)
975            .is_none());
976    }
977
978    #[test]
979    fn root_on_blind_trie_returns_blind_error() {
980        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
981
982        let err = sparse.root().unwrap_err();
983
984        assert!(matches!(err.kind(), SparseStateTrieErrorKind::Sparse(SparseTrieErrorKind::Blind)));
985    }
986
987    #[test]
988    fn take_trie_updates() {
989        reth_tracing::init_test_tracing();
990
991        // let mut rng = generators::rng();
992        let mut rng = StdRng::seed_from_u64(1);
993
994        let mut bytes = [0u8; 1024];
995        rng.fill(bytes.as_mut_slice());
996
997        let slot_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
998        let slot_path_1 = Nibbles::unpack(slot_1);
999        let value_1 = U256::from(rng.random::<u64>());
1000        let slot_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1001        let slot_path_2 = Nibbles::unpack(slot_2);
1002        let value_2 = U256::from(rng.random::<u64>());
1003        let slot_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1004        let value_3 = U256::from(rng.random::<u64>());
1005
1006        let mut storage_hash_builder = HashBuilder::default()
1007            .with_proof_retainer(ProofRetainer::from_iter([slot_path_1, slot_path_2]));
1008        storage_hash_builder.add_leaf(slot_path_1, &alloy_rlp::encode_fixed_size(&value_1));
1009        storage_hash_builder.add_leaf(slot_path_2, &alloy_rlp::encode_fixed_size(&value_2));
1010
1011        let storage_root = storage_hash_builder.root();
1012        let storage_proof_nodes = storage_hash_builder.take_proof_nodes();
1013        let storage_branch_node_masks = BranchNodeMasksMap::from_iter([
1014            (
1015                Nibbles::default(),
1016                BranchNodeMasks { hash_mask: TrieMask::new(0b010), tree_mask: TrieMask::default() },
1017            ),
1018            (
1019                Nibbles::from_nibbles([0x1]),
1020                BranchNodeMasks { hash_mask: TrieMask::new(0b11), tree_mask: TrieMask::default() },
1021            ),
1022        ]);
1023
1024        let address_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1025        let address_path_1 = Nibbles::unpack(address_1);
1026        let account_1 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1027        let mut trie_account_1 = account_1.into_trie_account(storage_root);
1028        let address_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1029        let address_path_2 = Nibbles::unpack(address_2);
1030        let account_2 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1031        let mut trie_account_2 = account_2.into_trie_account(EMPTY_ROOT_HASH);
1032
1033        let mut hash_builder = HashBuilder::default()
1034            .with_proof_retainer(ProofRetainer::from_iter([address_path_1, address_path_2]));
1035        hash_builder.add_leaf(address_path_1, &alloy_rlp::encode(trie_account_1));
1036        hash_builder.add_leaf(address_path_2, &alloy_rlp::encode(trie_account_2));
1037
1038        let root = hash_builder.root();
1039        let proof_nodes = hash_builder.take_proof_nodes();
1040        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default().with_updates(true);
1041        sparse
1042            .reveal_decoded_multiproof(
1043                MultiProof {
1044                    account_subtree: proof_nodes,
1045                    branch_node_masks: BranchNodeMasksMap::from_iter([(
1046                        Nibbles::from_nibbles([0x1]),
1047                        BranchNodeMasks {
1048                            hash_mask: TrieMask::new(0b00),
1049                            tree_mask: TrieMask::default(),
1050                        },
1051                    )]),
1052                    storages: HashMap::from_iter([
1053                        (
1054                            address_1,
1055                            StorageMultiProof {
1056                                root,
1057                                subtree: storage_proof_nodes.clone(),
1058                                branch_node_masks: storage_branch_node_masks.clone(),
1059                            },
1060                        ),
1061                        (
1062                            address_2,
1063                            StorageMultiProof {
1064                                root,
1065                                subtree: storage_proof_nodes,
1066                                branch_node_masks: storage_branch_node_masks,
1067                            },
1068                        ),
1069                    ]),
1070                }
1071                .try_into()
1072                .unwrap(),
1073            )
1074            .unwrap();
1075
1076        assert_eq!(sparse.root().unwrap(), root);
1077
1078        let address_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1079        let account_3 = Account { nonce: account_1.nonce + 1, ..account_1 };
1080        let trie_account_3 = account_3.into_trie_account(EMPTY_ROOT_HASH);
1081
1082        apply_account_update(
1083            &mut sparse,
1084            address_3,
1085            LeafUpdate::Changed(alloy_rlp::encode(trie_account_3)),
1086        );
1087
1088        let mut updates =
1089            B256Map::from_iter([(slot_3, LeafUpdate::Changed(alloy_rlp::encode(value_3)))]);
1090        sparse
1091            .storage_trie_mut(&address_1)
1092            .unwrap()
1093            .update_leaves(&mut updates, |_, _| {})
1094            .unwrap();
1095        assert!(updates.is_empty());
1096        trie_account_1.storage_root = sparse.storage_root(&address_1).unwrap();
1097        apply_account_update(
1098            &mut sparse,
1099            address_1,
1100            LeafUpdate::Changed(alloy_rlp::encode(trie_account_1)),
1101        );
1102
1103        sparse.wipe_storage(address_2).unwrap();
1104        trie_account_2.storage_root = sparse.storage_root(&address_2).unwrap();
1105        apply_account_update(
1106            &mut sparse,
1107            address_2,
1108            LeafUpdate::Changed(alloy_rlp::encode(trie_account_2)),
1109        );
1110
1111        sparse.root().unwrap();
1112
1113        let sparse_updates = sparse.take_trie_updates().unwrap();
1114        // TODO(alexey): assert against real state root calculation updates
1115        pretty_assertions::assert_eq!(
1116            sparse_updates,
1117            TrieUpdates {
1118                account_nodes: HashMap::default(),
1119                storage_tries: HashMap::from_iter([
1120                    (
1121                        b256!("0x1000000000000000000000000000000000000000000000000000000000000000"),
1122                        StorageTrieUpdates {
1123                            is_deleted: false,
1124                            storage_nodes: HashMap::default(),
1125                            removed_nodes: HashSet::from_iter([Nibbles::from_nibbles([0x1])])
1126                        }
1127                    ),
1128                    (
1129                        b256!("0x1100000000000000000000000000000000000000000000000000000000000000"),
1130                        StorageTrieUpdates {
1131                            is_deleted: true,
1132                            storage_nodes: HashMap::default(),
1133                            removed_nodes: HashSet::default()
1134                        }
1135                    )
1136                ]),
1137                removed_nodes: HashSet::default()
1138            }
1139        );
1140    }
1141}