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 the storage root of a revealed account.
651    ///
652    /// If the account doesn't exist in the trie, the function is a no-op.
653    ///
654    /// Returns false if the new storage root is empty, and the account info was already empty,
655    /// indicating the account leaf should be removed.
656    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
657    pub fn update_account_storage_root(
658        &mut self,
659        address: B256,
660        provider_factory: impl TrieNodeProviderFactory,
661    ) -> SparseStateTrieResult<bool> {
662        if !self.is_account_revealed(address) {
663            return Err(SparseTrieErrorKind::Blind.into())
664        }
665
666        // Nothing to update if the account doesn't exist in the trie.
667        let Some(mut trie_account) = self
668            .get_account_value(&address)
669            .map(|v| TrieAccount::decode(&mut &v[..]))
670            .transpose()?
671        else {
672            trace!(target: "trie::sparse", ?address, "Account not found in trie, skipping storage root update");
673            return Ok(true)
674        };
675
676        // Calculate the new storage root. If the storage trie doesn't exist, the storage root will
677        // be empty.
678        let storage_root = if let Some(storage_trie) = self.storage.tries.get_mut(&address) {
679            trace!(target: "trie::sparse", ?address, "Calculating storage root to update account");
680            storage_trie.root().ok_or(SparseTrieErrorKind::Blind)?
681        } else {
682            EMPTY_ROOT_HASH
683        };
684
685        // Update the account with the new storage root.
686        trie_account.storage_root = storage_root;
687
688        // If the account is empty, indicate that it should be removed.
689        if trie_account == TrieAccount::default() {
690            return Ok(false)
691        }
692
693        // Otherwise, update the account leaf.
694        trace!(target: "trie::sparse", ?address, "Updating account with the new storage root");
695        let nibbles = Nibbles::unpack(address);
696        self.account_rlp_buf.clear();
697        trie_account.encode(&mut self.account_rlp_buf);
698        self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?;
699
700        Ok(true)
701    }
702
703    /// Remove the account leaf node.
704    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
705    pub fn remove_account_leaf(
706        &mut self,
707        path: &Nibbles,
708        provider_factory: impl TrieNodeProviderFactory,
709    ) -> SparseStateTrieResult<()> {
710        let provider = provider_factory.account_node_provider();
711        self.state.remove_leaf(path, provider)?;
712        Ok(())
713    }
714
715    /// Update the leaf node of a storage trie at the provided address.
716    pub fn remove_storage_leaf(
717        &mut self,
718        address: B256,
719        slot: &Nibbles,
720        provider_factory: impl TrieNodeProviderFactory,
721    ) -> SparseStateTrieResult<()> {
722        let storage_trie =
723            self.storage.tries.get_mut(&address).ok_or(SparseTrieErrorKind::Blind)?;
724
725        let provider = provider_factory.storage_node_provider(address);
726        storage_trie.remove_leaf(slot, provider)?;
727        Ok(())
728    }
729}
730
731impl<A, S> SparseStateTrie<A, S>
732where
733    A: SparseTrieTrait + Default,
734    S: SparseTrieTrait + Default + Clone,
735{
736    /// Clears all trie data while preserving allocations for reuse.
737    ///
738    /// This resets the trie to an empty state but keeps the underlying memory allocations,
739    /// which can significantly reduce allocation overhead when the trie is reused.
740    pub fn clear(&mut self) {
741        self.state.clear();
742        self.storage.clear();
743        self.account_rlp_buf.clear();
744    }
745
746    /// Returns a heuristic for the total in-memory size of this state trie in bytes.
747    ///
748    /// This aggregates the memory usage of the account trie, all revealed storage tries
749    /// (including cleared ones retained for allocation reuse), and auxiliary data structures.
750    pub fn memory_size(&self) -> usize {
751        let mut size = core::mem::size_of::<Self>();
752
753        size += match &self.state {
754            RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
755                t.memory_size()
756            }
757            RevealableSparseTrie::Blind(None) => 0,
758        };
759
760        for trie in self.storage.tries.values() {
761            size += match trie {
762                RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
763                    t.memory_size()
764                }
765                RevealableSparseTrie::Blind(None) => 0,
766            };
767        }
768        for trie in &self.storage.cleared_tries {
769            size += match trie {
770                RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => {
771                    t.memory_size()
772                }
773                RevealableSparseTrie::Blind(None) => 0,
774            };
775        }
776
777        size
778    }
779
780    /// Returns the number of storage tries currently retained (active + cleared).
781    pub fn retained_storage_tries_count(&self) -> usize {
782        self.storage.tries.len() + self.storage.cleared_tries.len()
783    }
784
785    /// Shrinks the capacity of the sparse trie to the given node and value sizes.
786    ///
787    /// This helps reduce memory usage when the trie has excess capacity.
788    /// Distributes capacity equally among all tries (account + storage).
789    pub fn shrink_to(&mut self, max_nodes: usize, max_values: usize) {
790        // Count total number of storage tries (active + cleared)
791        let storage_tries_count = self.storage.tries.len() + self.storage.cleared_tries.len();
792
793        // Total tries = 1 account trie + all storage tries
794        let total_tries = 1 + storage_tries_count;
795
796        // Distribute capacity equally among all tries
797        let nodes_per_trie = max_nodes / total_tries;
798        let values_per_trie = max_values / total_tries;
799
800        // Shrink the account trie
801        self.state.shrink_nodes_to(nodes_per_trie);
802        self.state.shrink_values_to(values_per_trie);
803
804        // Give storage tries the remaining capacity after account trie allocation
805        let storage_nodes = max_nodes.saturating_sub(nodes_per_trie);
806        let storage_values = max_values.saturating_sub(values_per_trie);
807
808        // Shrink all storage tries (they will redistribute internally)
809        self.storage.shrink_to(storage_nodes, storage_values);
810    }
811
812    /// Prunes account/storage tries according to global LFU retention.
813    ///
814    /// - Top LFU `(address, slot)` entries are retained up to `max_hot_slots`.
815    ///
816    /// - Top LFU `(address, slot)` entries are retained in storage tries.
817    /// - Account trie retains only paths for accounts tracked by the account LFU.
818    /// - Storage tries retain only paths needed for retained slots.
819    /// - All other revealed paths are pruned to hash stubs or fully evicted.
820    ///
821    /// # Preconditions
822    ///
823    /// Node hashes must be computed via `root()` before calling this method. Otherwise, nodes
824    /// cannot be converted to hash stubs and pruning will have no effect.
825    #[cfg(feature = "std")]
826    #[instrument(
827        level = "debug",
828        name = "SparseStateTrie::prune",
829        target = "trie::sparse",
830        skip_all,
831        fields(%max_hot_slots, %max_hot_accounts)
832    )]
833    pub fn prune(&mut self, max_hot_slots: usize, max_hot_accounts: usize) {
834        self.hot_slots_lfu.decay_and_evict(max_hot_slots);
835        self.hot_accounts_lfu.decay_and_evict(max_hot_accounts);
836        let retained = self.hot_slots_lfu.retained_slots_by_address();
837
838        let retained_account_paths: Vec<Nibbles> =
839            self.hot_accounts_lfu.keys().map(|k| Nibbles::unpack(*k)).collect();
840
841        let retained_accounts = retained_account_paths.len();
842        let retained_storage_tries = retained.len();
843        let total_storage_tries_before = self.storage.tries.len();
844
845        // Prune account and storage tries in parallel using the same LFU-selected set.
846        let (account_nodes_pruned, storage_tries_evicted) = rayon::join(
847            || {
848                self.state
849                    .as_revealed_mut()
850                    .map(|trie| trie.prune(&retained_account_paths))
851                    .unwrap_or(0)
852            },
853            || self.storage.prune_by_retained_slots(retained),
854        );
855
856        debug!(
857            target: "trie::sparse",
858            retained_accounts,
859            retained_storage_tries,
860            account_nodes_pruned,
861            storage_tries_evicted,
862            storage_tries_after = total_storage_tries_before - storage_tries_evicted,
863            "SparseStateTrie::prune completed"
864        );
865    }
866
867    /// Commits the [`TrieUpdates`] to the sparse trie.
868    pub fn commit_updates(&mut self, updates: &TrieUpdates) {
869        if let Some(trie) = self.state.as_revealed_mut() {
870            trie.commit_updates(&updates.account_nodes, &updates.removed_nodes);
871        }
872        for (address, updates) in &updates.storage_tries {
873            if let Some(trie) =
874                self.storage.tries.get_mut(address).and_then(|t| t.as_revealed_mut())
875            {
876                trie.commit_updates(&updates.storage_nodes, &updates.removed_nodes);
877            }
878        }
879    }
880}
881
882/// The fields of [`SparseStateTrie`] related to storage tries. This is kept separate from the rest
883/// of [`SparseStateTrie`] to help enforce allocation re-use.
884#[derive(Debug, Default)]
885struct StorageTries<S = ParallelSparseTrie> {
886    /// Sparse storage tries.
887    tries: B256Map<RevealableSparseTrie<S>>,
888    /// Cleared storage tries, kept for re-use.
889    cleared_tries: Vec<RevealableSparseTrie<S>>,
890    /// A default cleared trie instance, which will be cloned when creating new tries.
891    default_trie: RevealableSparseTrie<S>,
892}
893
894#[cfg(feature = "std")]
895impl<S: SparseTrieTrait> StorageTries<S> {
896    /// Prunes storage tries using LFU-retained slots.
897    ///
898    /// Tries without retained slots are evicted entirely. Tries with retained slots are pruned to
899    /// those slots.
900    fn prune_by_retained_slots(&mut self, retained_slots: B256Map<Vec<Nibbles>>) -> usize {
901        // Parallel pass: prune retained tries and clear evicted ones in place.
902        {
903            use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
904            self.tries.par_iter_mut().for_each(|(address, trie)| {
905                if let Some(slots) = retained_slots.get(address) {
906                    if let Some(t) = trie.as_revealed_mut() {
907                        t.prune(slots);
908                    }
909                } else {
910                    trie.clear();
911                }
912            });
913        }
914
915        // Cheap sequential drain: move already-cleared tries into the reuse pool.
916        let addresses_to_evict: Vec<B256> = self
917            .tries
918            .keys()
919            .filter(|address| !retained_slots.contains_key(*address))
920            .copied()
921            .collect();
922
923        let evicted = addresses_to_evict.len();
924        self.cleared_tries.reserve(evicted);
925        for address in &addresses_to_evict {
926            if let Some(trie) = self.tries.remove(address) {
927                self.cleared_tries.push(trie);
928            }
929        }
930
931        evicted
932    }
933}
934
935impl<S: SparseTrieTrait> StorageTries<S> {
936    /// Returns all fields to a cleared state, equivalent to the default state, keeping cleared
937    /// collections for re-use later when possible.
938    fn clear(&mut self) {
939        self.cleared_tries.extend(self.tries.drain().map(|(_, mut trie)| {
940            trie.clear();
941            trie
942        }));
943    }
944
945    /// Shrinks the capacity of all storage tries to the given total sizes.
946    ///
947    /// Distributes capacity equally among all tries (active + cleared).
948    fn shrink_to(&mut self, max_nodes: usize, max_values: usize) {
949        let total_tries = self.tries.len() + self.cleared_tries.len();
950        if total_tries == 0 {
951            return;
952        }
953
954        // Distribute capacity equally among all tries
955        let nodes_per_trie = max_nodes / total_tries;
956        let values_per_trie = max_values / total_tries;
957
958        // Shrink active storage tries
959        for trie in self.tries.values_mut() {
960            trie.shrink_nodes_to(nodes_per_trie);
961            trie.shrink_values_to(values_per_trie);
962        }
963
964        // Shrink cleared storage tries
965        for trie in &mut self.cleared_tries {
966            trie.shrink_nodes_to(nodes_per_trie);
967            trie.shrink_values_to(values_per_trie);
968        }
969    }
970}
971
972impl<S: SparseTrieTrait + Clone> StorageTries<S> {
973    // Returns mutable reference to storage sparse trie, creating a blind one if it doesn't exist.
974    fn get_or_create_trie_mut(&mut self, address: B256) -> &mut RevealableSparseTrie<S> {
975        self.tries.entry(address).or_insert_with(|| {
976            self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone())
977        })
978    }
979
980    /// Takes the storage trie for the account from the internal `HashMap`, creating it if it
981    /// doesn't already exist.
982    #[cfg(feature = "std")]
983    fn take_or_create_trie(&mut self, account: &B256) -> RevealableSparseTrie<S> {
984        self.tries.remove(account).unwrap_or_else(|| {
985            self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone())
986        })
987    }
988}
989
990/// Key for identifying a storage slot in the global LFU cache.
991#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
992struct HotSlotKey {
993    address: B256,
994    slot: B256,
995}
996
997/// Slot-specific helpers for the `BucketedLfu<HotSlotKey>`.
998#[cfg(feature = "std")]
999impl BucketedLfu<HotSlotKey> {
1000    /// Returns retained slots grouped by address.
1001    fn retained_slots_by_address(&self) -> B256Map<Vec<Nibbles>> {
1002        let mut grouped =
1003            B256Map::<Vec<Nibbles>>::with_capacity_and_hasher(self.len(), Default::default());
1004        for key in self.keys() {
1005            grouped.entry(key.address).or_default().push(Nibbles::unpack(key.slot));
1006        }
1007
1008        for slots in grouped.values_mut() {
1009            slots.sort_unstable();
1010            slots.dedup();
1011        }
1012
1013        grouped
1014    }
1015}
1016
1017/// Calculates capacity estimation for V2 proof nodes.
1018///
1019/// This counts nodes and their children (for branch and extension nodes) to provide
1020/// proper capacity hints for `reserve_nodes`.
1021fn estimate_v2_proof_capacity(nodes: &[ProofTrieNodeV2]) -> usize {
1022    let mut capacity = nodes.len();
1023
1024    for node in nodes {
1025        if let TrieNodeV2::Branch(branch) = &node.node {
1026            capacity += branch.state_mask.count_ones() as usize;
1027        }
1028    }
1029
1030    capacity
1031}
1032
1033#[cfg(test)]
1034mod tests {
1035    use super::*;
1036    use crate::{provider::DefaultTrieNodeProviderFactory, LeafLookup, ParallelSparseTrie};
1037    use alloy_primitives::{
1038        b256,
1039        map::{HashMap, HashSet},
1040        U256,
1041    };
1042    use arbitrary::Arbitrary;
1043    use rand::{rngs::StdRng, Rng, SeedableRng};
1044    use reth_primitives_traits::Account;
1045    use reth_trie::{updates::StorageTrieUpdates, HashBuilder, MultiProof, EMPTY_ROOT_HASH};
1046    use reth_trie_common::{
1047        proof::{ProofNodes, ProofRetainer},
1048        BranchNodeMasks, BranchNodeMasksMap, BranchNodeV2, LeafNode, RlpNode, StorageMultiProof,
1049        TrieMask,
1050    };
1051
1052    /// Create a leaf key (suffix) with given nibbles padded with zeros to reach `total_len`.
1053    fn leaf_key(suffix: impl AsRef<[u8]>, total_len: usize) -> Nibbles {
1054        let suffix = suffix.as_ref();
1055        let mut nibbles = Nibbles::from_nibbles(suffix);
1056        nibbles.extend(&Nibbles::from_nibbles_unchecked(vec![0; total_len - suffix.len()]));
1057        nibbles
1058    }
1059
1060    #[test]
1061    fn reveal_account_path_twice() {
1062        let provider_factory = DefaultTrieNodeProviderFactory;
1063        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1064
1065        // Full 64-nibble paths
1066        let full_path_0 = leaf_key([0x0], 64);
1067        let _full_path_1 = leaf_key([0x1], 64);
1068
1069        let leaf_value = alloy_rlp::encode(TrieAccount::default());
1070        // Leaf key is 63 nibbles (suffix after 1-nibble node path)
1071        let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1072            leaf_key([], 63),
1073            leaf_value.clone(),
1074        )));
1075        let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1076            leaf_key([], 63),
1077            leaf_value.clone(),
1078        )));
1079
1080        let multiproof = MultiProof {
1081            account_subtree: ProofNodes::from_iter([
1082                (
1083                    Nibbles::default(),
1084                    alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
1085                        key: Nibbles::default(),
1086                        stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
1087                        state_mask: TrieMask::new(0b11),
1088                        branch_rlp_node: None,
1089                    }))
1090                    .into(),
1091                ),
1092                (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
1093                (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
1094            ]),
1095            ..Default::default()
1096        };
1097
1098        // Reveal multiproof and check that the state trie contains the leaf node and value
1099        sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
1100        assert!(matches!(
1101            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
1102            Ok(LeafLookup::Exists)
1103        ));
1104        assert_eq!(
1105            sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
1106            Some(&leaf_value)
1107        );
1108
1109        // Remove the leaf node and check that the state trie does not contain the leaf node and
1110        // value
1111        sparse.remove_account_leaf(&full_path_0, &provider_factory).unwrap();
1112        assert!(matches!(
1113            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
1114            Ok(LeafLookup::NonExistent)
1115        ));
1116        assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
1117    }
1118
1119    #[test]
1120    fn reveal_storage_path_twice() {
1121        let provider_factory = DefaultTrieNodeProviderFactory;
1122        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1123
1124        // Full 64-nibble path
1125        let full_path_0 = leaf_key([0x0], 64);
1126
1127        let leaf_value = alloy_rlp::encode(TrieAccount::default());
1128        let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1129            leaf_key([], 63),
1130            leaf_value.clone(),
1131        )));
1132        let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
1133            leaf_key([], 63),
1134            leaf_value.clone(),
1135        )));
1136
1137        let multiproof = MultiProof {
1138            storages: HashMap::from_iter([(
1139                B256::ZERO,
1140                StorageMultiProof {
1141                    root: B256::ZERO,
1142                    subtree: ProofNodes::from_iter([
1143                        (
1144                            Nibbles::default(),
1145                            alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
1146                                key: Nibbles::default(),
1147                                stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
1148                                state_mask: TrieMask::new(0b11),
1149                                branch_rlp_node: None,
1150                            }))
1151                            .into(),
1152                        ),
1153                        (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
1154                        (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
1155                    ]),
1156                    branch_node_masks: Default::default(),
1157                },
1158            )]),
1159            ..Default::default()
1160        };
1161
1162        // Reveal multiproof and check that the storage trie contains the leaf node and value
1163        sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
1164        assert!(matches!(
1165            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
1166            Ok(LeafLookup::Exists)
1167        ));
1168        assert_eq!(
1169            sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
1170            Some(&leaf_value)
1171        );
1172
1173        // Remove the leaf node and check that the storage trie does not contain the leaf node and
1174        // value
1175        sparse.remove_storage_leaf(B256::ZERO, &full_path_0, &provider_factory).unwrap();
1176        assert!(matches!(
1177            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
1178            Ok(LeafLookup::NonExistent)
1179        ));
1180        assert!(sparse
1181            .storage_trie_ref(&B256::ZERO)
1182            .unwrap()
1183            .get_leaf_value(&full_path_0)
1184            .is_none());
1185    }
1186
1187    #[test]
1188    fn seeded_hot_cache_capacities_preserve_first_cycle_touches() {
1189        let account = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1190        let slot = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1191        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1192
1193        sparse.set_hot_cache_capacities(1, 1);
1194        sparse.record_account_touch(account);
1195        sparse.record_slot_touch(account, slot);
1196        sparse.prune(1, 1);
1197
1198        assert_eq!(sparse.hot_accounts_lfu.keys().copied().collect::<Vec<_>>(), vec![account]);
1199        assert_eq!(
1200            sparse.hot_slots_lfu.keys().copied().collect::<Vec<_>>(),
1201            vec![HotSlotKey { address: account, slot }]
1202        );
1203    }
1204
1205    #[test]
1206    fn reveal_v2_proof_nodes() {
1207        let provider_factory = DefaultTrieNodeProviderFactory;
1208        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1209
1210        // Full 64-nibble path
1211        let full_path_0 = leaf_key([0x0], 64);
1212
1213        let leaf_value = alloy_rlp::encode(TrieAccount::default());
1214        let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
1215        let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
1216
1217        let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
1218            key: Nibbles::default(),
1219            stack: vec![
1220                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
1221                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
1222            ],
1223            state_mask: TrieMask::new(0b11),
1224            branch_rlp_node: None,
1225        });
1226
1227        // Create V2 proof nodes with masks already included
1228        let v2_proof_nodes = vec![
1229            ProofTrieNodeV2 {
1230                path: Nibbles::default(),
1231                node: branch_node,
1232                masks: Some(BranchNodeMasks {
1233                    hash_mask: TrieMask::default(),
1234                    tree_mask: TrieMask::default(),
1235                }),
1236            },
1237            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
1238            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
1239        ];
1240
1241        // Reveal V2 proof nodes
1242        sparse.reveal_account_v2_proof_nodes(v2_proof_nodes).unwrap();
1243
1244        // Check that the state trie contains the leaf node and value
1245        assert!(matches!(
1246            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
1247            Ok(LeafLookup::Exists)
1248        ));
1249        assert_eq!(
1250            sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
1251            Some(&leaf_value)
1252        );
1253
1254        // Remove the leaf node
1255        sparse.remove_account_leaf(&full_path_0, &provider_factory).unwrap();
1256        assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
1257    }
1258
1259    #[test]
1260    fn reveal_storage_v2_proof_nodes() {
1261        let provider_factory = DefaultTrieNodeProviderFactory;
1262        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default();
1263
1264        // Full 64-nibble path
1265        let full_path_0 = leaf_key([0x0], 64);
1266
1267        let storage_value: Vec<u8> = alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec();
1268        let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
1269        let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
1270
1271        let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
1272            key: Nibbles::default(),
1273            stack: vec![
1274                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
1275                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
1276            ],
1277            state_mask: TrieMask::new(0b11),
1278            branch_rlp_node: None,
1279        });
1280
1281        let v2_proof_nodes = vec![
1282            ProofTrieNodeV2 { path: Nibbles::default(), node: branch_node, masks: None },
1283            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
1284            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
1285        ];
1286
1287        // Reveal V2 storage proof nodes for account
1288        sparse.reveal_storage_v2_proof_nodes(B256::ZERO, v2_proof_nodes).unwrap();
1289
1290        // Check that the storage trie contains the leaf node and value
1291        assert!(matches!(
1292            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
1293            Ok(LeafLookup::Exists)
1294        ));
1295        assert_eq!(
1296            sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
1297            Some(&storage_value)
1298        );
1299
1300        // Remove the leaf node
1301        sparse.remove_storage_leaf(B256::ZERO, &full_path_0, &provider_factory).unwrap();
1302        assert!(sparse
1303            .storage_trie_ref(&B256::ZERO)
1304            .unwrap()
1305            .get_leaf_value(&full_path_0)
1306            .is_none());
1307    }
1308
1309    #[test]
1310    fn take_trie_updates() {
1311        reth_tracing::init_test_tracing();
1312
1313        // let mut rng = generators::rng();
1314        let mut rng = StdRng::seed_from_u64(1);
1315
1316        let mut bytes = [0u8; 1024];
1317        rng.fill(bytes.as_mut_slice());
1318
1319        let slot_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1320        let slot_path_1 = Nibbles::unpack(slot_1);
1321        let value_1 = U256::from(rng.random::<u64>());
1322        let slot_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1323        let slot_path_2 = Nibbles::unpack(slot_2);
1324        let value_2 = U256::from(rng.random::<u64>());
1325        let slot_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1326        let slot_path_3 = Nibbles::unpack(slot_3);
1327        let value_3 = U256::from(rng.random::<u64>());
1328
1329        let mut storage_hash_builder = HashBuilder::default()
1330            .with_proof_retainer(ProofRetainer::from_iter([slot_path_1, slot_path_2]));
1331        storage_hash_builder.add_leaf(slot_path_1, &alloy_rlp::encode_fixed_size(&value_1));
1332        storage_hash_builder.add_leaf(slot_path_2, &alloy_rlp::encode_fixed_size(&value_2));
1333
1334        let storage_root = storage_hash_builder.root();
1335        let storage_proof_nodes = storage_hash_builder.take_proof_nodes();
1336        let storage_branch_node_masks = BranchNodeMasksMap::from_iter([
1337            (
1338                Nibbles::default(),
1339                BranchNodeMasks { hash_mask: TrieMask::new(0b010), tree_mask: TrieMask::default() },
1340            ),
1341            (
1342                Nibbles::from_nibbles([0x1]),
1343                BranchNodeMasks { hash_mask: TrieMask::new(0b11), tree_mask: TrieMask::default() },
1344            ),
1345        ]);
1346
1347        let address_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1348        let address_path_1 = Nibbles::unpack(address_1);
1349        let account_1 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1350        let mut trie_account_1 = account_1.into_trie_account(storage_root);
1351        let address_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1352        let address_path_2 = Nibbles::unpack(address_2);
1353        let account_2 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1354        let mut trie_account_2 = account_2.into_trie_account(EMPTY_ROOT_HASH);
1355
1356        let mut hash_builder = HashBuilder::default()
1357            .with_proof_retainer(ProofRetainer::from_iter([address_path_1, address_path_2]));
1358        hash_builder.add_leaf(address_path_1, &alloy_rlp::encode(trie_account_1));
1359        hash_builder.add_leaf(address_path_2, &alloy_rlp::encode(trie_account_2));
1360
1361        let root = hash_builder.root();
1362        let proof_nodes = hash_builder.take_proof_nodes();
1363
1364        let provider_factory = DefaultTrieNodeProviderFactory;
1365        let mut sparse = SparseStateTrie::<ParallelSparseTrie>::default().with_updates(true);
1366        sparse
1367            .reveal_decoded_multiproof(
1368                MultiProof {
1369                    account_subtree: proof_nodes,
1370                    branch_node_masks: BranchNodeMasksMap::from_iter([(
1371                        Nibbles::from_nibbles([0x1]),
1372                        BranchNodeMasks {
1373                            hash_mask: TrieMask::new(0b00),
1374                            tree_mask: TrieMask::default(),
1375                        },
1376                    )]),
1377                    storages: HashMap::from_iter([
1378                        (
1379                            address_1,
1380                            StorageMultiProof {
1381                                root,
1382                                subtree: storage_proof_nodes.clone(),
1383                                branch_node_masks: storage_branch_node_masks.clone(),
1384                            },
1385                        ),
1386                        (
1387                            address_2,
1388                            StorageMultiProof {
1389                                root,
1390                                subtree: storage_proof_nodes,
1391                                branch_node_masks: storage_branch_node_masks,
1392                            },
1393                        ),
1394                    ]),
1395                }
1396                .try_into()
1397                .unwrap(),
1398            )
1399            .unwrap();
1400
1401        assert_eq!(sparse.root(&provider_factory).unwrap(), root);
1402
1403        let address_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1404        let address_path_3 = Nibbles::unpack(address_3);
1405        let account_3 = Account { nonce: account_1.nonce + 1, ..account_1 };
1406        let trie_account_3 = account_3.into_trie_account(EMPTY_ROOT_HASH);
1407
1408        sparse
1409            .update_account_leaf(
1410                address_path_3,
1411                alloy_rlp::encode(trie_account_3),
1412                &provider_factory,
1413            )
1414            .unwrap();
1415
1416        sparse
1417            .update_storage_leaf(
1418                address_1,
1419                slot_path_3,
1420                alloy_rlp::encode(value_3),
1421                &provider_factory,
1422            )
1423            .unwrap();
1424        trie_account_1.storage_root = sparse.storage_root(&address_1).unwrap();
1425        sparse
1426            .update_account_leaf(
1427                address_path_1,
1428                alloy_rlp::encode(trie_account_1),
1429                &provider_factory,
1430            )
1431            .unwrap();
1432
1433        sparse.wipe_storage(address_2).unwrap();
1434        trie_account_2.storage_root = sparse.storage_root(&address_2).unwrap();
1435        sparse
1436            .update_account_leaf(
1437                address_path_2,
1438                alloy_rlp::encode(trie_account_2),
1439                &provider_factory,
1440            )
1441            .unwrap();
1442
1443        sparse.root(&provider_factory).unwrap();
1444
1445        let sparse_updates = sparse.take_trie_updates().unwrap();
1446        // TODO(alexey): assert against real state root calculation updates
1447        pretty_assertions::assert_eq!(
1448            sparse_updates,
1449            TrieUpdates {
1450                account_nodes: HashMap::default(),
1451                storage_tries: HashMap::from_iter([(
1452                    b256!("0x1100000000000000000000000000000000000000000000000000000000000000"),
1453                    StorageTrieUpdates {
1454                        is_deleted: true,
1455                        storage_nodes: HashMap::default(),
1456                        removed_nodes: HashSet::default()
1457                    }
1458                )]),
1459                removed_nodes: HashSet::default()
1460            }
1461        );
1462    }
1463}