Skip to main content

reth_storage_overlay/
manager.rs

1//! Flattened state trie overlays for in-memory blocks.
2//!
3//! Payload validation needs a view of the state trie as of an in-memory parent block even when that
4//! parent has not been persisted yet. [`OverlayManager`] tracks those in-memory blocks and
5//! builds reusable flattened state trie overlays on demand.
6
7use crate::{changeset_cache::compute_block_trie_updates, ChangesetCache, OverlayBuilder};
8use alloy_primitives::{BlockNumber, B256};
9use parking_lot::Mutex;
10use reth_chain_state::{ExecutedBlock, PreservedSparseTrie};
11use reth_errors::ProviderResult;
12use reth_ethereum_primitives::EthPrimitives;
13use reth_metrics::{
14    metrics::{Counter, Histogram},
15    Metrics,
16};
17use reth_primitives_traits::{
18    dashmap::{mapref::entry::Entry, DashMap},
19    AlloyBlockHeader, FastInstant, NodePrimitives,
20};
21use reth_storage_api::{
22    BlockNumReader, ChangeSetReader, DBProvider, StorageChangeSetReader, StorageSettingsCache,
23};
24#[cfg(feature = "rayon")]
25use reth_tasks::WorkerPool;
26use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, TrieInputSorted};
27use std::{
28    fmt,
29    ops::RangeInclusive,
30    sync::{Arc, OnceLock},
31    time::Instant,
32};
33use tracing::{debug, trace};
34
35/// Manages flattened state trie overlays for in-memory blocks.
36///
37/// The manager owns the in-memory block graph, changeset cache, and a cache of flattened state trie
38/// overlays keyed by `(anchor_hash, tip_hash)`.
39#[derive(Clone)]
40pub struct OverlayManager<N: NodePrimitives = EthPrimitives> {
41    blocks: Arc<DashMap<B256, ExecutedBlock<N>>>,
42    overlays: Arc<DashMap<OverlayCacheKey, OverlayCacheEntry>>,
43    changeset_cache: ChangesetCache,
44    preserved_sparse_trie: Arc<Mutex<Option<PreservedSparseTrie>>>,
45    #[cfg(feature = "rayon")]
46    worker_pool: Option<Arc<WorkerPool>>,
47    metrics: StateTrieOverlayMetrics,
48}
49
50/// Metrics for state trie overlay management.
51#[derive(Clone, Metrics)]
52#[metrics(scope = "sync.block_validation.state_trie_overlay")]
53struct StateTrieOverlayMetrics {
54    /// Duration of overlay computation in seconds.
55    overlay_computation_duration_seconds: Histogram,
56    /// Number of requests satisfied by an existing overlay cache entry.
57    overlay_cache_reuses: Counter,
58    /// Number of overlay cache entries populated by computing an overlay.
59    overlay_cache_fills: Counter,
60}
61
62impl<N: NodePrimitives> Default for OverlayManager<N> {
63    fn default() -> Self {
64        Self {
65            blocks: Default::default(),
66            overlays: Default::default(),
67            changeset_cache: Default::default(),
68            preserved_sparse_trie: Default::default(),
69            #[cfg(feature = "rayon")]
70            worker_pool: None,
71            metrics: Default::default(),
72        }
73    }
74}
75
76impl<N: NodePrimitives> std::fmt::Debug for OverlayManager<N> {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        f.debug_struct("OverlayManager")
79            .field("blocks", &self.blocks.len())
80            .field("overlays", &self.overlays.len())
81            .finish()
82    }
83}
84
85impl<N: NodePrimitives> OverlayManager<N> {
86    /// Create a new [`OverlayManager`] backed by the given worker pool.
87    #[cfg(feature = "rayon")]
88    pub fn new(worker_pool: Arc<WorkerPool>) -> Self {
89        Self {
90            blocks: Default::default(),
91            overlays: Default::default(),
92            changeset_cache: Default::default(),
93            preserved_sparse_trie: Default::default(),
94            worker_pool: Some(worker_pool),
95            metrics: Default::default(),
96        }
97    }
98
99    /// Creates an overlay builder for `parent_hash`.
100    pub fn overlay_builder(&self, parent_hash: B256) -> OverlayBuilder<N> {
101        OverlayBuilder::new(parent_hash, self.clone())
102    }
103
104    /// Gets or computes cached changesets for an inclusive block range.
105    pub fn get_or_compute_cached_changesets_range<P>(
106        &self,
107        provider: &P,
108        range: RangeInclusive<BlockNumber>,
109    ) -> ProviderResult<Arc<TrieUpdatesSorted>>
110    where
111        P: DBProvider
112            + ChangeSetReader
113            + StorageChangeSetReader
114            + BlockNumReader
115            + StorageSettingsCache,
116    {
117        self.changeset_cache.get_or_compute_range(provider, range)
118    }
119
120    /// Evicts cached changesets for blocks below `up_to_block`.
121    pub fn evict_cached_changesets(&self, up_to_block: BlockNumber) {
122        self.changeset_cache.evict(up_to_block);
123    }
124
125    /// Computes the trie updates produced by `block_number`.
126    pub fn compute_block_trie_updates<P>(
127        &self,
128        provider: &P,
129        block_number: BlockNumber,
130    ) -> ProviderResult<TrieUpdatesSorted>
131    where
132        P: DBProvider
133            + ChangeSetReader
134            + StorageChangeSetReader
135            + BlockNumReader
136            + StorageSettingsCache,
137    {
138        compute_block_trie_updates(&self.changeset_cache, provider, block_number)
139    }
140
141    /// Takes the preserved sparse trie if present.
142    pub fn take_sparse_trie(&self) -> Option<PreservedSparseTrie> {
143        self.preserved_sparse_trie.lock().take()
144    }
145
146    /// Stores a preserved sparse trie for later reuse.
147    pub fn store_sparse_trie(&self, trie: PreservedSparseTrie) {
148        *self.preserved_sparse_trie.lock() = Some(trie);
149    }
150
151    /// Clears any preserved sparse trie state.
152    pub fn clear_sparse_trie(&self) {
153        *self.preserved_sparse_trie.lock() = None;
154    }
155
156    /// Waits until the sparse trie lock becomes available.
157    ///
158    /// This acquires and immediately releases the lock, ensuring that any ongoing operations
159    /// complete before returning. Returns the time spent waiting for the lock.
160    pub fn wait_for_sparse_trie_availability(&self) -> std::time::Duration {
161        let start = FastInstant::now();
162        let _guard = self.preserved_sparse_trie.lock();
163        let elapsed = start.elapsed();
164        if elapsed.as_millis() > 5 {
165            debug!(
166                target: "storage::overlay::manager",
167                blocked_for=?elapsed,
168                "Waited for preserved sparse trie to become available"
169            );
170        }
171        elapsed
172    }
173
174    /// Inserts an executed in-memory block into the state trie overlay manager.
175    #[tracing::instrument(
176        level = "trace",
177        target = "storage::overlay::manager",
178        skip_all,
179        fields(
180            block_hash = %block.recovered_block().hash(),
181            parent_hash = %block.recovered_block().parent_hash(),
182            duplicate = false,
183        )
184    )]
185    pub fn insert_block(&self, block: ExecutedBlock<N>) {
186        let hash = block.recovered_block().hash();
187        let parent_hash = block.recovered_block().parent_hash();
188        let span = tracing::Span::current();
189
190        // First add the block to the live graph; duplicate inserts do not need cache work.
191        match self.blocks.entry(hash) {
192            Entry::Occupied(_) => {
193                span.record("duplicate", true);
194                debug!(
195                    target: "storage::overlay::manager",
196                    %hash,
197                    %parent_hash,
198                    "state trie overlay block already inserted"
199                );
200                return
201            }
202            Entry::Vacant(entry) => {
203                entry.insert(block);
204            }
205        }
206
207        debug!(
208            target: "storage::overlay::manager",
209            %hash,
210            %parent_hash,
211            "inserted block into state trie overlay manager"
212        );
213    }
214
215    /// Removes blocks from the live block graph and prunes cached overlays that can no longer be
216    /// built from the remaining blocks.
217    #[tracing::instrument(
218        level = "trace",
219        target = "storage::overlay::manager",
220        skip_all,
221        fields(
222            block_count = tracing::field::Empty,
223            removed_blocks = tracing::field::Empty,
224            pruned_overlays = tracing::field::Empty,
225        )
226    )]
227    pub fn remove_blocks(&self, hashes: impl IntoIterator<Item = B256>) {
228        let span = tracing::Span::current();
229
230        // Remove blocks first, then prune overlays against the remaining block graph.
231        let mut block_count = 0usize;
232        let mut removed_blocks = 0usize;
233        let mut pruned_overlays = 0usize;
234        for hash in hashes {
235            block_count += 1;
236            removed_blocks += self.blocks.remove(&hash).is_some() as usize;
237        }
238        span.record("block_count", block_count);
239        span.record("removed_blocks", removed_blocks);
240
241        if removed_blocks > 0 {
242            let overlays_before = self.overlays.len();
243            let blocks = Arc::clone(&self.blocks);
244            self.overlays.retain(|key, _| {
245                key.tip_hash != key.anchor_hash &&
246                    Self::anchor_for_parent_in(blocks.as_ref(), key.tip_hash, key.anchor_hash) ==
247                        Some(key.anchor_hash)
248            });
249            pruned_overlays = overlays_before.saturating_sub(self.overlays.len());
250            span.record("pruned_overlays", pruned_overlays);
251        }
252        debug!(
253            target: "storage::overlay::manager",
254            block_count,
255            removed_blocks,
256            pruned_overlays,
257            "removed blocks from state trie overlay manager"
258        );
259    }
260
261    /// Returns the flattened overlay from `anchor_hash` to `parent_hash`.
262    #[tracing::instrument(
263        level = "trace",
264        target = "storage::overlay::manager",
265        skip_all,
266        fields(tip_hash = %parent_hash, anchor_hash = %anchor_hash)
267    )]
268    pub fn overlay_for_parent(
269        &self,
270        parent_hash: B256,
271        anchor_hash: B256,
272    ) -> Result<(Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>), StateTrieOverlayError> {
273        debug!(
274            target: "storage::overlay::manager",
275            tip_hash = %parent_hash,
276            %anchor_hash,
277            "loading state trie overlay for parent"
278        );
279        let input = self.get_overlay(parent_hash, anchor_hash)?;
280        Ok((Arc::clone(&input.nodes), Arc::clone(&input.state)))
281    }
282
283    #[tracing::instrument(
284        level = "trace",
285        target = "storage::overlay::manager",
286        skip_all,
287        fields(
288            tip_hash = %tip_hash,
289            anchor_hash = %anchor_hash,
290            cache_reused = tracing::field::Empty,
291            block_count = tracing::field::Empty,
292            parent_overlay_reused = tracing::field::Empty,
293        )
294    )]
295    fn get_overlay(
296        &self,
297        tip_hash: B256,
298        anchor_hash: B256,
299    ) -> Result<Arc<TrieInputSorted>, StateTrieOverlayError> {
300        let key = OverlayCacheKey { anchor_hash, tip_hash };
301        let span = tracing::Span::current();
302
303        if let Some(entry) = self.overlays.get(&key).map(|entry| entry.value().clone()) {
304            self.record_overlay_cache_reuse(&span);
305            return Ok(match entry {
306                OverlayCacheEntry::Ready(input) => input,
307                OverlayCacheEntry::Computing(waiter) => waiter.wait(),
308            })
309        }
310        span.record("cache_reused", false);
311
312        // Resolve the block path and any cached parent overlay before locking the child entry.
313        let mut hash = tip_hash;
314        let mut blocks = Vec::new();
315        loop {
316            let block =
317                self.blocks.get(&hash).ok_or(StateTrieOverlayError { tip_hash, anchor_hash })?;
318            let parent_hash = block.recovered_block().parent_hash();
319            blocks.push(block.clone());
320
321            if parent_hash == anchor_hash {
322                break
323            }
324            hash = parent_hash;
325        }
326        span.record("block_count", blocks.len());
327        let parent_input = blocks.first().and_then(|block| {
328            let parent_hash = block.recovered_block().parent_hash();
329            (parent_hash != anchor_hash)
330                .then(|| {
331                    self.overlays
332                        .get(&OverlayCacheKey { anchor_hash, tip_hash: parent_hash })
333                        .and_then(|entry| entry.value().ready())
334                })
335                .flatten()
336        });
337        span.record("parent_overlay_reused", parent_input.is_some());
338        let compute_input = match parent_input {
339            Some(parent_input) => {
340                ComputeOverlayInput::ExtendCached { block: blocks.swap_remove(0), parent_input }
341            }
342            None => ComputeOverlayInput::MergeBlocks(blocks),
343        };
344
345        enum CacheAction {
346            Ready(Arc<TrieInputSorted>),
347            Wait(Arc<OverlayWaiter>),
348            Compute(Arc<OverlayWaiter>),
349        }
350
351        let action = match self.overlays.entry(key) {
352            Entry::Occupied(entry) => {
353                let entry = entry.get().clone();
354                self.record_overlay_cache_reuse(&span);
355                match entry {
356                    OverlayCacheEntry::Ready(input) => CacheAction::Ready(input),
357                    OverlayCacheEntry::Computing(waiter) => CacheAction::Wait(waiter),
358                }
359            }
360            Entry::Vacant(entry) => {
361                self.metrics.overlay_cache_fills.increment(1);
362                let waiter = Arc::new(OverlayWaiter::new());
363                entry.insert(OverlayCacheEntry::Computing(Arc::clone(&waiter)));
364                CacheAction::Compute(waiter)
365            }
366        };
367
368        match action {
369            CacheAction::Ready(input) => Ok(input),
370            CacheAction::Wait(waiter) => Ok(waiter.wait()),
371            CacheAction::Compute(waiter) => {
372                let input = self.compute_overlay(compute_input, anchor_hash, span);
373                waiter.finish(Arc::clone(&input));
374
375                if let Entry::Occupied(mut entry) = self.overlays.entry(key) {
376                    // The entry may have been pruned while the overlay was computing. Only cache
377                    // the result if the map still points at the waiter installed by this task.
378                    let should_publish = match entry.get() {
379                        OverlayCacheEntry::Computing(existing) => Arc::ptr_eq(existing, &waiter),
380                        OverlayCacheEntry::Ready(_) => false,
381                    };
382                    if should_publish {
383                        entry.insert(OverlayCacheEntry::Ready(Arc::clone(&input)));
384                    }
385                }
386
387                Ok(input)
388            }
389        }
390    }
391
392    fn record_overlay_cache_reuse(&self, span: &tracing::Span) {
393        self.metrics.overlay_cache_reuses.increment(1);
394        span.record("cache_reused", true);
395    }
396
397    /// Returns `preferred_anchor` if it is on the parent chain, otherwise the first missing parent.
398    ///
399    /// Returns `None` if `parent_hash` is not `preferred_anchor` and the manager does not contain a
400    /// block for `parent_hash`, meaning there is no in-memory parent chain to inspect.
401    pub fn anchor_for_parent(&self, parent_hash: B256, preferred_anchor: B256) -> Option<B256> {
402        Self::anchor_for_parent_in(self.blocks.as_ref(), parent_hash, preferred_anchor)
403    }
404
405    /// Returns true if `hash` is in the parent chain segment from `anchor_hash` inclusive to
406    /// `parent_hash` inclusive.
407    pub fn contains_hash(&self, parent_hash: B256, anchor_hash: B256, hash: B256) -> bool {
408        let mut current_hash = parent_hash;
409
410        loop {
411            if current_hash == hash {
412                return true
413            }
414            if current_hash == anchor_hash {
415                return false
416            }
417
418            let Some(block) = self.blocks.get(&current_hash) else { return false };
419            current_hash = block.recovered_block().parent_hash();
420        }
421    }
422
423    fn anchor_for_parent_in(
424        blocks: &DashMap<B256, ExecutedBlock<N>>,
425        parent_hash: B256,
426        preferred_anchor: B256,
427    ) -> Option<B256> {
428        if parent_hash == preferred_anchor {
429            return Some(preferred_anchor)
430        }
431
432        let mut hash = parent_hash;
433
434        loop {
435            let block_parent_hash = blocks.get(&hash)?.recovered_block().parent_hash();
436            if block_parent_hash == preferred_anchor {
437                return Some(block_parent_hash)
438            }
439            if !blocks.contains_key(&block_parent_hash) {
440                return Some(block_parent_hash)
441            }
442            hash = block_parent_hash;
443        }
444    }
445
446    fn compute_overlay(
447        &self,
448        compute_input: ComputeOverlayInput<N>,
449        anchor_hash: B256,
450        _span: tracing::Span,
451    ) -> Arc<TrieInputSorted> {
452        #[cfg(feature = "rayon")]
453        {
454            if let Some(worker_pool) = &self.worker_pool {
455                let compute_span = _span;
456                let metrics = self.metrics.clone();
457                return Arc::new(worker_pool.install_fn(move || {
458                    let _guard = compute_span.enter();
459                    compute_overlay(compute_input, anchor_hash, &metrics)
460                }))
461            }
462        }
463
464        Arc::new(compute_overlay(compute_input, anchor_hash, &self.metrics))
465    }
466}
467
468/// Error returned when a state trie overlay cannot be built from the manager's current block set.
469#[derive(Debug)]
470pub struct StateTrieOverlayError {
471    /// Requested in-memory tip hash.
472    tip_hash: B256,
473    /// Requested anchor hash.
474    anchor_hash: B256,
475}
476
477impl fmt::Display for StateTrieOverlayError {
478    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479        write!(
480            f,
481            "state trie overlay for tip {} cannot be anchored to {} with current blocks",
482            self.tip_hash, self.anchor_hash
483        )
484    }
485}
486
487impl std::error::Error for StateTrieOverlayError {}
488
489#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
490struct OverlayCacheKey {
491    anchor_hash: B256,
492    tip_hash: B256,
493}
494
495#[derive(Clone)]
496enum OverlayCacheEntry {
497    Ready(Arc<TrieInputSorted>),
498    Computing(Arc<OverlayWaiter>),
499}
500
501impl OverlayCacheEntry {
502    fn ready(&self) -> Option<Arc<TrieInputSorted>> {
503        match self {
504            Self::Ready(input) => Some(Arc::clone(input)),
505            Self::Computing(_) => None,
506        }
507    }
508}
509
510struct OverlayWaiter {
511    input: OnceLock<Arc<TrieInputSorted>>,
512}
513
514impl OverlayWaiter {
515    const fn new() -> Self {
516        Self { input: OnceLock::new() }
517    }
518
519    fn wait(&self) -> Arc<TrieInputSorted> {
520        Arc::clone(self.input.wait())
521    }
522
523    fn finish(&self, computed: Arc<TrieInputSorted>) {
524        let _ = self.input.set(computed);
525    }
526}
527
528enum ComputeOverlayInput<N: NodePrimitives> {
529    ExtendCached { block: ExecutedBlock<N>, parent_input: Arc<TrieInputSorted> },
530    MergeBlocks(Vec<ExecutedBlock<N>>),
531}
532
533#[tracing::instrument(
534    level = "trace",
535    target = "storage::overlay::manager",
536    skip_all,
537    fields(
538        anchor_hash = %anchor_hash,
539        block_count = tracing::field::Empty,
540        parent_overlay = tracing::field::Empty,
541        elapsed_us = tracing::field::Empty,
542    )
543)]
544fn compute_overlay<N: NodePrimitives>(
545    input: ComputeOverlayInput<N>,
546    anchor_hash: B256,
547    metrics: &StateTrieOverlayMetrics,
548) -> TrieInputSorted {
549    let started_at = Instant::now();
550    let block_count = match &input {
551        ComputeOverlayInput::ExtendCached { .. } => 1,
552        ComputeOverlayInput::MergeBlocks(blocks) => blocks.len(),
553    };
554    let parent_overlay = matches!(&input, ComputeOverlayInput::ExtendCached { .. });
555    tracing::Span::current().record("block_count", block_count);
556    tracing::Span::current().record("parent_overlay", parent_overlay);
557
558    let overlay = match input {
559        ComputeOverlayInput::ExtendCached { block, parent_input } => {
560            let trie_data = block.trie_data();
561
562            trace!(
563                target: "storage::overlay::manager",
564                %anchor_hash,
565                head = %block.recovered_block().hash(),
566                "extending cached parent state trie overlay"
567            );
568
569            let mut overlay = parent_input.as_ref().clone();
570            extend_overlay(
571                &mut overlay,
572                &trie_data.sorted.hashed_state,
573                &trie_data.sorted.trie_updates,
574            );
575            overlay
576        }
577        ComputeOverlayInput::MergeBlocks(blocks) => merge_blocks(blocks),
578    };
579
580    let elapsed = started_at.elapsed();
581    metrics.overlay_computation_duration_seconds.record(elapsed.as_secs_f64());
582    tracing::Span::current().record("elapsed_us", elapsed.as_micros() as u64);
583    debug!(
584        target: "storage::overlay::manager",
585        %anchor_hash,
586        block_count,
587        parent_overlay,
588        ?elapsed,
589        "computed state trie overlay"
590    );
591
592    overlay
593}
594
595fn merge_blocks<N: NodePrimitives>(blocks: Vec<ExecutedBlock<N>>) -> TrieInputSorted {
596    let trie_data = blocks.iter().map(ExecutedBlock::trie_data).collect::<Vec<_>>();
597
598    #[cfg(feature = "rayon")]
599    let (nodes, state) = rayon::join(
600        || {
601            TrieUpdatesSorted::merge_batch(
602                trie_data.iter().map(|data| Arc::clone(&data.sorted.trie_updates)),
603            )
604        },
605        || {
606            HashedPostStateSorted::merge_batch(
607                trie_data.iter().map(|data| Arc::clone(&data.sorted.hashed_state)),
608            )
609        },
610    );
611
612    #[cfg(not(feature = "rayon"))]
613    let (nodes, state) = (
614        TrieUpdatesSorted::merge_batch(
615            trie_data.iter().map(|data| Arc::clone(&data.sorted.trie_updates)),
616        ),
617        HashedPostStateSorted::merge_batch(
618            trie_data.iter().map(|data| Arc::clone(&data.sorted.hashed_state)),
619        ),
620    );
621
622    TrieInputSorted::new(nodes, state, Default::default())
623}
624
625fn extend_overlay(
626    overlay: &mut TrieInputSorted,
627    hashed_state: &HashedPostStateSorted,
628    trie_updates: &TrieUpdatesSorted,
629) {
630    #[cfg(feature = "rayon")]
631    {
632        rayon::join(
633            || {
634                if !hashed_state.is_empty() {
635                    Arc::make_mut(&mut overlay.state).extend_ref_and_sort(hashed_state);
636                }
637            },
638            || {
639                if !trie_updates.is_empty() {
640                    Arc::make_mut(&mut overlay.nodes).extend_ref_and_sort(trie_updates);
641                }
642            },
643        );
644    }
645
646    #[cfg(not(feature = "rayon"))]
647    {
648        if !hashed_state.is_empty() {
649            Arc::make_mut(&mut overlay.state).extend_ref_and_sort(hashed_state);
650        }
651        if !trie_updates.is_empty() {
652            Arc::make_mut(&mut overlay.nodes).extend_ref_and_sort(trie_updates);
653        }
654    }
655}
656
657#[cfg(test)]
658mod tests {
659    use super::*;
660    use alloy_primitives::U256;
661    use reth_chain_state::{test_utils::TestBlockBuilder, ExecutedBlock, SparseTrie};
662    use reth_ethereum_primitives::EthPrimitives;
663    use reth_primitives_traits::Account;
664    use reth_trie::{updates::TrieUpdatesSorted, ComputedTrieData, HashedPostState, HashedStorage};
665    use std::{
666        sync::{mpsc, Arc},
667        thread,
668        time::Duration,
669    };
670
671    fn with_unique_state(
672        block: &ExecutedBlock<EthPrimitives>,
673        id: u8,
674    ) -> ExecutedBlock<EthPrimitives> {
675        let hashed_address = B256::with_last_byte(id);
676        let hashed_slot = B256::with_last_byte(id.saturating_add(32));
677        let hashed_state = HashedPostState::default()
678            .with_accounts([(hashed_address, Some(Account::default()))])
679            .with_storages([(
680                hashed_address,
681                HashedStorage::from_iter(false, [(hashed_slot, U256::from(id))]),
682            )])
683            .into_sorted();
684
685        ExecutedBlock::new(
686            Arc::clone(&block.recovered_block),
687            Arc::clone(&block.execution_output),
688            ComputedTrieData::new(Arc::new(hashed_state), Arc::new(TrieUpdatesSorted::default())),
689        )
690    }
691
692    fn test_blocks() -> Vec<ExecutedBlock<EthPrimitives>> {
693        TestBlockBuilder::eth()
694            .get_executed_blocks(1..4)
695            .enumerate()
696            .map(|(index, block)| with_unique_state(&block, index as u8 + 1))
697            .collect()
698    }
699
700    #[test]
701    fn errors_for_unknown_parent() {
702        let manager = OverlayManager::<EthPrimitives>::default();
703        let parent = B256::random();
704        let anchor = B256::random();
705
706        let err = manager.overlay_for_parent(parent, anchor).unwrap_err();
707
708        assert_eq!(err.tip_hash, parent);
709        assert_eq!(err.anchor_hash, anchor);
710    }
711
712    #[test]
713    fn builds_managed_overlay_for_inserted_blocks() {
714        let manager = OverlayManager::default();
715        let blocks = test_blocks();
716        for block in &blocks {
717            manager.insert_block(block.clone());
718        }
719
720        let anchor_hash = blocks[0].recovered_block().parent_hash();
721
722        let (_, state) =
723            manager.overlay_for_parent(blocks[2].recovered_block().hash(), anchor_hash).unwrap();
724        assert_eq!(state.accounts.len(), 3);
725
726        let short_anchor = blocks[1].recovered_block().hash();
727        let (_, short) =
728            manager.overlay_for_parent(blocks[2].recovered_block().hash(), short_anchor).unwrap();
729        assert_eq!(short.accounts.len(), 1);
730        let (_, cached_short) =
731            manager.overlay_for_parent(blocks[2].recovered_block().hash(), short_anchor).unwrap();
732        assert!(Arc::ptr_eq(&short, &cached_short));
733    }
734
735    #[test]
736    fn returns_anchor_for_in_memory_parent() {
737        let manager = OverlayManager::default();
738        let blocks = test_blocks();
739        for block in &blocks {
740            manager.insert_block(block.clone());
741        }
742
743        assert_eq!(
744            manager.anchor_for_parent(blocks[2].recovered_block().hash(), B256::random()),
745            Some(blocks[0].recovered_block().parent_hash())
746        );
747
748        manager.remove_blocks([blocks[0].recovered_block().hash()]);
749        assert_eq!(
750            manager.anchor_for_parent(
751                blocks[2].recovered_block().hash(),
752                blocks[0].recovered_block().hash()
753            ),
754            Some(blocks[0].recovered_block().hash())
755        );
756    }
757
758    #[test]
759    fn prefers_anchor_in_parent_chain() {
760        let manager = OverlayManager::default();
761        let blocks = test_blocks();
762        for block in &blocks {
763            manager.insert_block(block.clone());
764        }
765
766        let db_tip_hash = blocks[1].recovered_block().hash();
767        assert_eq!(
768            manager.anchor_for_parent(blocks[2].recovered_block().hash(), db_tip_hash),
769            Some(db_tip_hash)
770        );
771    }
772
773    #[test]
774    fn contains_hash_detects_hashes_from_anchor_to_parent() {
775        let manager = OverlayManager::default();
776        let blocks = test_blocks();
777        for block in &blocks {
778            manager.insert_block(block.clone());
779        }
780
781        let anchor_hash = blocks[0].recovered_block().parent_hash();
782        let parent_hash = blocks[2].recovered_block().hash();
783
784        assert!(manager.contains_hash(parent_hash, anchor_hash, anchor_hash));
785        for block in &blocks {
786            assert!(manager.contains_hash(
787                parent_hash,
788                anchor_hash,
789                block.recovered_block().hash()
790            ));
791        }
792        assert!(!manager.contains_hash(parent_hash, anchor_hash, B256::random()));
793    }
794
795    #[test]
796    fn contains_hash_rejects_hash_before_anchor() {
797        let manager = OverlayManager::default();
798        let blocks = test_blocks();
799        for block in &blocks {
800            manager.insert_block(block.clone());
801        }
802
803        let parent_hash = blocks[2].recovered_block().hash();
804        let anchor_hash = blocks[1].recovered_block().hash();
805        let before_anchor_hash = blocks[0].recovered_block().hash();
806
807        assert!(manager.contains_hash(parent_hash, anchor_hash, parent_hash));
808        assert!(manager.contains_hash(parent_hash, anchor_hash, anchor_hash));
809        assert!(!manager.contains_hash(parent_hash, anchor_hash, before_anchor_hash));
810    }
811
812    #[test]
813    fn contains_hash_rejects_unknown_anchor() {
814        let manager = OverlayManager::default();
815        let blocks = test_blocks();
816        for block in &blocks {
817            manager.insert_block(block.clone());
818        }
819
820        let parent_hash = blocks[2].recovered_block().hash();
821        let anchor_hash = B256::random();
822
823        assert!(!manager.contains_hash(parent_hash, anchor_hash, anchor_hash));
824    }
825
826    #[test]
827    fn taking_sparse_trie_removes_it() {
828        let manager = OverlayManager::<EthPrimitives>::default();
829        let state_root = B256::with_last_byte(1);
830        let other_state_root = B256::with_last_byte(2);
831        let anchor_hash = B256::with_last_byte(3);
832
833        manager.store_sparse_trie(PreservedSparseTrie::anchored(
834            SparseTrie::default(),
835            state_root,
836            anchor_hash,
837        ));
838
839        let preserved = manager.take_sparse_trie().expect("preserved trie should be available");
840        assert_eq!(preserved.state_root(), state_root);
841        assert_eq!(preserved.anchor_hash(), anchor_hash);
842        assert!(preserved.into_trie_for(other_state_root).unwrap().is_none());
843        assert!(manager.take_sparse_trie().is_none());
844    }
845
846    #[test]
847    fn required_lookup_waits_for_in_progress_overlay() {
848        let manager = OverlayManager::<EthPrimitives>::default();
849        let key = OverlayCacheKey {
850            anchor_hash: B256::with_last_byte(1),
851            tip_hash: B256::with_last_byte(2),
852        };
853        let waiter = Arc::new(OverlayWaiter::new());
854        manager.overlays.insert(key, OverlayCacheEntry::Computing(Arc::clone(&waiter)));
855
856        let (tx, rx) = mpsc::channel();
857        thread::spawn(move || {
858            let res =
859                manager.overlay_for_parent(key.tip_hash, key.anchor_hash).map(|(_, state)| state);
860            tx.send(res).unwrap();
861        });
862
863        assert!(matches!(
864            rx.recv_timeout(Duration::from_millis(50)),
865            Err(mpsc::RecvTimeoutError::Timeout)
866        ));
867
868        waiter.finish(Arc::new(TrieInputSorted::default()));
869
870        let state = rx.recv_timeout(Duration::from_secs(1)).unwrap().unwrap();
871        assert!(state.is_empty());
872    }
873
874    #[test]
875    fn prunes_cached_overlays_after_removing_blocks() {
876        let manager = OverlayManager::default();
877        let blocks = test_blocks();
878        for block in &blocks {
879            manager.insert_block(block.clone());
880        }
881
882        let original_anchor = blocks[0].recovered_block().parent_hash();
883        manager.overlay_for_parent(blocks[2].recovered_block().hash(), original_anchor).unwrap();
884
885        manager.remove_blocks([
886            blocks[0].recovered_block().hash(),
887            blocks[1].recovered_block().hash(),
888        ]);
889
890        let anchor_hash = blocks[1].recovered_block().hash();
891        assert!(manager
892            .overlay_for_parent(blocks[2].recovered_block().hash(), original_anchor)
893            .is_err());
894
895        let (_, state) =
896            manager.overlay_for_parent(blocks[2].recovered_block().hash(), anchor_hash).unwrap();
897        assert_eq!(state.accounts.len(), 1);
898    }
899}