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