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