Skip to main content

reth_trie_sparse/
state.rs

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