Skip to main content

reth_trie_sparse/
state.rs

1#[cfg(feature = "trie-debug")]
2use crate::debug_recorder::TrieDebugRecorder;
3use crate::{
4    traits::SparseTrie as SparseTrieTrait, ArenaParallelSparseTrie, RevealableSparseTrie,
5    TrieNodeEpoch,
6};
7use alloc::vec::Vec;
8use alloy_primitives::{map::B256Map, B256};
9use either::Either;
10use reth_execution_errors::{SparseStateTrieResult, SparseTrieErrorKind};
11use reth_trie_common::{
12    updates::{StorageTrieUpdates, TrieUpdates},
13    DecodedMultiProof, MultiProof, Nibbles, ProofTrieNodeV2,
14};
15#[cfg(feature = "std")]
16use tracing::debug;
17use tracing::instrument;
18
19/// Holds data that should be dropped after any locks are released.
20///
21/// This is used to defer expensive deallocations (like proof node buffers) until after final state
22/// root is calculated
23#[derive(Debug, Default)]
24pub struct DeferredDrops {
25    /// Each nodes reveal operation creates a new buffer, uses it, and pushes it here.
26    pub proof_nodes_bufs: Vec<Vec<ProofTrieNodeV2>>,
27}
28
29#[derive(Debug)]
30/// Sparse state trie representing lazy-loaded Ethereum state trie.
31pub struct SparseStateTrie<
32    A = ArenaParallelSparseTrie, // Account trie implementation
33    S = ArenaParallelSparseTrie, // Storage trie implementation
34> {
35    /// Sparse account trie.
36    state: RevealableSparseTrie<A>,
37    /// State related to storage tries.
38    storage: StorageTries<S>,
39    /// Flag indicating whether trie updates should be retained.
40    retain_updates: bool,
41    /// Holds data that should be dropped after final state root is calculated.
42    deferred_drops: DeferredDrops,
43    /// Metrics for the sparse state trie.
44    #[cfg(feature = "metrics")]
45    metrics: crate::metrics::SparseStateTrieMetrics,
46}
47
48impl<A, S> Default for SparseStateTrie<A, S>
49where
50    A: Default,
51    S: Default,
52{
53    fn default() -> Self {
54        Self {
55            state: Default::default(),
56            storage: Default::default(),
57            retain_updates: false,
58            deferred_drops: DeferredDrops::default(),
59            #[cfg(feature = "metrics")]
60            metrics: Default::default(),
61        }
62    }
63}
64
65#[cfg(test)]
66impl SparseStateTrie {
67    /// Create state trie from state trie.
68    pub fn from_state(state: RevealableSparseTrie) -> Self {
69        Self { state, ..Default::default() }
70    }
71}
72
73impl<A, S> SparseStateTrie<A, S> {
74    /// Set the retention of branch node updates and deletions.
75    pub const fn set_updates(&mut self, retain_updates: bool) {
76        self.retain_updates = retain_updates;
77    }
78
79    /// Set the retention of branch node updates and deletions.
80    pub const fn with_updates(mut self, retain_updates: bool) -> Self {
81        self.set_updates(retain_updates);
82        self
83    }
84
85    /// Set the accounts trie to the given `RevealableSparseTrie`.
86    pub fn set_accounts_trie(&mut self, trie: RevealableSparseTrie<A>) {
87        self.state = trie;
88    }
89
90    /// Set the accounts trie to the given `RevealableSparseTrie`.
91    pub fn with_accounts_trie(mut self, trie: RevealableSparseTrie<A>) -> Self {
92        self.set_accounts_trie(trie);
93        self
94    }
95
96    /// Set the default trie which will be cloned when creating new storage
97    /// [`RevealableSparseTrie`]s.
98    pub fn set_default_storage_trie(&mut self, trie: RevealableSparseTrie<S>) {
99        self.storage.default_trie = trie;
100    }
101
102    /// Set the default trie which will be cloned when creating new storage
103    /// [`RevealableSparseTrie`]s.
104    pub fn with_default_storage_trie(mut self, trie: RevealableSparseTrie<S>) -> Self {
105        self.set_default_storage_trie(trie);
106        self
107    }
108
109    /// Takes the data structures for deferred dropping.
110    ///
111    /// This allows the caller to drop the buffers later, avoiding expensive deallocations while
112    /// calculating the state root.
113    pub fn take_deferred_drops(&mut self) -> DeferredDrops {
114        core::mem::take(&mut self.deferred_drops)
115    }
116}
117
118impl SparseStateTrie {
119    /// Create new [`SparseStateTrie`] with the default trie implementation.
120    pub fn new() -> Self {
121        Self::default()
122    }
123}
124
125impl<A: SparseTrieTrait, S: SparseTrieTrait> SparseStateTrie<A, S> {
126    /// Takes all debug recorders from the account trie and all revealed storage tries.
127    ///
128    /// Returns a vec of `(Option<B256>, TrieDebugRecorder)` where `None` is the account trie
129    /// key, and `Some(address)` are storage trie keys.
130    #[cfg(feature = "trie-debug")]
131    pub fn take_debug_recorders(&mut self) -> alloc::vec::Vec<(Option<B256>, TrieDebugRecorder)> {
132        let mut recorders = alloc::vec::Vec::new();
133        if let Some(trie) = self.state.as_revealed_mut() {
134            recorders.push((None, trie.take_debug_recorder()));
135        }
136        for (address, trie) in &mut self.storage.tries {
137            if let Some(trie) = trie.as_revealed_mut() {
138                recorders.push((Some(*address), trie.take_debug_recorder()));
139            }
140        }
141        recorders
142    }
143}
144
145impl<A, S> SparseStateTrie<A, S>
146where
147    A: SparseTrieTrait + Default,
148    S: SparseTrieTrait + Default + Clone,
149{
150    /// Returns mutable reference to account trie.
151    pub const fn trie_mut(&mut self) -> &mut RevealableSparseTrie<A> {
152        &mut self.state
153    }
154
155    /// Returns `true` if the account path has been revealed in the sparse trie.
156    pub fn is_account_revealed(&self, account: B256) -> bool {
157        let path = Nibbles::unpack(account);
158        let trie = match self.state_trie_ref() {
159            Some(t) => t,
160            None => return false,
161        };
162
163        trie.find_leaf(&path, None).is_ok()
164    }
165
166    /// Was the storage-slot witness for (`address`,`slot`) complete?
167    pub fn check_valid_storage_witness(&self, address: B256, slot: B256) -> bool {
168        let path = Nibbles::unpack(slot);
169        let trie = match self.storage_trie_ref(&address) {
170            Some(t) => t,
171            None => return false,
172        };
173
174        trie.find_leaf(&path, None).is_ok()
175    }
176
177    /// Returns reference to bytes representing leaf value for the target account.
178    pub fn get_account_value(&self, account: &B256) -> Option<&Vec<u8>> {
179        self.state.as_revealed_ref()?.get_leaf_value(&Nibbles::unpack(account))
180    }
181
182    /// Returns reference to bytes representing leaf value for the target account and storage slot.
183    pub fn get_storage_slot_value(&self, account: &B256, slot: &B256) -> Option<&Vec<u8>> {
184        self.storage.tries.get(account)?.as_revealed_ref()?.get_leaf_value(&Nibbles::unpack(slot))
185    }
186
187    /// Returns reference to state trie if it was revealed.
188    pub const fn state_trie_ref(&self) -> Option<&A> {
189        self.state.as_revealed_ref()
190    }
191
192    /// Returns reference to storage trie if it was revealed.
193    pub fn storage_trie_ref(&self, address: &B256) -> Option<&S> {
194        self.storage.tries.get(address).and_then(|e| e.as_revealed_ref())
195    }
196
197    /// Returns mutable reference to storage sparse trie if it was revealed.
198    pub fn storage_trie_mut(&mut self, address: &B256) -> Option<&mut S> {
199        self.storage.tries.get_mut(address).and_then(|e| e.as_revealed_mut())
200    }
201
202    /// Returns mutable reference to storage tries.
203    pub const fn storage_tries_mut(&mut self) -> &mut B256Map<RevealableSparseTrie<S>> {
204        &mut self.storage.tries
205    }
206
207    /// Takes the storage trie for the provided address.
208    pub fn take_storage_trie(&mut self, address: &B256) -> Option<RevealableSparseTrie<S>> {
209        self.storage.tries.remove(address)
210    }
211
212    /// Takes the storage trie for the provided address, creating a blind one if it doesn't exist.
213    pub fn take_or_create_storage_trie(&mut self, address: &B256) -> RevealableSparseTrie<S> {
214        self.storage.tries.remove(address).unwrap_or_else(|| {
215            self.storage.cleared_tries.pop().unwrap_or_else(|| self.storage.default_trie.clone())
216        })
217    }
218
219    /// Inserts storage trie for the provided address.
220    pub fn insert_storage_trie(&mut self, address: B256, storage_trie: RevealableSparseTrie<S>) {
221        self.storage.tries.insert(address, storage_trie);
222    }
223
224    /// Returns mutable reference to storage sparse trie, creating a blind one if it doesn't exist.
225    pub fn get_or_create_storage_trie_mut(
226        &mut self,
227        address: B256,
228    ) -> &mut RevealableSparseTrie<S> {
229        self.storage.get_or_create_trie_mut(address)
230    }
231
232    /// Reveal unknown trie paths from multiproof.
233    /// NOTE: This method does not extensively validate the proof.
234    pub fn reveal_multiproof(&mut self, multiproof: MultiProof) -> SparseStateTrieResult<()> {
235        // first decode the multiproof
236        let decoded_multiproof = multiproof.try_into()?;
237
238        // then reveal the decoded multiproof
239        self.reveal_decoded_multiproof(decoded_multiproof)
240    }
241
242    /// Reveal unknown trie paths from decoded multiproof.
243    /// NOTE: This method does not extensively validate the proof.
244    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
245    pub fn reveal_decoded_multiproof(
246        &mut self,
247        multiproof: DecodedMultiProof,
248    ) -> SparseStateTrieResult<()> {
249        self.reveal_decoded_multiproof_v2(multiproof.into())
250    }
251
252    /// Reveals a V2 decoded multiproof.
253    ///
254    /// V2 multiproofs use a simpler format where proof nodes are stored as vectors rather than
255    /// hashmaps, with masks already included in the `ProofTrieNode` structure.
256    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
257    pub fn reveal_decoded_multiproof_v2(
258        &mut self,
259        multiproof: reth_trie_common::DecodedMultiProofV2,
260    ) -> SparseStateTrieResult<()> {
261        let reth_trie_common::DecodedMultiProofV2 { account_proofs, mut storage_proofs, .. } =
262            multiproof;
263
264        // Collect `(trie, proof_nodes)` pairs for both the account trie and every storage trie
265        // touched by this multiproof.
266        let mut targets = Vec::with_capacity(storage_proofs.len() + 1);
267
268        if !account_proofs.is_empty() {
269            #[cfg(feature = "metrics")]
270            self.metrics.increment_total_account_nodes(account_proofs.len() as u64);
271            targets.push((None, Either::Left(&mut self.state), account_proofs));
272        }
273
274        // Ensure a storage trie exists for every address whose proofs we're about to reveal
275        for &account in storage_proofs.keys() {
276            let _ = self.storage.get_or_create_trie_mut(account);
277        }
278
279        for (account, trie) in &mut self.storage.tries {
280            if let Some(nodes) = storage_proofs.remove(account) {
281                #[cfg(feature = "metrics")]
282                self.metrics.increment_total_storage_nodes(nodes.len() as u64);
283                targets.push((Some(*account), Either::Right(trie), nodes));
284            }
285        }
286
287        let retain_updates = self.retain_updates;
288
289        #[cfg(not(feature = "std"))]
290        let results: Vec<_> = targets
291            .into_iter()
292            .map(|(_, target, mut nodes)| {
293                let result = match target {
294                    Either::Left(trie) => trie.reveal_v2_proof_nodes(&mut nodes, retain_updates),
295                    Either::Right(trie) => trie.reveal_v2_proof_nodes(&mut nodes, retain_updates),
296                };
297                (result, nodes)
298            })
299            .collect();
300
301        #[cfg(feature = "std")]
302        let results: Vec<_> = {
303            use rayon::iter::ParallelIterator;
304            use reth_primitives_traits::ParallelBridgeBuffered;
305
306            let parent_span = tracing::Span::current();
307            targets
308                .into_iter()
309                .par_bridge_buffered()
310                .map(|(hashed_address, target, mut nodes)| {
311                    let _span = tracing::trace_span!(
312                        target: "trie::sparse",
313                        parent: &parent_span,
314                        "reveal_v2_proof_nodes",
315                        ?hashed_address,
316                    )
317                    .entered();
318
319                    let result = match target {
320                        Either::Left(trie) => {
321                            trie.reveal_v2_proof_nodes(&mut nodes, retain_updates)
322                        }
323                        Either::Right(trie) => {
324                            trie.reveal_v2_proof_nodes(&mut nodes, retain_updates)
325                        }
326                    };
327                    (result, nodes)
328                })
329                .collect()
330        };
331
332        // Accumulate the first error and defer dropping the proof node buffers.
333        let mut any_err = Ok(());
334        for (result, nodes) in results {
335            if result.is_err() && any_err.is_ok() {
336                any_err = result.map_err(Into::into);
337            }
338            self.deferred_drops.proof_nodes_bufs.push(nodes);
339        }
340
341        any_err
342    }
343
344    /// Wipe the storage trie at the provided address.
345    pub fn wipe_storage(&mut self, address: B256) -> SparseStateTrieResult<()> {
346        if let Some(trie) = self.storage.tries.get_mut(&address) {
347            trie.wipe()?;
348        }
349        Ok(())
350    }
351
352    /// Calculates the hashes of subtries.
353    ///
354    /// If the trie has not been revealed, this function does nothing.
355    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
356    pub fn calculate_subtries(&mut self, new_epoch: TrieNodeEpoch) {
357        if let RevealableSparseTrie::Revealed(trie) = &mut self.state {
358            trie.update_subtrie_hashes(new_epoch);
359        }
360    }
361
362    /// Returns storage sparse trie root if the trie has been revealed.
363    pub fn storage_root(&mut self, account: &B256, new_epoch: TrieNodeEpoch) -> Option<B256> {
364        self.storage.tries.get_mut(account).and_then(|trie| trie.root(new_epoch))
365    }
366
367    /// Returns mutable reference to the revealed account sparse trie.
368    fn revealed_trie_mut(&mut self) -> SparseStateTrieResult<&mut A> {
369        self.state.as_revealed_mut().ok_or_else(|| SparseTrieErrorKind::Blind.into())
370    }
371
372    /// Returns sparse trie root.
373    pub fn root(&mut self, new_epoch: TrieNodeEpoch) -> SparseStateTrieResult<B256> {
374        // record revealed node metrics
375        #[cfg(feature = "metrics")]
376        self.metrics.record();
377
378        Ok(self.revealed_trie_mut()?.root(new_epoch))
379    }
380
381    /// Returns sparse trie root and trie updates.
382    ///
383    /// Returns an error if the account trie is still blind.
384    #[instrument(level = "debug", target = "trie::sparse", skip_all)]
385    pub fn root_with_updates(
386        &mut self,
387        new_epoch: TrieNodeEpoch,
388    ) -> SparseStateTrieResult<(B256, TrieUpdates)> {
389        // record revealed node metrics
390        #[cfg(feature = "metrics")]
391        self.metrics.record();
392
393        let storage_tries = self.storage_trie_updates();
394        let revealed = self.revealed_trie_mut()?;
395
396        let (root, updates) = (revealed.root(new_epoch), revealed.take_updates());
397        let updates = TrieUpdates {
398            account_nodes: updates.updated_nodes,
399            removed_nodes: updates.removed_nodes,
400            storage_tries,
401        };
402        Ok((root, updates))
403    }
404
405    /// Returns storage trie updates for tries that have been revealed.
406    ///
407    /// Panics if any of the storage tries are not revealed.
408    pub fn storage_trie_updates(&mut self) -> B256Map<StorageTrieUpdates> {
409        self.storage
410            .tries
411            .iter_mut()
412            .map(|(address, trie)| {
413                let trie = trie.as_revealed_mut().unwrap();
414                let updates = trie.take_updates();
415                let updates = StorageTrieUpdates {
416                    is_deleted: updates.wiped,
417                    storage_nodes: updates.updated_nodes,
418                    removed_nodes: updates.removed_nodes,
419                };
420                (*address, updates)
421            })
422            .filter(|(_, updates)| !updates.is_empty())
423            .collect()
424    }
425
426    /// Returns [`TrieUpdates`] by taking the updates from the revealed sparse tries.
427    ///
428    /// Returns `None` if the accounts trie is not revealed.
429    pub fn take_trie_updates(&mut self) -> Option<TrieUpdates> {
430        let storage_tries = self.storage_trie_updates();
431        self.state.as_revealed_mut().map(|state| {
432            let updates = state.take_updates();
433            TrieUpdates {
434                account_nodes: updates.updated_nodes,
435                removed_nodes: updates.removed_nodes,
436                storage_tries,
437            }
438        })
439    }
440}
441
442impl<A, S> SparseStateTrie<A, S>
443where
444    A: SparseTrieTrait + Default,
445    S: SparseTrieTrait + Default + Clone,
446{
447    /// Clears all trie data while preserving allocations for reuse.
448    ///
449    /// This resets the trie to an empty state but keeps the underlying memory allocations,
450    /// which can significantly reduce allocation overhead when the trie is reused.
451    pub fn clear(&mut self) {
452        self.state.clear();
453        self.storage.clear();
454    }
455
456    /// Returns the number of storage tries currently retained (active + cleared).
457    pub fn retained_storage_tries_count(&self) -> usize {
458        self.storage.tries.len() + self.storage.cleared_tries.len()
459    }
460
461    /// Prunes account and storage trie nodes last modified before `prune_before`.
462    ///
463    /// Storage tries whose root epochs predate the cutoff are fully evicted.
464    ///
465    /// # Preconditions
466    ///
467    /// Modified account and storage tries must already have computed hashes via `root()` /
468    /// `storage_root()` for their current state. Unmodified storage roots revealed only by
469    /// prewarming are treated as epoch zero.
470    #[cfg(feature = "std")]
471    #[instrument(
472        level = "debug",
473        name = "SparseStateTrie::prune",
474        target = "trie::sparse",
475        skip_all
476    )]
477    pub fn prune(&mut self, prune_before: TrieNodeEpoch) {
478        let total_storage_tries_before = self.storage.tries.len();
479
480        let parent_span = tracing::Span::current();
481        let account_parent_span = parent_span.clone();
482
483        // Prune account and storage tries in parallel using the same epoch cutoff.
484        let (account_nodes_pruned, storage_tries_evicted) = rayon::join(
485            || {
486                let hashed_address = Option::<B256>::None;
487                let _span = tracing::trace_span!(
488                    target: "trie::sparse",
489                    parent: &account_parent_span,
490                    "prune_trie",
491                    ?hashed_address,
492                )
493                .entered();
494
495                self.state.as_revealed_mut().map(|trie| trie.prune(prune_before)).unwrap_or(0)
496            },
497            || self.storage.prune(prune_before, &parent_span),
498        );
499
500        debug!(
501            target: "trie::sparse",
502            prune_before = prune_before.get(),
503            account_nodes_pruned,
504            storage_tries_evicted,
505            storage_tries_after = total_storage_tries_before - storage_tries_evicted,
506            "SparseStateTrie::prune completed"
507        );
508    }
509}
510
511/// The fields of [`SparseStateTrie`] related to storage tries. This is kept separate from the rest
512/// of [`SparseStateTrie`] to help enforce allocation re-use.
513#[derive(Debug, Default)]
514struct StorageTries<S = ArenaParallelSparseTrie> {
515    /// Sparse storage tries.
516    tries: B256Map<RevealableSparseTrie<S>>,
517    /// Cleared storage tries, kept for re-use.
518    cleared_tries: Vec<RevealableSparseTrie<S>>,
519    /// A default cleared trie instance, which will be cloned when creating new tries.
520    default_trie: RevealableSparseTrie<S>,
521}
522
523#[cfg(feature = "std")]
524impl<S: SparseTrieTrait> StorageTries<S> {
525    /// Prunes storage tries by epoch, returning fully old tries to the reuse pool.
526    fn prune(&mut self, prune_before: TrieNodeEpoch, parent_span: &tracing::Span) -> usize {
527        use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
528
529        let addresses_to_evict: Vec<B256> = self
530            .tries
531            .par_iter_mut()
532            .filter_map(|(address, trie)| {
533                let hashed_address = Some(*address);
534                let _span = tracing::trace_span!(
535                    target: "trie::sparse",
536                    parent: parent_span,
537                    "prune_trie",
538                    ?hashed_address,
539                )
540                .entered();
541
542                let evict = trie.as_revealed_mut().is_none_or(|revealed| {
543                    // Avoid traversing and compacting a trie that will be immediately evicted.
544                    let root_epoch =
545                        revealed.root_epoch().expect("storage trie root must not be dirty");
546                    if !root_epoch.should_prune(prune_before) {
547                        revealed.prune(prune_before);
548                    }
549                    root_epoch.should_prune(prune_before)
550                });
551
552                evict.then(|| {
553                    trie.clear();
554                    *address
555                })
556            })
557            .collect();
558
559        let evicted = addresses_to_evict.len();
560        self.cleared_tries.reserve(evicted);
561        for address in addresses_to_evict {
562            if let Some(trie) = self.tries.remove(&address) {
563                self.cleared_tries.push(trie);
564            }
565        }
566
567        evicted
568    }
569}
570
571impl<S: SparseTrieTrait> StorageTries<S> {
572    /// Returns all fields to a cleared state, equivalent to the default state, keeping cleared
573    /// collections for re-use later when possible.
574    fn clear(&mut self) {
575        self.cleared_tries.extend(self.tries.drain().map(|(_, mut trie)| {
576            trie.clear();
577            trie
578        }));
579    }
580}
581
582impl<S: SparseTrieTrait + Clone> StorageTries<S> {
583    // Returns mutable reference to storage sparse trie, creating a blind one if it doesn't exist.
584    fn get_or_create_trie_mut(&mut self, address: B256) -> &mut RevealableSparseTrie<S> {
585        self.tries.entry(address).or_insert_with(|| {
586            self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone())
587        })
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    use super::*;
594    use crate::{ArenaParallelSparseTrie, LeafLookup, LeafUpdate};
595    use alloy_primitives::{
596        b256,
597        map::{HashMap, HashSet},
598        U256,
599    };
600    use arbitrary::Arbitrary;
601    use rand::{rngs::StdRng, Rng, SeedableRng};
602    use reth_execution_errors::{SparseStateTrieErrorKind, SparseTrieErrorKind};
603    use reth_primitives_traits::Account;
604    use reth_trie::{updates::StorageTrieUpdates, HashBuilder, MultiProof, EMPTY_ROOT_HASH};
605    use reth_trie_common::{
606        proof::{ProofNodes, ProofRetainer},
607        BranchNodeMasks, BranchNodeMasksMap, BranchNodeV2, LeafNode, RlpNode, StorageMultiProof,
608        TrieAccount, TrieMask, TrieNodeV2,
609    };
610
611    const fn epoch(value: u64) -> TrieNodeEpoch {
612        TrieNodeEpoch::new(value)
613    }
614
615    /// Create a leaf key (suffix) with given nibbles padded with zeros to reach `total_len`.
616    fn leaf_key(suffix: impl AsRef<[u8]>, total_len: usize) -> Nibbles {
617        let suffix = suffix.as_ref();
618        let mut nibbles = Nibbles::from_nibbles(suffix);
619        nibbles.extend(&Nibbles::from_nibbles_unchecked(vec![0; total_len - suffix.len()]));
620        nibbles
621    }
622
623    fn apply_account_update(sparse: &mut SparseStateTrie, address: B256, update: LeafUpdate) {
624        let mut updates = B256Map::from_iter([(address, update)]);
625        sparse.trie_mut().update_leaves(&mut updates, |_, _| {}).unwrap();
626        assert!(updates.is_empty());
627    }
628
629    #[test]
630    fn reveal_account_path_twice() {
631        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
632
633        // Full 64-nibble paths
634        let full_path_0 = leaf_key([0x0], 64);
635        let _full_path_1 = leaf_key([0x1], 64);
636
637        let leaf_value = alloy_rlp::encode(TrieAccount::default());
638        // Leaf key is 63 nibbles (suffix after 1-nibble node path)
639        let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
640            leaf_key([], 63),
641            leaf_value.clone(),
642        )));
643        let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
644            leaf_key([], 63),
645            leaf_value.clone(),
646        )));
647
648        let multiproof = MultiProof {
649            account_subtree: ProofNodes::from_iter([
650                (
651                    Nibbles::default(),
652                    alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
653                        key: Nibbles::default(),
654                        stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
655                        state_mask: TrieMask::new(0b11),
656                        branch_rlp_node: None,
657                    }))
658                    .into(),
659                ),
660                (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
661                (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
662            ]),
663            ..Default::default()
664        };
665
666        // Reveal multiproof and check that the state trie contains the leaf node and value
667        sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
668        assert!(matches!(
669            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
670            Ok(LeafLookup::Exists)
671        ));
672        assert_eq!(
673            sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
674            Some(&leaf_value)
675        );
676
677        // Remove the leaf node and check that the state trie does not contain the leaf node and
678        // value
679        apply_account_update(&mut sparse, B256::ZERO, LeafUpdate::Changed(Vec::new()));
680        assert!(matches!(
681            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
682            Ok(LeafLookup::NonExistent)
683        ));
684        assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
685    }
686
687    #[test]
688    fn reveal_storage_path_twice() {
689        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
690
691        // Full 64-nibble path
692        let full_path_0 = leaf_key([0x0], 64);
693
694        let leaf_value = alloy_rlp::encode(TrieAccount::default());
695        let leaf_1 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
696            leaf_key([], 63),
697            leaf_value.clone(),
698        )));
699        let leaf_2 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
700            leaf_key([], 63),
701            leaf_value.clone(),
702        )));
703
704        let multiproof = MultiProof {
705            storages: HashMap::from_iter([(
706                B256::ZERO,
707                StorageMultiProof {
708                    root: B256::ZERO,
709                    subtree: ProofNodes::from_iter([
710                        (
711                            Nibbles::default(),
712                            alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
713                                key: Nibbles::default(),
714                                stack: vec![RlpNode::from_rlp(&leaf_1), RlpNode::from_rlp(&leaf_2)],
715                                state_mask: TrieMask::new(0b11),
716                                branch_rlp_node: None,
717                            }))
718                            .into(),
719                        ),
720                        (Nibbles::from_nibbles([0x0]), leaf_1.clone().into()),
721                        (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
722                    ]),
723                    branch_node_masks: Default::default(),
724                },
725            )]),
726            ..Default::default()
727        };
728
729        // Reveal multiproof and check that the storage trie contains the leaf node and value
730        sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
731        assert!(matches!(
732            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
733            Ok(LeafLookup::Exists)
734        ));
735        assert_eq!(
736            sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
737            Some(&leaf_value)
738        );
739
740        // Remove the leaf node and check that the storage trie does not contain the leaf node and
741        // value
742        let mut updates = B256Map::from_iter([(B256::ZERO, LeafUpdate::Changed(Vec::new()))]);
743        sparse
744            .storage_trie_mut(&B256::ZERO)
745            .unwrap()
746            .update_leaves(&mut updates, |_, _| {})
747            .unwrap();
748        assert!(updates.is_empty());
749        assert!(matches!(
750            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
751            Ok(LeafLookup::NonExistent)
752        ));
753        assert!(sparse
754            .storage_trie_ref(&B256::ZERO)
755            .unwrap()
756            .get_leaf_value(&full_path_0)
757            .is_none());
758    }
759
760    #[test]
761    fn prune_uses_epochs_for_account_and_storage_tries() {
762        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
763
764        let account = B256::ZERO;
765        let old_account =
766            b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
767        let prewarmed_account =
768            b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
769        let slot = B256::ZERO;
770        let account_path = leaf_key([0x0], 64);
771        let old_account_path = leaf_key([0x1], 64);
772        let storage_path = leaf_key([0x0], 64);
773
774        let leaf_value = alloy_rlp::encode(TrieAccount::default());
775        let leaf_0 = alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(
776            leaf_key([], 63),
777            leaf_value.clone(),
778        )));
779        let leaf_1 =
780            alloy_rlp::encode(TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value)));
781
782        let subtree = || {
783            ProofNodes::from_iter([
784                (
785                    Nibbles::default(),
786                    alloy_rlp::encode(TrieNodeV2::Branch(BranchNodeV2 {
787                        key: Nibbles::default(),
788                        stack: vec![RlpNode::from_rlp(&leaf_0), RlpNode::from_rlp(&leaf_1)],
789                        state_mask: TrieMask::new(0b11),
790                        branch_rlp_node: None,
791                    }))
792                    .into(),
793                ),
794                (Nibbles::from_nibbles([0x0]), leaf_0.clone().into()),
795                (Nibbles::from_nibbles([0x1]), leaf_1.clone().into()),
796            ])
797        };
798
799        let multiproof = MultiProof {
800            account_subtree: subtree(),
801            storages: HashMap::from_iter([
802                (
803                    account,
804                    StorageMultiProof {
805                        root: B256::ZERO,
806                        subtree: subtree(),
807                        branch_node_masks: Default::default(),
808                    },
809                ),
810                (
811                    old_account,
812                    StorageMultiProof {
813                        root: B256::ZERO,
814                        subtree: subtree(),
815                        branch_node_masks: Default::default(),
816                    },
817                ),
818                (
819                    prewarmed_account,
820                    StorageMultiProof {
821                        root: B256::ZERO,
822                        subtree: subtree(),
823                        branch_node_masks: Default::default(),
824                    },
825                ),
826            ]),
827            ..Default::default()
828        };
829
830        sparse.reveal_decoded_multiproof(multiproof.try_into().unwrap()).unwrap();
831
832        sparse.storage_root(&account, epoch(0)).unwrap();
833        sparse.storage_root(&old_account, epoch(0)).unwrap();
834        sparse.root(epoch(0)).unwrap();
835        assert!(!sparse.storage_trie_ref(&prewarmed_account).unwrap().is_root_cached());
836
837        let mut storage_updates = B256Map::from_iter([(
838            slot,
839            LeafUpdate::Changed(alloy_rlp::encode_fixed_size(&U256::from(2)).to_vec()),
840        )]);
841        sparse
842            .storage_trie_mut(&account)
843            .unwrap()
844            .update_leaves(&mut storage_updates, |_, _| {
845                panic!("fully revealed storage trie must not request proofs")
846            })
847            .unwrap();
848        assert!(storage_updates.is_empty());
849
850        let trie_account = TrieAccount {
851            storage_root: sparse.storage_root(&account, epoch(10)).unwrap(),
852            ..Default::default()
853        };
854        apply_account_update(
855            &mut sparse,
856            account,
857            LeafUpdate::Changed(alloy_rlp::encode(trie_account)),
858        );
859        let root_before = sparse.root(epoch(10)).unwrap();
860        sparse.prune(epoch(10));
861
862        assert!(matches!(
863            sparse.state_trie_ref().unwrap().find_leaf(&account_path, None),
864            Ok(LeafLookup::Exists)
865        ));
866        assert!(matches!(
867            sparse.storage_trie_ref(&account).unwrap().find_leaf(&storage_path, None),
868            Ok(LeafLookup::Exists)
869        ));
870        assert!(matches!(
871            sparse.state_trie_ref().unwrap().find_leaf(&old_account_path, None),
872            Err(crate::LeafLookupError::BlindedNode { .. })
873        ));
874        assert!(sparse.storage_trie_ref(&old_account).is_none());
875        assert!(sparse.storage_trie_ref(&prewarmed_account).is_none());
876        assert_eq!(sparse.root(epoch(10)).unwrap(), root_before);
877
878        // An intermediate storage root does not advance without another change, so the trie
879        // becomes eligible once the cutoff passes its last modification epoch.
880        sparse.prune(epoch(11));
881        assert!(sparse.storage_trie_ref(&account).is_none());
882        assert_eq!(sparse.root(epoch(11)).unwrap(), root_before);
883    }
884
885    #[test]
886    fn reveal_v2_proof_nodes() {
887        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
888
889        // Full 64-nibble path
890        let full_path_0 = leaf_key([0x0], 64);
891
892        let leaf_value = alloy_rlp::encode(TrieAccount::default());
893        let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
894        let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), leaf_value.clone()));
895
896        let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
897            key: Nibbles::default(),
898            stack: vec![
899                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
900                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
901            ],
902            state_mask: TrieMask::new(0b11),
903            branch_rlp_node: None,
904        });
905
906        // Create V2 proof nodes with masks already included
907        let v2_proof_nodes = vec![
908            ProofTrieNodeV2 {
909                path: Nibbles::default(),
910                node: branch_node,
911                masks: Some(BranchNodeMasks {
912                    hash_mask: TrieMask::default(),
913                    tree_mask: TrieMask::default(),
914                }),
915            },
916            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
917            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
918        ];
919
920        // Reveal V2 proof nodes
921        sparse
922            .reveal_decoded_multiproof_v2(reth_trie_common::DecodedMultiProofV2 {
923                account_proofs: v2_proof_nodes,
924                ..Default::default()
925            })
926            .unwrap();
927
928        // Check that the state trie contains the leaf node and value
929        assert!(matches!(
930            sparse.state_trie_ref().unwrap().find_leaf(&full_path_0, None),
931            Ok(LeafLookup::Exists)
932        ));
933        assert_eq!(
934            sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0),
935            Some(&leaf_value)
936        );
937
938        // Remove the leaf node
939        apply_account_update(&mut sparse, B256::ZERO, LeafUpdate::Changed(Vec::new()));
940        assert!(sparse.state_trie_ref().unwrap().get_leaf_value(&full_path_0).is_none());
941    }
942
943    #[test]
944    fn reveal_storage_v2_proof_nodes() {
945        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
946
947        // Full 64-nibble path
948        let full_path_0 = leaf_key([0x0], 64);
949
950        let storage_value: Vec<u8> = alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec();
951        let leaf_1_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
952        let leaf_2_node = TrieNodeV2::Leaf(LeafNode::new(leaf_key([], 63), storage_value.clone()));
953
954        let branch_node = TrieNodeV2::Branch(BranchNodeV2 {
955            key: Nibbles::default(),
956            stack: vec![
957                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1_node)),
958                RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2_node)),
959            ],
960            state_mask: TrieMask::new(0b11),
961            branch_rlp_node: None,
962        });
963
964        let v2_proof_nodes = vec![
965            ProofTrieNodeV2 { path: Nibbles::default(), node: branch_node, masks: None },
966            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x0]), node: leaf_1_node, masks: None },
967            ProofTrieNodeV2 { path: Nibbles::from_nibbles([0x1]), node: leaf_2_node, masks: None },
968        ];
969
970        // Reveal V2 storage proof nodes for account
971        sparse
972            .reveal_decoded_multiproof_v2(reth_trie_common::DecodedMultiProofV2 {
973                storage_proofs: B256Map::from_iter([(B256::ZERO, v2_proof_nodes)]),
974                ..Default::default()
975            })
976            .unwrap();
977
978        // Check that the storage trie contains the leaf node and value
979        assert!(matches!(
980            sparse.storage_trie_ref(&B256::ZERO).unwrap().find_leaf(&full_path_0, None),
981            Ok(LeafLookup::Exists)
982        ));
983        assert_eq!(
984            sparse.storage_trie_ref(&B256::ZERO).unwrap().get_leaf_value(&full_path_0),
985            Some(&storage_value)
986        );
987
988        // Remove the leaf node
989        let mut updates = B256Map::from_iter([(B256::ZERO, LeafUpdate::Changed(Vec::new()))]);
990        sparse
991            .storage_trie_mut(&B256::ZERO)
992            .unwrap()
993            .update_leaves(&mut updates, |_, _| {})
994            .unwrap();
995        assert!(updates.is_empty());
996        assert!(sparse
997            .storage_trie_ref(&B256::ZERO)
998            .unwrap()
999            .get_leaf_value(&full_path_0)
1000            .is_none());
1001    }
1002
1003    #[test]
1004    fn root_on_blind_trie_returns_blind_error() {
1005        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default();
1006
1007        let err = sparse.root(epoch(0)).unwrap_err();
1008
1009        assert!(matches!(err.kind(), SparseStateTrieErrorKind::Sparse(SparseTrieErrorKind::Blind)));
1010    }
1011
1012    #[test]
1013    fn take_trie_updates() {
1014        reth_tracing::init_test_tracing();
1015
1016        // let mut rng = generators::rng();
1017        let mut rng = StdRng::seed_from_u64(1);
1018
1019        let mut bytes = [0u8; 1024];
1020        rng.fill(bytes.as_mut_slice());
1021
1022        let slot_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1023        let slot_path_1 = Nibbles::unpack(slot_1);
1024        let value_1 = U256::from(rng.random::<u64>());
1025        let slot_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1026        let slot_path_2 = Nibbles::unpack(slot_2);
1027        let value_2 = U256::from(rng.random::<u64>());
1028        let slot_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1029        let value_3 = U256::from(rng.random::<u64>());
1030
1031        let mut storage_hash_builder = HashBuilder::default()
1032            .with_proof_retainer(ProofRetainer::from_iter([slot_path_1, slot_path_2]));
1033        storage_hash_builder.add_leaf(slot_path_1, &alloy_rlp::encode_fixed_size(&value_1));
1034        storage_hash_builder.add_leaf(slot_path_2, &alloy_rlp::encode_fixed_size(&value_2));
1035
1036        let storage_root = storage_hash_builder.root();
1037        let storage_proof_nodes = storage_hash_builder.take_proof_nodes();
1038        let storage_branch_node_masks = BranchNodeMasksMap::from_iter([
1039            (
1040                Nibbles::default(),
1041                BranchNodeMasks { hash_mask: TrieMask::new(0b010), tree_mask: TrieMask::default() },
1042            ),
1043            (
1044                Nibbles::from_nibbles([0x1]),
1045                BranchNodeMasks { hash_mask: TrieMask::new(0b11), tree_mask: TrieMask::default() },
1046            ),
1047        ]);
1048
1049        let address_1 = b256!("0x1000000000000000000000000000000000000000000000000000000000000000");
1050        let address_path_1 = Nibbles::unpack(address_1);
1051        let account_1 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1052        let mut trie_account_1 = account_1.into_trie_account(storage_root);
1053        let address_2 = b256!("0x1100000000000000000000000000000000000000000000000000000000000000");
1054        let address_path_2 = Nibbles::unpack(address_2);
1055        let account_2 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
1056        let mut trie_account_2 = account_2.into_trie_account(EMPTY_ROOT_HASH);
1057
1058        let mut hash_builder = HashBuilder::default()
1059            .with_proof_retainer(ProofRetainer::from_iter([address_path_1, address_path_2]));
1060        hash_builder.add_leaf(address_path_1, &alloy_rlp::encode(trie_account_1));
1061        hash_builder.add_leaf(address_path_2, &alloy_rlp::encode(trie_account_2));
1062
1063        let root = hash_builder.root();
1064        let proof_nodes = hash_builder.take_proof_nodes();
1065        let mut sparse = SparseStateTrie::<ArenaParallelSparseTrie>::default().with_updates(true);
1066        sparse
1067            .reveal_decoded_multiproof(
1068                MultiProof {
1069                    account_subtree: proof_nodes,
1070                    branch_node_masks: BranchNodeMasksMap::from_iter([(
1071                        Nibbles::from_nibbles([0x1]),
1072                        BranchNodeMasks {
1073                            hash_mask: TrieMask::new(0b00),
1074                            tree_mask: TrieMask::default(),
1075                        },
1076                    )]),
1077                    storages: HashMap::from_iter([
1078                        (
1079                            address_1,
1080                            StorageMultiProof {
1081                                root,
1082                                subtree: storage_proof_nodes.clone(),
1083                                branch_node_masks: storage_branch_node_masks.clone(),
1084                            },
1085                        ),
1086                        (
1087                            address_2,
1088                            StorageMultiProof {
1089                                root,
1090                                subtree: storage_proof_nodes,
1091                                branch_node_masks: storage_branch_node_masks,
1092                            },
1093                        ),
1094                    ]),
1095                }
1096                .try_into()
1097                .unwrap(),
1098            )
1099            .unwrap();
1100
1101        assert_eq!(sparse.root(epoch(0)).unwrap(), root);
1102
1103        let address_3 = b256!("0x2000000000000000000000000000000000000000000000000000000000000000");
1104        let account_3 = Account { nonce: account_1.nonce + 1, ..account_1 };
1105        let trie_account_3 = account_3.into_trie_account(EMPTY_ROOT_HASH);
1106
1107        apply_account_update(
1108            &mut sparse,
1109            address_3,
1110            LeafUpdate::Changed(alloy_rlp::encode(trie_account_3)),
1111        );
1112
1113        let mut updates =
1114            B256Map::from_iter([(slot_3, LeafUpdate::Changed(alloy_rlp::encode(value_3)))]);
1115        sparse
1116            .storage_trie_mut(&address_1)
1117            .unwrap()
1118            .update_leaves(&mut updates, |_, _| {})
1119            .unwrap();
1120        assert!(updates.is_empty());
1121        trie_account_1.storage_root = sparse.storage_root(&address_1, epoch(0)).unwrap();
1122        apply_account_update(
1123            &mut sparse,
1124            address_1,
1125            LeafUpdate::Changed(alloy_rlp::encode(trie_account_1)),
1126        );
1127
1128        sparse.wipe_storage(address_2).unwrap();
1129        trie_account_2.storage_root = sparse.storage_root(&address_2, epoch(0)).unwrap();
1130        apply_account_update(
1131            &mut sparse,
1132            address_2,
1133            LeafUpdate::Changed(alloy_rlp::encode(trie_account_2)),
1134        );
1135
1136        sparse.root(epoch(0)).unwrap();
1137
1138        let sparse_updates = sparse.take_trie_updates().unwrap();
1139        // TODO(alexey): assert against real state root calculation updates
1140        pretty_assertions::assert_eq!(
1141            sparse_updates,
1142            TrieUpdates {
1143                account_nodes: HashMap::default(),
1144                storage_tries: HashMap::from_iter([
1145                    (
1146                        b256!("0x1000000000000000000000000000000000000000000000000000000000000000"),
1147                        StorageTrieUpdates {
1148                            is_deleted: false,
1149                            storage_nodes: HashMap::default(),
1150                            removed_nodes: HashSet::from_iter([Nibbles::from_nibbles([0x1])])
1151                        }
1152                    ),
1153                    (
1154                        b256!("0x1100000000000000000000000000000000000000000000000000000000000000"),
1155                        StorageTrieUpdates {
1156                            is_deleted: true,
1157                            storage_nodes: HashMap::default(),
1158                            removed_nodes: HashSet::default()
1159                        }
1160                    )
1161                ]),
1162                removed_nodes: HashSet::default()
1163            }
1164        );
1165    }
1166}