Skip to main content

reth_trie_sparse/
state.rs

1#[cfg(feature = "trie-debug")]
2use crate::debug_recorder::TrieDebugRecorder;
3use crate::{
4    lfu::BucketedLfu,
5    provider::{TrieNodeProvider, TrieNodeProviderFactory},
6    traits::SparseTrie as SparseTrieTrait,
7    ParallelSparseTrie, RevealableSparseTrie,
8};
9use alloc::vec::Vec;
10use alloy_primitives::{map::B256Map, B256};
11use alloy_rlp::{Decodable, Encodable};
12use reth_execution_errors::{SparseStateTrieResult, SparseTrieErrorKind};
13use reth_primitives_traits::Account;
14use reth_trie_common::{
15    updates::{StorageTrieUpdates, TrieUpdates},
16    BranchNodeMasks, DecodedMultiProof, MultiProof, Nibbles, ProofTrieNodeV2, TrieAccount,
17    TrieNodeV2, EMPTY_ROOT_HASH, TRIE_ACCOUNT_RLP_MAX_SIZE,
18};
19#[cfg(feature = "std")]
20use tracing::debug;
21use tracing::{instrument, trace};
22
23/// Holds data that should be dropped after any locks are released.
24///
25/// This is used to defer expensive deallocations (like proof node buffers) until after final state
26/// root is calculated
27#[derive(Debug, Default)]
28pub struct DeferredDrops {
29    /// Each nodes reveal operation creates a new buffer, uses it, and pushes it here.
30    pub proof_nodes_bufs: Vec<Vec<ProofTrieNodeV2>>,
31}
32
33#[derive(Debug)]
34/// Sparse state trie representing lazy-loaded Ethereum state trie.
35pub struct SparseStateTrie<
36    A = ParallelSparseTrie, // Account trie implementation
37    S = ParallelSparseTrie, // Storage trie implementation
38> {
39    /// Sparse account trie.
40    state: RevealableSparseTrie<A>,
41    /// State related to storage tries.
42    storage: StorageTries<S>,
43    /// Flag indicating whether trie updates should be retained.
44    retain_updates: bool,
45    /// Reusable buffer for RLP encoding of trie accounts.
46    account_rlp_buf: Vec<u8>,
47    /// Holds data that should be dropped after final state root is calculated.
48    deferred_drops: DeferredDrops,
49    /// Global LFU tracker for hot `(address, slot)` storage entries.
50    hot_slots_lfu: BucketedLfu<HotSlotKey>,
51    /// Global LFU tracker for hot account entries.
52    hot_accounts_lfu: BucketedLfu<B256>,
53    /// Metrics for the sparse state trie.
54    #[cfg(feature = "metrics")]
55    metrics: crate::metrics::SparseStateTrieMetrics,
56}
57
58impl<A, S> Default for SparseStateTrie<A, S>
59where
60    A: Default,
61    S: Default,
62{
63    fn default() -> Self {
64        Self {
65            state: Default::default(),
66            storage: Default::default(),
67            retain_updates: false,
68            account_rlp_buf: Vec::with_capacity(TRIE_ACCOUNT_RLP_MAX_SIZE),
69            deferred_drops: DeferredDrops::default(),
70            hot_slots_lfu: BucketedLfu::default(),
71            hot_accounts_lfu: BucketedLfu::default(),
72            #[cfg(feature = "metrics")]
73            metrics: Default::default(),
74        }
75    }
76}
77
78#[cfg(test)]
79impl SparseStateTrie {
80    /// Create state trie from state trie.
81    pub fn from_state(state: RevealableSparseTrie) -> Self {
82        Self { state, ..Default::default() }
83    }
84}
85
86impl<A, S> SparseStateTrie<A, S> {
87    /// Set the retention of branch node updates and deletions.
88    pub const fn set_updates(&mut self, retain_updates: bool) {
89        self.retain_updates = retain_updates;
90    }
91
92    /// Set the retention of branch node updates and deletions.
93    pub const fn with_updates(mut self, retain_updates: bool) -> Self {
94        self.set_updates(retain_updates);
95        self
96    }
97
98    /// Seeds the hot account/storage LFU caches with their configured capacities.
99    ///
100    /// This must happen before the first `record_*_touch` call, otherwise touches are ignored while
101    /// the LFUs still have zero capacity.
102    pub fn set_hot_cache_capacities(&mut self, max_hot_slots: usize, max_hot_accounts: usize) {
103        self.hot_slots_lfu.decay_and_evict(max_hot_slots);
104        self.hot_accounts_lfu.decay_and_evict(max_hot_accounts);
105    }
106
107    /// Seeds the hot account/storage LFU caches with their configured capacities.
108    pub fn with_hot_cache_capacities(
109        mut self,
110        max_hot_slots: usize,
111        max_hot_accounts: usize,
112    ) -> Self {
113        self.set_hot_cache_capacities(max_hot_slots, max_hot_accounts);
114        self
115    }
116
117    /// Set the accounts trie to the given `RevealableSparseTrie`.
118    pub fn set_accounts_trie(&mut self, trie: RevealableSparseTrie<A>) {
119        self.state = trie;
120    }
121
122    /// Set the accounts trie to the given `RevealableSparseTrie`.
123    pub fn with_accounts_trie(mut self, trie: RevealableSparseTrie<A>) -> Self {
124        self.set_accounts_trie(trie);
125        self
126    }
127
128    /// Set the default trie which will be cloned when creating new storage
129    /// [`RevealableSparseTrie`]s.
130    pub fn set_default_storage_trie(&mut self, trie: RevealableSparseTrie<S>) {
131        self.storage.default_trie = trie;
132    }
133
134    /// Set the default trie which will be cloned when creating new storage
135    /// [`RevealableSparseTrie`]s.
136    pub fn with_default_storage_trie(mut self, trie: RevealableSparseTrie<S>) -> Self {
137        self.set_default_storage_trie(trie);
138        self
139    }
140
141    /// Takes the data structures for deferred dropping.
142    ///
143    /// This allows the caller to drop the buffers later, avoiding expensive deallocations while
144    /// calculating the state root.
145    pub fn take_deferred_drops(&mut self) -> DeferredDrops {
146        core::mem::take(&mut self.deferred_drops)
147    }
148}
149
150impl SparseStateTrie {
151    /// Create new [`SparseStateTrie`] with the default trie implementation.
152    pub fn new() -> Self {
153        Self::default()
154    }
155}
156
157impl<A: SparseTrieTrait, S: SparseTrieTrait> SparseStateTrie<A, S> {
158    /// Takes all debug recorders from the account trie and all revealed storage tries.
159    ///
160    /// Returns a vec of `(Option<B256>, TrieDebugRecorder)` where `None` is the account trie
161    /// key, and `Some(address)` are storage trie keys.
162    #[cfg(feature = "trie-debug")]
163    pub fn take_debug_recorders(&mut self) -> alloc::vec::Vec<(Option<B256>, TrieDebugRecorder)> {
164        let mut recorders = alloc::vec::Vec::new();
165        if let Some(trie) = self.state.as_revealed_mut() {
166            recorders.push((None, trie.take_debug_recorder()));
167        }
168        for (address, trie) in &mut self.storage.tries {
169            if let Some(trie) = trie.as_revealed_mut() {
170                recorders.push((Some(*address), trie.take_debug_recorder()));
171            }
172        }
173        recorders
174    }
175}
176
177impl<A, S> SparseStateTrie<A, S>
178where
179    A: SparseTrieTrait + Default,
180    S: SparseTrieTrait + Default + Clone,
181{
182    /// Returns mutable reference to account trie.
183    pub const fn trie_mut(&mut self) -> &mut RevealableSparseTrie<A> {
184        &mut self.state
185    }
186
187    /// Returns `true` if the account path has been revealed in the sparse trie.
188    pub fn is_account_revealed(&self, account: B256) -> bool {
189        let path = Nibbles::unpack(account);
190        let trie = match self.state_trie_ref() {
191            Some(t) => t,
192            None => return false,
193        };
194
195        trie.find_leaf(&path, None).is_ok()
196    }
197
198    /// Was the storage-slot witness for (`address`,`slot`) complete?
199    pub fn check_valid_storage_witness(&self, address: B256, slot: B256) -> bool {
200        let path = Nibbles::unpack(slot);
201        let trie = match self.storage_trie_ref(&address) {
202            Some(t) => t,
203            None => return false,
204        };
205
206        trie.find_leaf(&path, None).is_ok()
207    }
208
209    /// Records a storage slot access/update in the global LFU tracker.
210    #[inline]
211    pub fn record_slot_touch(&mut self, account: B256, slot: B256) {
212        self.hot_slots_lfu.touch(HotSlotKey { address: account, slot });
213    }
214
215    /// Records an account access/update in the global LFU tracker.
216    #[inline]
217    pub fn record_account_touch(&mut self, account: B256) {
218        self.hot_accounts_lfu.touch(account);
219    }
220
221    /// Returns reference to bytes representing leaf value for the target account.
222    pub fn get_account_value(&self, account: &B256) -> Option<&Vec<u8>> {
223        self.state.as_revealed_ref()?.get_leaf_value(&Nibbles::unpack(account))
224    }
225
226    /// Returns reference to bytes representing leaf value for the target account and storage slot.
227    pub fn get_storage_slot_value(&self, account: &B256, slot: &B256) -> Option<&Vec<u8>> {
228        self.storage.tries.get(account)?.as_revealed_ref()?.get_leaf_value(&Nibbles::unpack(slot))
229    }
230
231    /// Returns reference to state trie if it was revealed.
232    pub const fn state_trie_ref(&self) -> Option<&A> {
233        self.state.as_revealed_ref()
234    }
235
236    /// Returns reference to storage trie if it was revealed.
237    pub fn storage_trie_ref(&self, address: &B256) -> Option<&S> {
238        self.storage.tries.get(address).and_then(|e| e.as_revealed_ref())
239    }
240
241    /// Returns mutable reference to storage sparse trie if it was revealed.
242    pub fn storage_trie_mut(&mut self, address: &B256) -> Option<&mut S> {
243        self.storage.tries.get_mut(address).and_then(|e| e.as_revealed_mut())
244    }
245
246    /// Returns mutable reference to storage tries.
247    pub const fn storage_tries_mut(&mut self) -> &mut B256Map<RevealableSparseTrie<S>> {
248        &mut self.storage.tries
249    }
250
251    /// Takes the storage trie for the provided address.
252    pub fn take_storage_trie(&mut self, address: &B256) -> Option<RevealableSparseTrie<S>> {
253        self.storage.tries.remove(address)
254    }
255
256    /// Takes the storage trie for the provided address, creating a blind one if it doesn't exist.
257    pub fn take_or_create_storage_trie(&mut self, address: &B256) -> RevealableSparseTrie<S> {
258        self.storage.tries.remove(address).unwrap_or_else(|| {
259            self.storage.cleared_tries.pop().unwrap_or_else(|| self.storage.default_trie.clone())
260        })
261    }
262
263    /// Inserts storage trie for the provided address.
264    pub fn insert_storage_trie(&mut self, address: B256, storage_trie: RevealableSparseTrie<S>) {
265        self.storage.tries.insert(address, storage_trie);
266    }
267
268    /// Returns mutable reference to storage sparse trie, creating a blind one if it doesn't exist.
269    pub fn get_or_create_storage_trie_mut(
270        &mut self,
271        address: B256,
272    ) -> &mut RevealableSparseTrie<S> {
273        self.storage.get_or_create_trie_mut(address)
274    }
275
276    /// Reveal unknown trie paths from multiproof.
277    /// NOTE: This method does not extensively validate the proof.
278    pub fn reveal_multiproof(&mut self, multiproof: MultiProof) -> SparseStateTrieResult<()> {
279        // first decode the multiproof
280        let decoded_multiproof = multiproof.try_into()?;
281
282        // then reveal the decoded multiproof
283        self.reveal_decoded_multiproof(decoded_multiproof)
284    }
285
286    /// Reveal unknown trie paths from decoded multiproof.
287    /// NOTE: This method does not extensively validate the proof.
288    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
289    pub fn reveal_decoded_multiproof(
290        &mut self,
291        multiproof: DecodedMultiProof,
292    ) -> SparseStateTrieResult<()> {
293        self.reveal_decoded_multiproof_v2(multiproof.into())
294    }
295
296    /// Reveals a V2 decoded multiproof.
297    ///
298    /// V2 multiproofs use a simpler format where proof nodes are stored as vectors rather than
299    /// hashmaps, with masks already included in the `ProofTrieNode` structure.
300    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
301    pub fn reveal_decoded_multiproof_v2(
302        &mut self,
303        multiproof: reth_trie_common::DecodedMultiProofV2,
304    ) -> SparseStateTrieResult<()> {
305        // Reveal the account proof nodes.
306        //
307        // Skip revealing account nodes if this result only contains storage proofs.
308        // `reveal_account_v2_proof_nodes` will return an error if empty `nodes` are passed into it
309        // before the accounts trie root was revealed. This might happen in cases when first account
310        // trie proof arrives later than first storage trie proof even though the account trie proof
311        // was requested first.
312        if !multiproof.account_proofs.is_empty() {
313            self.reveal_account_v2_proof_nodes(multiproof.account_proofs)?;
314        }
315
316        #[cfg(not(feature = "std"))]
317        // If nostd then serially reveal storage proof nodes for each storage trie
318        {
319            for (account, storage_proofs) in multiproof.storage_proofs {
320                self.reveal_storage_v2_proof_nodes(account, storage_proofs)?;
321            }
322
323            Ok(())
324        }
325
326        #[cfg(feature = "std")]
327        // If std then reveal storage proofs in parallel
328        {
329            use rayon::iter::ParallelIterator;
330            use reth_primitives_traits::ParallelBridgeBuffered;
331
332            let retain_updates = self.retain_updates;
333
334            // Process all storage trie revealings in parallel, having first removed the
335            // `RevealableSparseTrie`s for each account from their HashMaps. These will be
336            // returned after processing.
337            let results: Vec<_> = multiproof
338                .storage_proofs
339                .into_iter()
340                .map(|(account, storage_proofs)| {
341                    let trie = self.storage.take_or_create_trie(&account);
342                    (account, storage_proofs, trie)
343                })
344                .par_bridge_buffered()
345                .map(|(account, storage_proofs, mut trie)| {
346                    let mut bufs = Vec::new();
347                    let result = Self::reveal_storage_v2_proof_nodes_inner(
348                        account,
349                        storage_proofs,
350                        &mut trie,
351                        &mut bufs,
352                        retain_updates,
353                    );
354                    (account, result, trie, bufs)
355                })
356                .collect();
357
358            let mut any_err = Ok(());
359            for (account, result, trie, bufs) in results {
360                self.storage.tries.insert(account, trie);
361                if let Ok(_total_nodes) = result {
362                    #[cfg(feature = "metrics")]
363                    {
364                        self.metrics.increment_total_storage_nodes(_total_nodes as u64);
365                    }
366                } else {
367                    any_err = result.map(|_| ());
368                }
369
370                // Keep buffers for deferred dropping
371                self.deferred_drops.proof_nodes_bufs.extend(bufs);
372            }
373
374            any_err
375        }
376    }
377
378    /// Reveals account proof nodes from a V2 proof.
379    ///
380    /// V2 proofs already include the masks in the `ProofTrieNode` structure,
381    /// so no separate masks map is needed.
382    pub fn reveal_account_v2_proof_nodes(
383        &mut self,
384        mut nodes: Vec<ProofTrieNodeV2>,
385    ) -> SparseStateTrieResult<()> {
386        let capacity = estimate_v2_proof_capacity(&nodes);
387
388        #[cfg(feature = "metrics")]
389        self.metrics.increment_total_account_nodes(nodes.len() as u64);
390
391        let root_node = nodes.iter().find(|n| n.path.is_empty());
392        let trie = if let Some(root_node) = root_node {
393            trace!(target: "trie::sparse", ?root_node, "Revealing root account node from V2 proof");
394            self.state.reveal_root(root_node.node.clone(), root_node.masks, self.retain_updates)?
395        } else {
396            self.state.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?
397        };
398        trie.reserve_nodes(capacity);
399        trace!(target: "trie::sparse", total_nodes = ?nodes.len(), "Revealing account nodes from V2 proof");
400        trie.reveal_nodes(&mut nodes)?;
401
402        self.deferred_drops.proof_nodes_bufs.push(nodes);
403        Ok(())
404    }
405
406    /// Reveals storage proof nodes from a V2 proof for the given address.
407    ///
408    /// V2 proofs already include the masks in the `ProofTrieNode` structure,
409    /// so no separate masks map is needed.
410    pub fn reveal_storage_v2_proof_nodes(
411        &mut self,
412        account: B256,
413        nodes: Vec<ProofTrieNodeV2>,
414    ) -> SparseStateTrieResult<()> {
415        let trie = self.storage.get_or_create_trie_mut(account);
416        let _total_nodes = Self::reveal_storage_v2_proof_nodes_inner(
417            account,
418            nodes,
419            trie,
420            &mut self.deferred_drops.proof_nodes_bufs,
421            self.retain_updates,
422        )?;
423
424        #[cfg(feature = "metrics")]
425        {
426            self.metrics.increment_total_storage_nodes(_total_nodes as u64);
427        }
428
429        Ok(())
430    }
431
432    /// Reveals storage V2 proof nodes for the given address. This is an internal static function
433    /// designed to handle a variety of associated public functions.
434    fn reveal_storage_v2_proof_nodes_inner(
435        account: B256,
436        mut nodes: Vec<ProofTrieNodeV2>,
437        trie: &mut RevealableSparseTrie<S>,
438        bufs: &mut Vec<Vec<ProofTrieNodeV2>>,
439        retain_updates: bool,
440    ) -> SparseStateTrieResult<usize> {
441        let capacity = estimate_v2_proof_capacity(&nodes);
442        let total_nodes = nodes.len();
443
444        let root_node = nodes.iter().find(|n| n.path.is_empty());
445        let trie = if let Some(root_node) = root_node {
446            trace!(target: "trie::sparse", ?account, ?root_node, "Revealing root storage node from V2 proof");
447            trie.reveal_root(root_node.node.clone(), root_node.masks, retain_updates)?
448        } else {
449            trie.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?
450        };
451        trie.reserve_nodes(capacity);
452        trace!(target: "trie::sparse", ?account, total_nodes, "Revealing storage nodes from V2 proof");
453        trie.reveal_nodes(&mut nodes)?;
454
455        bufs.push(nodes);
456        Ok(total_nodes)
457    }
458
459    /// Wipe the storage trie at the provided address.
460    pub fn wipe_storage(&mut self, address: B256) -> SparseStateTrieResult<()> {
461        if let Some(trie) = self.storage.tries.get_mut(&address) {
462            trie.wipe()?;
463        }
464        Ok(())
465    }
466
467    /// Calculates the hashes of subtries.
468    ///
469    /// If the trie has not been revealed, this function does nothing.
470    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
471    pub fn calculate_subtries(&mut self) {
472        if let RevealableSparseTrie::Revealed(trie) = &mut self.state {
473            trie.update_subtrie_hashes();
474        }
475    }
476
477    /// Returns storage sparse trie root if the trie has been revealed.
478    pub fn storage_root(&mut self, account: &B256) -> Option<B256> {
479        self.storage.tries.get_mut(account).and_then(|trie| trie.root())
480    }
481
482    /// Returns mutable reference to the revealed account sparse trie.
483    ///
484    /// If the trie is not revealed yet, its root will be revealed using the trie node provider.
485    fn revealed_trie_mut(
486        &mut self,
487        provider_factory: impl TrieNodeProviderFactory,
488    ) -> SparseStateTrieResult<&mut A> {
489        match self.state {
490            RevealableSparseTrie::Blind(_) => {
491                let (root_node, hash_mask, tree_mask) = provider_factory
492                    .account_node_provider()
493                    .trie_node(&Nibbles::default())?
494                    .map(|node| {
495                        TrieNodeV2::decode(&mut &node.node[..])
496                            .map(|decoded| (decoded, node.hash_mask, node.tree_mask))
497                    })
498                    .transpose()?
499                    .unwrap_or((TrieNodeV2::EmptyRoot, None, None));
500                let masks = BranchNodeMasks::from_optional(hash_mask, tree_mask);
501                self.state.reveal_root(root_node, masks, self.retain_updates).map_err(Into::into)
502            }
503            RevealableSparseTrie::Revealed(ref mut trie) => Ok(trie),
504        }
505    }
506
507    /// Returns sparse trie root.
508    ///
509    /// If the trie has not been revealed, this function reveals the root node and returns its hash.
510    pub fn root(
511        &mut self,
512        provider_factory: impl TrieNodeProviderFactory,
513    ) -> SparseStateTrieResult<B256> {
514        // record revealed node metrics
515        #[cfg(feature = "metrics")]
516        self.metrics.record();
517
518        Ok(self.revealed_trie_mut(provider_factory)?.root())
519    }
520
521    /// Returns sparse trie root and trie updates if the trie has been revealed.
522    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
523    pub fn root_with_updates(
524        &mut self,
525        provider_factory: impl TrieNodeProviderFactory,
526    ) -> SparseStateTrieResult<(B256, TrieUpdates)> {
527        // record revealed node metrics
528        #[cfg(feature = "metrics")]
529        self.metrics.record();
530
531        let storage_tries = self.storage_trie_updates();
532        let revealed = self.revealed_trie_mut(provider_factory)?;
533
534        let (root, updates) = (revealed.root(), revealed.take_updates());
535        let updates = TrieUpdates {
536            account_nodes: updates.updated_nodes,
537            removed_nodes: updates.removed_nodes,
538            storage_tries,
539        };
540        Ok((root, updates))
541    }
542
543    /// Returns storage trie updates for tries that have been revealed.
544    ///
545    /// Panics if any of the storage tries are not revealed.
546    pub fn storage_trie_updates(&mut self) -> B256Map<StorageTrieUpdates> {
547        self.storage
548            .tries
549            .iter_mut()
550            .map(|(address, trie)| {
551                let trie = trie.as_revealed_mut().unwrap();
552                let updates = trie.take_updates();
553                let updates = StorageTrieUpdates {
554                    is_deleted: updates.wiped,
555                    storage_nodes: updates.updated_nodes,
556                    removed_nodes: updates.removed_nodes,
557                };
558                (*address, updates)
559            })
560            .filter(|(_, updates)| !updates.is_empty())
561            .collect()
562    }
563
564    /// Returns [`TrieUpdates`] by taking the updates from the revealed sparse tries.
565    ///
566    /// Returns `None` if the accounts trie is not revealed.
567    pub fn take_trie_updates(&mut self) -> Option<TrieUpdates> {
568        let storage_tries = self.storage_trie_updates();
569        self.state.as_revealed_mut().map(|state| {
570            let updates = state.take_updates();
571            TrieUpdates {
572                account_nodes: updates.updated_nodes,
573                removed_nodes: updates.removed_nodes,
574                storage_tries,
575            }
576        })
577    }
578
579    /// Update the account leaf node.
580    pub fn update_account_leaf(
581        &mut self,
582        path: Nibbles,
583        value: Vec<u8>,
584        provider_factory: impl TrieNodeProviderFactory,
585    ) -> SparseStateTrieResult<()> {
586        let provider = provider_factory.account_node_provider();
587        self.state.update_leaf(path, value, provider)?;
588        Ok(())
589    }
590
591    /// Update the leaf node of a revealed storage trie at the provided address.
592    pub fn update_storage_leaf(
593        &mut self,
594        address: B256,
595        slot: Nibbles,
596        value: Vec<u8>,
597        provider_factory: impl TrieNodeProviderFactory,
598    ) -> SparseStateTrieResult<()> {
599        let provider = provider_factory.storage_node_provider(address);
600        self.storage
601            .tries
602            .get_mut(&address)
603            .ok_or(SparseTrieErrorKind::Blind)?
604            .update_leaf(slot, value, provider)?;
605        Ok(())
606    }
607
608    /// Update or remove trie account based on new account info. This method will either recompute
609    /// the storage root based on update storage trie or look it up from existing leaf value.
610    ///
611    /// Returns false if the new account info and storage trie are empty, indicating the account
612    /// leaf should be removed.
613    #[instrument(level = "trace", target = "trie::sparse", skip_all)]
614    pub fn update_account(
615        &mut self,
616        address: B256,
617        account: Account,
618        provider_factory: impl TrieNodeProviderFactory,
619    ) -> SparseStateTrieResult<bool> {
620        let storage_root = if let Some(storage_trie) = self.storage.tries.get_mut(&address) {
621            trace!(target: "trie::sparse", ?address, "Calculating storage root to update account");
622            storage_trie.root().ok_or(SparseTrieErrorKind::Blind)?
623        } else if self.is_account_revealed(address) {
624            trace!(target: "trie::sparse", ?address, "Retrieving storage root from account leaf to update account");
625            // The account was revealed, either...
626            if let Some(value) = self.get_account_value(&address) {
627                // ..it exists and we should take its current storage root or...
628                TrieAccount::decode(&mut &value[..])?.storage_root
629            } else {
630                // ...the account is newly created and the storage trie is empty.
631                EMPTY_ROOT_HASH
632            }
633        } else {
634            return Err(SparseTrieErrorKind::Blind.into())
635        };
636
637        if account.is_empty() && storage_root == EMPTY_ROOT_HASH {
638            return Ok(false);
639        }
640
641        trace!(target: "trie::sparse", ?address, "Updating account");
642        let nibbles = Nibbles::unpack(address);
643        self.account_rlp_buf.clear();
644        account.into_trie_account(storage_root).encode(&mut self.account_rlp_buf);
645        self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?;
646
647        Ok(true)
648    }
649
650    /// Update or remove a trie account for stateless validation.
651    ///
652    /// Unlike [`Self::update_account`], this method:
653    /// - Accepts `Option<Account>` — `None` removes the account leaf.
654    /// - Falls back to [`EMPTY_ROOT_HASH`] for unrevealed accounts instead of returning
655    ///   [`SparseTrieErrorKind::Blind`], since stateless validation operates with a sparse witness
656    ///   where not all accounts are revealed.
657    #[instrument(level = "trace", target = "trie::sparse", skip_all)]
658    pub fn update_account_stateless(
659        &mut self,
660        address: B256,
661        account: Option<Account>,
662        provider_factory: impl TrieNodeProviderFactory,
663    ) -> SparseStateTrieResult<()> {
664        let nibbles = Nibbles::unpack(address);
665
666        let Some(account) = account else {
667            trace!(target: "trie::sparse", ?address, "Removing account");
668            return self.remove_account_leaf(&nibbles, provider_factory);
669        };
670
671        let storage_root = if let Some(storage_trie) = self.storage.tries.get_mut(&address) {
672            trace!(target: "trie::sparse", ?address, "Calculating storage root to update account");
673            storage_trie.root().ok_or(SparseTrieErrorKind::Blind)?
674        } else if let Some(value) = self.get_account_value(&address) {
675            trace!(target: "trie::sparse", ?address, "Retrieving storage root from account leaf to update account");
676            TrieAccount::decode(&mut &value[..])?.storage_root
677        } else {
678            EMPTY_ROOT_HASH
679        };
680
681        trace!(target: "trie::sparse", ?address, "Updating account");
682        self.account_rlp_buf.clear();
683        account.into_trie_account(storage_root).encode(&mut self.account_rlp_buf);
684        self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?;
685
686        Ok(())
687    }
688
689    /// Update the storage root of a revealed account.
690    ///
691    /// If the account doesn't exist in the trie, the function is a no-op.
692    ///
693    /// Returns false if the new storage root is empty, and the account info was already empty,
694    /// indicating the account leaf should be removed.
695    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
696    pub fn update_account_storage_root(
697        &mut self,
698        address: B256,
699        provider_factory: impl TrieNodeProviderFactory,
700    ) -> SparseStateTrieResult<bool> {
701        if !self.is_account_revealed(address) {
702            return Err(SparseTrieErrorKind::Blind.into())
703        }
704
705        // Nothing to update if the account doesn't exist in the trie.
706        let Some(mut trie_account) = self
707            .get_account_value(&address)
708            .map(|v| TrieAccount::decode(&mut &v[..]))
709            .transpose()?
710        else {
711            trace!(target: "trie::sparse", ?address, "Account not found in trie, skipping storage root update");
712            return Ok(true)
713        };
714
715        // Calculate the new storage root. If the storage trie doesn't exist, the storage root will
716        // be empty.
717        let storage_root = if let Some(storage_trie) = self.storage.tries.get_mut(&address) {
718            trace!(target: "trie::sparse", ?address, "Calculating storage root to update account");
719            storage_trie.root().ok_or(SparseTrieErrorKind::Blind)?
720        } else {
721            EMPTY_ROOT_HASH
722        };
723
724        // Update the account with the new storage root.
725        trie_account.storage_root = storage_root;
726
727        // If the account is empty, indicate that it should be removed.
728        if trie_account == TrieAccount::default() {
729            return Ok(false)
730        }
731
732        // Otherwise, update the account leaf.
733        trace!(target: "trie::sparse", ?address, "Updating account with the new storage root");
734        let nibbles = Nibbles::unpack(address);
735        self.account_rlp_buf.clear();
736        trie_account.encode(&mut self.account_rlp_buf);
737        self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?;
738
739        Ok(true)
740    }
741
742    /// Remove the account leaf node.
743    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
744    pub fn remove_account_leaf(
745        &mut self,
746        path: &Nibbles,
747        provider_factory: impl TrieNodeProviderFactory,
748    ) -> SparseStateTrieResult<()> {
749        let provider = provider_factory.account_node_provider();
750        self.state.remove_leaf(path, provider)?;
751        Ok(())
752    }
753
754    /// Update the leaf node of a storage trie at the provided address.
755    pub fn remove_storage_leaf(
756        &mut self,
757        address: B256,
758        slot: &Nibbles,
759        provider_factory: impl TrieNodeProviderFactory,
760    ) -> SparseStateTrieResult<()> {
761        let storage_trie =
762            self.storage.tries.get_mut(&address).ok_or(SparseTrieErrorKind::Blind)?;
763
764        let provider = provider_factory.storage_node_provider(address);
765        storage_trie.remove_leaf(slot, provider)?;
766        Ok(())
767    }
768}
769
770impl<A, S> SparseStateTrie<A, S>
771where
772    A: SparseTrieTrait + Default,
773    S: SparseTrieTrait + Default + Clone,
774{
775    /// Clears all trie data while preserving allocations for reuse.
776    ///
777    /// This resets the trie to an empty state but keeps the underlying memory allocations,
778    /// which can significantly reduce allocation overhead when the trie is reused.
779    pub fn clear(&mut self) {
780        self.state.clear();
781        self.storage.clear();
782        self.account_rlp_buf.clear();
783    }
784
785    /// Returns a heuristic for the total in-memory size of this state trie in bytes.
786    ///
787    /// This aggregates the memory usage of the account trie, all revealed storage tries
788    /// (including cleared ones retained for allocation reuse), and auxiliary data structures.
789    pub fn memory_size(&self) -> usize {
790        let mut size = core::mem::size_of::<Self>();
791
792        size += match &self.state {
793            RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
794                t.memory_size()
795            }
796            RevealableSparseTrie::Blind(None) => 0,
797        };
798
799        for trie in self.storage.tries.values() {
800            size += match trie {
801                RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
802                    t.memory_size()
803                }
804                RevealableSparseTrie::Blind(None) => 0,
805            };
806        }
807        for trie in &self.storage.cleared_tries {
808            size += match trie {
809                RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
810                    t.memory_size()
811                }
812                RevealableSparseTrie::Blind(None) => 0,
813            };
814        }
815
816        size
817    }
818
819    /// Returns the number of storage tries currently retained (active + cleared).
820    pub fn retained_storage_tries_count(&self) -> usize {
821        self.storage.tries.len() + self.storage.cleared_tries.len()
822    }
823
824    /// Shrinks the capacity of the sparse trie to the given node and value sizes.
825    ///
826    /// This helps reduce memory usage when the trie has excess capacity.
827    /// Distributes capacity equally among all tries (account + storage).
828    pub fn shrink_to(&mut self, max_nodes: usize, max_values: usize) {
829        // Count total number of storage tries (active + cleared)
830        let storage_tries_count = self.storage.tries.len() + self.storage.cleared_tries.len();
831
832        // Total tries = 1 account trie + all storage tries
833        let total_tries = 1 + storage_tries_count;
834
835        // Distribute capacity equally among all tries
836        let nodes_per_trie = max_nodes / total_tries;
837        let values_per_trie = max_values / total_tries;
838
839        // Shrink the account trie
840        self.state.shrink_nodes_to(nodes_per_trie);
841        self.state.shrink_values_to(values_per_trie);
842
843        // Give storage tries the remaining capacity after account trie allocation
844        let storage_nodes = max_nodes.saturating_sub(nodes_per_trie);
845        let storage_values = max_values.saturating_sub(values_per_trie);
846
847        // Shrink all storage tries (they will redistribute internally)
848        self.storage.shrink_to(storage_nodes, storage_values);
849    }
850
851    /// Prunes account/storage tries according to global LFU retention.
852    ///
853    /// - Top LFU `(address, slot)` entries are retained up to `max_hot_slots`.
854    ///
855    /// - Top LFU `(address, slot)` entries are retained in storage tries.
856    /// - Account trie retains only paths for accounts tracked by the account LFU.
857    /// - Storage tries retain only paths needed for retained slots.
858    /// - All other revealed paths are pruned to hash stubs or fully evicted.
859    ///
860    /// # Preconditions
861    ///
862    /// All revealed account and storage tries must already have computed hashes via `root()`
863    /// / `storage_root()` for their current state. Pruning a dirty revealed trie is a hard
864    /// error and may panic.
865    #[cfg(feature = "std")]
866    #[instrument(
867        level = "debug",
868        name = "SparseStateTrie::prune",
869        target = "trie::sparse",
870        skip_all,
871        fields(%max_hot_slots, %max_hot_accounts)
872    )]
873    pub fn prune(&mut self, max_hot_slots: usize, max_hot_accounts: usize) {
874        self.hot_slots_lfu.decay_and_evict(max_hot_slots);
875        self.hot_accounts_lfu.decay_and_evict(max_hot_accounts);
876        let retained = self.hot_slots_lfu.retained_slots_by_address();
877
878        let retained_account_paths: Vec<Nibbles> =
879            self.hot_accounts_lfu.keys().map(|k| Nibbles::unpack(*k)).collect();
880
881        let retained_accounts = retained_account_paths.len();
882        let retained_storage_tries = retained.len();
883        let total_storage_tries_before = self.storage.tries.len();
884
885        // Prune account and storage tries in parallel using the same LFU-selected set.
886        let (account_nodes_pruned, storage_tries_evicted) = rayon::join(
887            || {
888                self.state
889                    .as_revealed_mut()
890                    .map(|trie| trie.prune(&retained_account_paths))
891                    .unwrap_or(0)
892            },
893            || self.storage.prune_by_retained_slots(retained),
894        );
895
896        debug!(
897            target: "trie::sparse",
898            retained_accounts,
899            retained_storage_tries,
900            account_nodes_pruned,
901            storage_tries_evicted,
902            storage_tries_after = total_storage_tries_before - storage_tries_evicted,
903            "SparseStateTrie::prune completed"
904        );
905    }
906
907    /// Commits the [`TrieUpdates`] to the sparse trie.
908    pub fn commit_updates(&mut self, updates: &TrieUpdates) {
909        if let Some(trie) = self.state.as_revealed_mut() {
910            trie.commit_updates(&updates.account_nodes, &updates.removed_nodes);
911        }
912        for (address, updates) in &updates.storage_tries {
913            if let Some(trie) =
914                self.storage.tries.get_mut(address).and_then(|t| t.as_revealed_mut())
915            {
916                trie.commit_updates(&updates.storage_nodes, &updates.removed_nodes);
917            }
918        }
919    }
920}
921
922/// The fields of [`SparseStateTrie`] related to storage tries. This is kept separate from the rest
923/// of [`SparseStateTrie`] to help enforce allocation re-use.
924#[derive(Debug, Default)]
925struct StorageTries<S = ParallelSparseTrie> {
926    /// Sparse storage tries.
927    tries: B256Map<RevealableSparseTrie<S>>,
928    /// Cleared storage tries, kept for re-use.
929    cleared_tries: Vec<RevealableSparseTrie<S>>,
930    /// A default cleared trie instance, which will be cloned when creating new tries.
931    default_trie: RevealableSparseTrie<S>,
932}
933
934#[cfg(feature = "std")]
935impl<S: SparseTrieTrait> StorageTries<S> {
936    /// Prunes storage tries using LFU-retained slots.
937    ///
938    /// Tries without retained slots are evicted entirely. Tries with retained slots are pruned to
939    /// those slots.
940    fn prune_by_retained_slots(&mut self, retained_slots: B256Map<Vec<Nibbles>>) -> usize {
941        // Parallel pass: prune retained tries and clear evicted ones in place.
942        {
943            use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
944            self.tries.par_iter_mut().for_each(|(address, trie)| {
945                if let Some(slots) = retained_slots.get(address) {
946                    if let Some(t) = trie.as_revealed_mut() {
947                        t.prune(slots);
948                    }
949                } else {
950                    trie.clear();
951                }
952            });
953        }
954
955        // Cheap sequential drain: move already-cleared tries into the reuse pool.
956        let addresses_to_evict: Vec<B256> = self
957            .tries
958            .keys()
959            .filter(|address| !retained_slots.contains_key(*address))
960            .copied()
961            .collect();
962
963        let evicted = addresses_to_evict.len();
964        self.cleared_tries.reserve(evicted);
965        for address in &addresses_to_evict {
966            if let Some(trie) = self.tries.remove(address) {
967                self.cleared_tries.push(trie);
968            }
969        }
970
971        evicted
972    }
973}
974
975impl<S: SparseTrieTrait> StorageTries<S> {
976    /// Returns all fields to a cleared state, equivalent to the default state, keeping cleared
977    /// collections for re-use later when possible.
978    fn clear(&mut self) {
979        self.cleared_tries.extend(self.tries.drain().map(|(_, mut trie)| {
980            trie.clear();
981            trie
982        }));
983    }
984
985    /// Shrinks the capacity of all storage tries to the given total sizes.
986    ///
987    /// Distributes capacity equally among all tries (active + cleared).
988    fn shrink_to(&mut self, max_nodes: usize, max_values: usize) {
989        let total_tries = self.tries.len() + self.cleared_tries.len();
990        if total_tries == 0 {
991            return;
992        }
993
994        // Distribute capacity equally among all tries
995        let nodes_per_trie = max_nodes / total_tries;
996        let values_per_trie = max_values / total_tries;
997
998        // Shrink active storage tries
999        for trie in self.tries.values_mut() {
1000            trie.shrink_nodes_to(nodes_per_trie);
1001            trie.shrink_values_to(values_per_trie);
1002        }
1003
1004        // Shrink cleared storage tries
1005        for trie in &mut self.cleared_tries {
1006            trie.shrink_nodes_to(nodes_per_trie);
1007            trie.shrink_values_to(values_per_trie);
1008        }
1009    }
1010}
1011
1012impl<S: SparseTrieTrait + Clone> StorageTries<S> {
1013    // Returns mutable reference to storage sparse trie, creating a blind one if it doesn't exist.
1014    fn get_or_create_trie_mut(&mut self, address: B256) -> &mut RevealableSparseTrie<S> {
1015        self.tries.entry(address).or_insert_with(|| {
1016            self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone())
1017        })
1018    }
1019
1020    /// Takes the storage trie for the account from the internal `HashMap`, creating it if it
1021    /// doesn't already exist.
1022    #[cfg(feature = "std")]
1023    fn take_or_create_trie(&mut self, account: &B256) -> RevealableSparseTrie<S> {
1024        self.tries.remove(account).unwrap_or_else(|| {
1025            self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone())
1026        })
1027    }
1028}
1029
1030/// Key for identifying a storage slot in the global LFU cache.
1031#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1032struct HotSlotKey {
1033    address: B256,
1034    slot: B256,
1035}
1036
1037/// Slot-specific helpers for the `BucketedLfu<HotSlotKey>`.
1038#[cfg(feature = "std")]
1039impl BucketedLfu<HotSlotKey> {
1040    /// Returns retained slots grouped by address.
1041    fn retained_slots_by_address(&self) -> B256Map<Vec<Nibbles>> {
1042        let mut grouped =
1043            B256Map::<Vec<Nibbles>>::with_capacity_and_hasher(self.len(), Default::default());
1044        for key in self.keys() {
1045            grouped.entry(key.address).or_default().push(Nibbles::unpack(key.slot));
1046        }
1047
1048        for slots in grouped.values_mut() {
1049            slots.sort_unstable();
1050            slots.dedup();
1051        }
1052
1053        grouped
1054    }
1055}
1056
1057/// Calculates capacity estimation for V2 proof nodes.
1058///
1059/// This counts nodes and their children (for branch and extension nodes) to provide
1060/// proper capacity hints for `reserve_nodes`.
1061fn estimate_v2_proof_capacity(nodes: &[ProofTrieNodeV2]) -> usize {
1062    let mut capacity = nodes.len();
1063
1064    for node in nodes {
1065        if let TrieNodeV2::Branch(branch) = &node.node {
1066            capacity += branch.state_mask.count_ones() as usize;
1067        }
1068    }
1069
1070    capacity
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075    use super::*;
1076    use crate::{provider::DefaultTrieNodeProviderFactory, LeafLookup, ParallelSparseTrie};
1077    use alloy_primitives::{
1078        b256,
1079        map::{HashMap, HashSet},
1080        U256,
1081    };
1082    use arbitrary::Arbitrary;
1083    use rand::{rngs::StdRng, Rng, SeedableRng};
1084    use reth_primitives_traits::Account;
1085    use reth_trie::{updates::StorageTrieUpdates, HashBuilder, MultiProof, EMPTY_ROOT_HASH};
1086    use reth_trie_common::{
1087        proof::{ProofNodes, ProofRetainer},
1088        BranchNodeMasks, BranchNodeMasksMap, BranchNodeV2, LeafNode, RlpNode, StorageMultiProof,
1089        TrieMask,
1090    };
1091
1092    /// Create a leaf key (suffix) with given nibbles padded with zeros to reach `total_len`.
1093    fn leaf_key(suffix: impl AsRef<[u8]>, total_len: usize) -> Nibbles {
1094        let suffix = suffix.as_ref();
1095        let mut nibbles = Nibbles::from_nibbles(suffix);
1096        nibbles.extend(&Nibbles::from_nibbles_unchecked(vec![0; total_len - suffix.len()]));
1097        nibbles
1098    }
1099
1100    #[test]
1101    fn reveal_account_path_twice() {
1102        let provider_factory = DefaultTrieNodeProviderFactory;
1103        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1104
1105        // Full 64-nibble paths
1106        let full_path_0 = leaf_key([0x0], 64);
1107        let _full_path_1 = leaf_key([0x1], 64);
1108
1109        let leaf_value = alloy_rlp::encode(TrieAccount::default());
1110        // Leaf key is 63 nibbles (suffix after 1-nibble node path)
1111        let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1112            leaf_key([], 63),
1113            leaf_value.clone(),
1114        )));
1115        let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1116            leaf_key([], 63),
1117            leaf_value.clone(),
1118        )));
1119
1120        let multiproof = MultiProof {
1121            account_subtree: ProofNodes::from_iter([
1122                (
1123                    Nibbles::default(),
1124                    alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
1125                        key: Nibbles::default(),
1126                        stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
1127                        state_mask: TrieMask::new(0b11),
1128                        branch_rlp_node: None,
1129                    }))
1130                    .into(),
1131                ),
1132                (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
1133                (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
1134            ]),
1135            ..Default::default()
1136        };
1137
1138        // Reveal multiproof and check that the state trie contains the leaf node and value
1139        sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
1140        assert!(matches!(
1141            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
1142            Ok(LeafLookup::Exists)
1143        ));
1144        assert_eq!(
1145            sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
1146            Some(&leaf_value)
1147        );
1148
1149        // Remove the leaf node and check that the state trie does not contain the leaf node and
1150        // value
1151        sparse.remove_account_leaf(&full_path_0, &provider_factory).unwrap();
1152        assert!(matches!(
1153            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
1154            Ok(LeafLookup::NonExistent)
1155        ));
1156        assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
1157    }
1158
1159    #[test]
1160    fn reveal_storage_path_twice() {
1161        let provider_factory = DefaultTrieNodeProviderFactory;
1162        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1163
1164        // Full 64-nibble path
1165        let full_path_0 = leaf_key([0x0], 64);
1166
1167        let leaf_value = alloy_rlp::encode(TrieAccount::default());
1168        let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1169            leaf_key([], 63),
1170            leaf_value.clone(),
1171        )));
1172        let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1173            leaf_key([], 63),
1174            leaf_value.clone(),
1175        )));
1176
1177        let multiproof = MultiProof {
1178            storages: HashMap::from_iter([(
1179                B256::ZERO,
1180                StorageMultiProof {
1181                    root: B256::ZERO,
1182                    subtree: ProofNodes::from_iter([
1183                        (
1184                            Nibbles::default(),
1185                            alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
1186                                key: Nibbles::default(),
1187                                stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
1188                                state_mask: TrieMask::new(0b11),
1189                                branch_rlp_node: None,
1190                            }))
1191                            .into(),
1192                        ),
1193                        (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
1194                        (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
1195                    ]),
1196                    branch_node_masks: Default::default(),
1197                },
1198            )]),
1199            ..Default::default()
1200        };
1201
1202        // Reveal multiproof and check that the storage trie contains the leaf node and value
1203        sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
1204        assert!(matches!(
1205            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
1206            Ok(LeafLookup::Exists)
1207        ));
1208        assert_eq!(
1209            sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
1210            Some(&leaf_value)
1211        );
1212
1213        // Remove the leaf node and check that the storage trie does not contain the leaf node and
1214        // value
1215        sparse.remove_storage_leaf(B256::ZERO, &full_path_0, &provider_factory).unwrap();
1216        assert!(matches!(
1217            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
1218            Ok(LeafLookup::NonExistent)
1219        ));
1220        assert!(sparse
1221            .storage_trie_ref(&B256::ZERO)
1222            .unwrap()
1223            .get_leaf_value(&full_path_0)
1224            .is_none());
1225    }
1226
1227    #[test]
1228    fn seeded_hot_cache_capacities_preserve_first_cycle_touches() {
1229        let account = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1230        let slot = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1231        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1232
1233        sparse.set_hot_cache_capacities(1, 1);
1234        sparse.record_account_touch(account);
1235        sparse.record_slot_touch(account, slot);
1236        sparse.prune(1, 1);
1237
1238        assert_eq!(sparse.hot_accounts_lfu.keys().copied().collect::<Vec<_>>(), vec![account]);
1239        assert_eq!(
1240            sparse.hot_slots_lfu.keys().copied().collect::<Vec<_>>(),
1241            vec![HotSlotKey { address: account, slot }]
1242        );
1243    }
1244
1245    #[test]
1246    fn reveal_v2_proof_nodes() {
1247        let provider_factory = DefaultTrieNodeProviderFactory;
1248        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1249
1250        // Full 64-nibble path
1251        let full_path_0 = leaf_key([0x0], 64);
1252
1253        let leaf_value = alloy_rlp::encode(TrieAccount::default());
1254        let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
1255        let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
1256
1257        let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
1258            key: Nibbles::default(),
1259            stack: vec![
1260                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
1261                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
1262            ],
1263            state_mask: TrieMask::new(0b11),
1264            branch_rlp_node: None,
1265        });
1266
1267        // Create V2 proof nodes with masks already included
1268        let v2_proof_nodes = vec![
1269            ProofTrieNodeV2 {
1270                path: Nibbles::default(),
1271                node: branch_node,
1272                masks: Some(BranchNodeMasks {
1273                    hash_mask: TrieMask::default(),
1274                    tree_mask: TrieMask::default(),
1275                }),
1276            },
1277            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
1278            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
1279        ];
1280
1281        // Reveal V2 proof nodes
1282        sparse.reveal_account_v2_proof_nodes(v2_proof_nodes).unwrap();
1283
1284        // Check that the state trie contains the leaf node and value
1285        assert!(matches!(
1286            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
1287            Ok(LeafLookup::Exists)
1288        ));
1289        assert_eq!(
1290            sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
1291            Some(&leaf_value)
1292        );
1293
1294        // Remove the leaf node
1295        sparse.remove_account_leaf(&full_path_0, &provider_factory).unwrap();
1296        assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
1297    }
1298
1299    #[test]
1300    fn reveal_storage_v2_proof_nodes() {
1301        let provider_factory = DefaultTrieNodeProviderFactory;
1302        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1303
1304        // Full 64-nibble path
1305        let full_path_0 = leaf_key([0x0], 64);
1306
1307        let storage_value: Vec<u8> = alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec();
1308        let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
1309        let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
1310
1311        let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
1312            key: Nibbles::default(),
1313            stack: vec![
1314                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
1315                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
1316            ],
1317            state_mask: TrieMask::new(0b11),
1318            branch_rlp_node: None,
1319        });
1320
1321        let v2_proof_nodes = vec![
1322            ProofTrieNodeV2 { path: Nibbles::default(), node: branch_node, masks: None },
1323            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
1324            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
1325        ];
1326
1327        // Reveal V2 storage proof nodes for account
1328        sparse.reveal_storage_v2_proof_nodes(B256::ZERO, v2_proof_nodes).unwrap();
1329
1330        // Check that the storage trie contains the leaf node and value
1331        assert!(matches!(
1332            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
1333            Ok(LeafLookup::Exists)
1334        ));
1335        assert_eq!(
1336            sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
1337            Some(&storage_value)
1338        );
1339
1340        // Remove the leaf node
1341        sparse.remove_storage_leaf(B256::ZERO, &full_path_0, &provider_factory).unwrap();
1342        assert!(sparse
1343            .storage_trie_ref(&B256::ZERO)
1344            .unwrap()
1345            .get_leaf_value(&full_path_0)
1346            .is_none());
1347    }
1348
1349    #[test]
1350    fn take_trie_updates() {
1351        reth_tracing::init_test_tracing();
1352
1353        // let mut rng = generators::rng();
1354        let mut rng = StdRng::seed_from_u64(1);
1355
1356        let mut bytes = [0u8; 1024];
1357        rng.fill(bytes.as_mut_slice());
1358
1359        let slot_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1360        let slot_path_1 = Nibbles::unpack(slot_1);
1361        let value_1 = U256::from(rng.random::<u64>());
1362        let slot_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1363        let slot_path_2 = Nibbles::unpack(slot_2);
1364        let value_2 = U256::from(rng.random::<u64>());
1365        let slot_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1366        let slot_path_3 = Nibbles::unpack(slot_3);
1367        let value_3 = U256::from(rng.random::<u64>());
1368
1369        let mut storage_hash_builder = HashBuilder::default()
1370            .with_proof_retainer(ProofRetainer::from_iter([slot_path_1, slot_path_2]));
1371        storage_hash_builder.add_leaf(slot_path_1, &alloy_rlp::encode_fixed_size(&value_1));
1372        storage_hash_builder.add_leaf(slot_path_2, &alloy_rlp::encode_fixed_size(&value_2));
1373
1374        let storage_root = storage_hash_builder.root();
1375        let storage_proof_nodes = storage_hash_builder.take_proof_nodes();
1376        let storage_branch_node_masks = BranchNodeMasksMap::from_iter([
1377            (
1378                Nibbles::default(),
1379                BranchNodeMasks { hash_mask: TrieMask::new(0b010), tree_mask: TrieMask::default() },
1380            ),
1381            (
1382                Nibbles::from_nibbles([0x1]),
1383                BranchNodeMasks { hash_mask: TrieMask::new(0b11), tree_mask: TrieMask::default() },
1384            ),
1385        ]);
1386
1387        let address_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1388        let address_path_1 = Nibbles::unpack(address_1);
1389        let account_1 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1390        let mut trie_account_1 = account_1.into_trie_account(storage_root);
1391        let address_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1392        let address_path_2 = Nibbles::unpack(address_2);
1393        let account_2 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1394        let mut trie_account_2 = account_2.into_trie_account(EMPTY_ROOT_HASH);
1395
1396        let mut hash_builder = HashBuilder::default()
1397            .with_proof_retainer(ProofRetainer::from_iter([address_path_1, address_path_2]));
1398        hash_builder.add_leaf(address_path_1, &alloy_rlp::encode(trie_account_1));
1399        hash_builder.add_leaf(address_path_2, &alloy_rlp::encode(trie_account_2));
1400
1401        let root = hash_builder.root();
1402        let proof_nodes = hash_builder.take_proof_nodes();
1403
1404        let provider_factory = DefaultTrieNodeProviderFactory;
1405        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default().with_updates(true);
1406        sparse
1407            .reveal_decoded_multiproof(
1408                MultiProof {
1409                    account_subtree: proof_nodes,
1410                    branch_node_masks: BranchNodeMasksMap::from_iter([(
1411                        Nibbles::from_nibbles([0x1]),
1412                        BranchNodeMasks {
1413                            hash_mask: TrieMask::new(0b00),
1414                            tree_mask: TrieMask::default(),
1415                        },
1416                    )]),
1417                    storages: HashMap::from_iter([
1418                        (
1419                            address_1,
1420                            StorageMultiProof {
1421                                root,
1422                                subtree: storage_proof_nodes.clone(),
1423                                branch_node_masks: storage_branch_node_masks.clone(),
1424                            },
1425                        ),
1426                        (
1427                            address_2,
1428                            StorageMultiProof {
1429                                root,
1430                                subtree: storage_proof_nodes,
1431                                branch_node_masks: storage_branch_node_masks,
1432                            },
1433                        ),
1434                    ]),
1435                }
1436                .try_into()
1437                .unwrap(),
1438            )
1439            .unwrap();
1440
1441        assert_eq!(sparse.root(&provider_factory).unwrap(), root);
1442
1443        let address_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1444        let address_path_3 = Nibbles::unpack(address_3);
1445        let account_3 = Account { nonce: account_1.nonce + 1, ..account_1 };
1446        let trie_account_3 = account_3.into_trie_account(EMPTY_ROOT_HASH);
1447
1448        sparse
1449            .update_account_leaf(
1450                address_path_3,
1451                alloy_rlp::encode(trie_account_3),
1452                &provider_factory,
1453            )
1454            .unwrap();
1455
1456        sparse
1457            .update_storage_leaf(
1458                address_1,
1459                slot_path_3,
1460                alloy_rlp::encode(value_3),
1461                &provider_factory,
1462            )
1463            .unwrap();
1464        trie_account_1.storage_root = sparse.storage_root(&address_1).unwrap();
1465        sparse
1466            .update_account_leaf(
1467                address_path_1,
1468                alloy_rlp::encode(trie_account_1),
1469                &provider_factory,
1470            )
1471            .unwrap();
1472
1473        sparse.wipe_storage(address_2).unwrap();
1474        trie_account_2.storage_root = sparse.storage_root(&address_2).unwrap();
1475        sparse
1476            .update_account_leaf(
1477                address_path_2,
1478                alloy_rlp::encode(trie_account_2),
1479                &provider_factory,
1480            )
1481            .unwrap();
1482
1483        sparse.root(&provider_factory).unwrap();
1484
1485        let sparse_updates = sparse.take_trie_updates().unwrap();
1486        // TODO(alexey): assert against real state root calculation updates
1487        pretty_assertions::assert_eq!(
1488            sparse_updates,
1489            TrieUpdates {
1490                account_nodes: HashMap::default(),
1491                storage_tries: HashMap::from_iter([(
1492                    b256!("0x1100000000000000000000000000000000000000000000000000000000000000"),
1493                    StorageTrieUpdates {
1494                        is_deleted: true,
1495                        storage_nodes: HashMap::default(),
1496                        removed_nodes: HashSet::default()
1497                    }
1498                )]),
1499                removed_nodes: HashSet::default()
1500            }
1501        );
1502    }
1503}