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