Skip to main content

reth_trie_sparse/
state.rs

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