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