Skip to main content

reth_trie_sparse/
state.rs

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