1use crate::{
2 manager::{OverlayCacheConfig, StateTrieOverlayError},
3 OverlayManager,
4};
5use alloy_eips::BlockNumHash;
6use alloy_primitives::{
7 map::{AddressMap, AddressSet, B256Map, U256Map},
8 Address, BlockHash, BlockNumber, B256, U256,
9};
10use metrics::{Counter, Histogram};
11use reth_chain_state::{BlockState, ExecutedBlock};
12use reth_errors::{ProviderError, ProviderResult};
13use reth_ethereum_primitives::EthPrimitives;
14use reth_metrics::Metrics;
15use reth_primitives_traits::{AlloyBlockHeader, NodePrimitives};
16use reth_prune_types::PruneSegment;
17use reth_stages_types::StageId;
18use reth_storage_api::{
19 BlockNumReader, ChangeSetReader, DBProvider, PruneCheckpointReader, StageCheckpointReader,
20 StorageChangeSetReader, StorageSettingsCache,
21};
22use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, TrieInputSorted};
23use reth_trie_db::DatabaseHashedPostState;
24use revm::{bytecode::Bytecode, database::BundleState, state::AccountInfo};
25use std::{
26 ops::RangeInclusive,
27 sync::Arc,
28 time::{Duration, Instant},
29};
30use tracing::{debug, debug_span, instrument};
31
32#[derive(Debug, Clone)]
34pub struct StateTrieOverlay {
35 input: TrieInputSorted,
36 skipped_for_reused_sparse_trie: bool,
38}
39
40impl StateTrieOverlay {
41 pub(crate) const fn new(input: TrieInputSorted) -> Self {
42 Self { input, skipped_for_reused_sparse_trie: false }
43 }
44
45 fn empty() -> Self {
46 Self { input: TrieInputSorted::default(), skipped_for_reused_sparse_trie: true }
47 }
48
49 pub const fn input(&self) -> &TrieInputSorted {
51 &self.input
52 }
53
54 pub(crate) const fn skipped_for_reused_sparse_trie(&self) -> bool {
55 self.skipped_for_reused_sparse_trie
56 }
57}
58
59#[derive(Clone, Debug, Default)]
64pub struct ExecutionOverlay {
65 block_hashes: Vec<BlockNumHash>,
67 accounts: AddressMap<Option<AccountInfo>>,
69 storage: AddressMap<U256Map<U256>>,
71 storage_wipes: AddressSet,
76 code_hashes: B256Map<Bytecode>,
78}
79
80impl ExecutionOverlay {
81 pub const fn block_hashes(&self) -> &[BlockNumHash] {
83 self.block_hashes.as_slice()
84 }
85
86 pub const fn accounts(&self) -> &AddressMap<Option<AccountInfo>> {
88 &self.accounts
89 }
90
91 pub const fn storage(&self) -> &AddressMap<U256Map<U256>> {
93 &self.storage
94 }
95
96 pub(crate) fn storage_value(&self, address: Address, slot: U256) -> Option<U256> {
99 self.storage
100 .get(&address)
101 .and_then(|storage| storage.get(&slot))
102 .copied()
103 .or_else(|| self.storage_wipes.contains(&address).then_some(U256::ZERO))
104 }
105
106 pub const fn code_hashes(&self) -> &B256Map<Bytecode> {
108 &self.code_hashes
109 }
110
111 #[cfg(test)]
112 pub(crate) const fn block_hashes_mut(&mut self) -> &mut Vec<BlockNumHash> {
113 &mut self.block_hashes
114 }
115
116 #[cfg(test)]
117 pub(crate) const fn accounts_mut(&mut self) -> &mut AddressMap<Option<AccountInfo>> {
118 &mut self.accounts
119 }
120
121 #[cfg(test)]
122 pub(crate) const fn storage_mut(&mut self) -> &mut AddressMap<U256Map<U256>> {
123 &mut self.storage
124 }
125
126 #[cfg(test)]
127 pub(crate) const fn code_hashes_mut(&mut self) -> &mut B256Map<Bytecode> {
128 &mut self.code_hashes
129 }
130
131 pub(crate) fn extend_block<N: NodePrimitives>(&mut self, block: &ExecutedBlock<N>) {
133 self.block_hashes.push(block.recovered_block().num_hash());
134 self.extend_state(&block.execution_output.state);
135 }
136
137 fn extend_state(&mut self, state: &BundleState) {
143 let (accounts, storage, storage_wipes, code_hashes) =
144 (&mut self.accounts, &mut self.storage, &mut self.storage_wipes, &mut self.code_hashes);
145
146 #[allow(unused_mut)]
147 let mut extend_accounts_and_storage = || {
148 for (address, account) in state.state() {
149 accounts.insert(*address, Self::normalized_account_info(account.info.clone()));
150 if account.was_destroyed() {
151 storage_wipes.insert(*address);
152 storage.remove(address);
153 }
154 let account_storage = storage.entry(*address).or_default();
155 for (slot, value) in &account.storage {
156 account_storage.insert(*slot, value.present_value);
157 }
158 }
159 };
160 #[allow(unused_mut)]
161 let mut extend_code_hashes = || {
162 code_hashes.extend(state.contracts.iter().map(|(hash, code)| (*hash, code.clone())));
163 };
164
165 #[cfg(feature = "rayon")]
166 rayon::join(extend_accounts_and_storage, extend_code_hashes);
167
168 #[cfg(not(feature = "rayon"))]
169 {
170 extend_accounts_and_storage();
171 extend_code_hashes();
172 }
173 }
174
175 const fn normalized_account_info(mut info: Option<AccountInfo>) -> Option<AccountInfo> {
181 if let Some(info) = &mut info {
182 info.account_id = None;
183 }
184 info
185 }
186}
187
188#[derive(Debug, Clone)]
193pub struct OverlayBuilder<N: NodePrimitives = EthPrimitives> {
194 parent_hash: B256,
196 overlay_manager: OverlayManager<N>,
198 parent_state: Option<Arc<BlockState<N>>>,
203 reused_sparse_trie_anchor_hash: Option<B256>,
205 no_reverts: bool,
207 overlay_cache_config: OverlayCacheConfig,
209 metrics: OverlayBuilderMetrics,
211}
212
213impl<N: NodePrimitives> OverlayBuilder<N> {
214 pub(crate) fn new(
216 parent_hash: B256,
217 parent_state: Option<Arc<BlockState<N>>>,
218 overlay_manager: OverlayManager<N>,
219 ) -> Self {
220 Self {
221 parent_hash,
222 overlay_manager,
223 parent_state,
224 reused_sparse_trie_anchor_hash: None,
225 no_reverts: false,
226 overlay_cache_config: OverlayCacheConfig::default(),
227 metrics: OverlayBuilderMetrics::default(),
228 }
229 }
230
231 pub(crate) const fn with_skip_overlay_for_reused_sparse_trie(
234 mut self,
235 anchor_hash: B256,
236 ) -> Self {
237 self.reused_sparse_trie_anchor_hash = Some(anchor_hash);
238 self
239 }
240
241 pub(crate) const fn with_no_reverts(mut self) -> Self {
243 self.no_reverts = true;
244 self
245 }
246
247 pub fn with_appended_block(mut self, block: ExecutedBlock<N>) -> Self {
249 debug_assert_eq!(block.recovered_block().parent_hash(), self.parent_hash);
250 self.parent_hash = block.recovered_block().hash();
251 self.parent_state =
252 Some(Arc::new(BlockState::with_parent(block, self.parent_state.take())));
253 self.reused_sparse_trie_anchor_hash = None;
254 self.overlay_cache_config.write_to_cache = false;
255 self
256 }
257
258 #[cfg(test)]
260 fn anchor_at_parent<Provider>(&self, provider: &Provider) -> ProviderResult<AnchorForParent>
261 where
262 Provider: StageCheckpointReader + BlockNumReader + PruneCheckpointReader,
263 {
264 let (partial_state_trie, finish) = database_state_frontiers(provider)?;
265 self.anchor_at_parent_with_frontiers(provider, partial_state_trie, finish)
266 }
267
268 fn anchor_at_parent_with_frontiers<Provider>(
270 &self,
271 provider: &Provider,
272 partial_state_trie: BlockNumHash,
273 finish: BlockNumHash,
274 ) -> ProviderResult<AnchorForParent>
275 where
276 Provider: BlockNumReader + PruneCheckpointReader,
277 {
278 use std::io::Error;
279
280 let mut in_mem_chain = self
281 .parent_state
282 .iter()
283 .flat_map(|state| state.chain())
284 .map(BlockState::block_ref)
285 .peekable();
286 let persisted_parent = match in_mem_chain
287 .peek()
288 .filter(|block| block.recovered_block().hash() == self.parent_hash)
289 .map(|block| block.recovered_block().number())
290 {
291 Some(parent_number) if parent_number > partial_state_trie.number => None,
292 Some(parent_number)
293 if parent_number == partial_state_trie.number &&
294 self.parent_hash == partial_state_trie.hash =>
295 {
296 Some(parent_number)
297 }
298 Some(parent_number) => (provider.block_hash(parent_number)? == Some(self.parent_hash))
299 .then_some(parent_number),
300 None if self.parent_hash == partial_state_trie.hash => Some(partial_state_trie.number),
301 None => provider
302 .block_number(self.parent_hash)?
303 .filter(|&number| number <= partial_state_trie.number),
304 };
305
306 let mut finish_seen = self.parent_hash == finish.hash;
307 let anchor = if let Some(parent_number) = persisted_parent {
308 BlockNumHash::new(parent_number, self.parent_hash)
309 } else {
310 let mut in_mem_chain = in_mem_chain.inspect(|block| {
311 finish_seen |= block.recovered_block().hash() == finish.hash;
312 });
313
314 if let Some(anchor) =
315 anchor_for_parent_in(self.parent_hash, &mut in_mem_chain, partial_state_trie)
316 {
317 anchor
318 } else {
319 let anchor_number = provider
320 .convert_hash_or_number(self.parent_hash.into())?
321 .ok_or(ProviderError::BlockHashNotFound(self.parent_hash))?;
322 BlockNumHash::new(anchor_number, self.parent_hash)
323 }
324 };
325
326 finish_seen |= anchor.hash == finish.hash;
327
328 if anchor.number > partial_state_trie.number {
329 return Err(ProviderError::other(Error::other(format!(
330 "overlay anchor #{} ({}) is after partial state trie frontier #{} ({}); missing trie updates for blocks #{}..=#{}",
331 anchor.number,
332 anchor.hash,
333 partial_state_trie.number,
334 partial_state_trie.hash,
335 partial_state_trie.number + 1,
336 anchor.number,
337 ))))
338 }
339
340 if finish_seen {
345 return Ok(AnchorForParent::NoReverts { anchor })
346 }
347
348 if provider.block_hash(anchor.number)? != Some(anchor.hash) {
351 return Err(ProviderError::BlockHashNotFound(anchor.hash))
352 }
353
354 let account_history = provider
357 .get_prune_checkpoint(PruneSegment::AccountHistory)?
358 .and_then(|checkpoint| checkpoint.block_number);
359 let storage_history = provider
360 .get_prune_checkpoint(PruneSegment::StorageHistory)?
361 .and_then(|checkpoint| checkpoint.block_number);
362 let lower_bound = account_history.max(storage_history).unwrap_or_default();
363 let available_range = lower_bound..=finish.number;
364 if !available_range.contains(&anchor.number) {
365 return Err(ProviderError::InsufficientChangesets {
366 requested: anchor.number,
367 available: available_range,
368 })
369 }
370
371 Ok(AnchorForParent::RevertsRequired { anchor, finish })
372 }
373
374 #[cfg(test)]
378 #[instrument(level = "debug", target = "storage::overlay", skip_all)]
379 fn build_state_trie_overlay<Provider>(
380 &self,
381 provider: &Provider,
382 trie_changesets: bool,
383 ) -> ProviderResult<StateTrieOverlay>
384 where
385 Provider: StageCheckpointReader
386 + PruneCheckpointReader
387 + ChangeSetReader
388 + StorageChangeSetReader
389 + DBProvider
390 + BlockNumReader
391 + StorageSettingsCache,
392 {
393 let (state_trie_tip_block, finish_tip_block) = database_state_frontiers(provider)?;
394 self.build_state_trie_overlay_at_frontiers(
395 provider,
396 state_trie_tip_block,
397 finish_tip_block,
398 trie_changesets,
399 )
400 }
401
402 #[instrument(
412 level = "debug",
413 target = "storage::overlay",
414 skip_all,
415 fields(?state_trie_tip_block, ?finish_tip_block, parent_hash = ?self.parent_hash)
416 )]
417 pub(crate) fn build_state_trie_overlay_at_frontiers<Provider>(
418 &self,
419 provider: &Provider,
420 state_trie_tip_block: BlockNumHash,
421 finish_tip_block: BlockNumHash,
422 trie_changesets: bool,
423 ) -> ProviderResult<StateTrieOverlay>
424 where
425 Provider: ChangeSetReader
426 + StorageChangeSetReader
427 + DBProvider
428 + BlockNumReader
429 + StageCheckpointReader
430 + PruneCheckpointReader
431 + StorageSettingsCache,
432 {
433 let retrieve_trie_reverts_duration;
434 let retrieve_hashed_state_reverts_duration;
435 let trie_updates_total_len;
436 let hashed_state_updates_total_len;
437
438 let anchor_for_parent =
439 self.anchor_at_parent_with_frontiers(provider, state_trie_tip_block, finish_tip_block)?;
440
441 let (trie_updates, hashed_post_state, prefix_sets) = match &anchor_for_parent {
443 AnchorForParent::RevertsRequired { anchor, .. } => {
444 let revert_blocks =
445 self.revert_blocks(&anchor_for_parent)?.expect("reverts are required");
446
447 debug!(
448 target: "storage::overlay",
449 ?revert_blocks,
450 ?anchor,
451 "Collecting trie reverts for overlay state provider"
452 );
453
454 let trie_reverts = if trie_changesets {
455 let _guard = debug_span!(target: "storage::overlay", "retrieving_trie_reverts")
456 .entered();
457 let start = Instant::now();
458 let accumulated_reverts =
459 self.overlay_manager.get_or_compute_cached_changesets_range_at_frontiers(
460 provider,
461 revert_blocks.clone(),
462 state_trie_tip_block,
463 finish_tip_block,
464 )?;
465 retrieve_trie_reverts_duration = start.elapsed();
466 accumulated_reverts
467 } else if state_trie_tip_block == finish_tip_block {
468 retrieve_trie_reverts_duration = Duration::ZERO;
469 Arc::default()
470 } else {
471 let start = Instant::now();
474 let finish_state = self
475 .overlay_manager
476 .block_state(finish_tip_block.hash)
477 .ok_or_else(|| {
478 ProviderError::other(StateTrieOverlayError {
479 tip_hash: finish_tip_block.hash,
480 anchor_hash: state_trie_tip_block.hash,
481 })
482 })?;
483 let (nodes, _) = self
484 .overlay_manager
485 .overlay_for_parent(
486 &finish_state,
487 state_trie_tip_block.hash,
488 OverlayCacheConfig::default(),
489 )
490 .map_err(ProviderError::other)?;
491 retrieve_trie_reverts_duration = start.elapsed();
492 nodes
493 };
494
495 let mut hashed_state_reverts = {
496 let _guard =
497 debug_span!(target: "storage::overlay", "retrieving_hashed_state_reverts")
498 .entered();
499 let start = Instant::now();
500 let res = HashedPostStateSorted::from_reverts(provider, revert_blocks)?;
501 retrieve_hashed_state_reverts_duration = start.elapsed();
502 res
503 };
504
505 let (overlay_trie, overlay_state) =
508 self.resolve_state_trie_overlays(anchor.hash)?;
509
510 let trie_updates = if trie_reverts.is_empty() {
511 overlay_trie
512 } else if !overlay_trie.is_empty() {
513 let mut trie_reverts = (*trie_reverts).clone();
514 trie_reverts.extend_ref_and_sort(&overlay_trie);
515 Arc::new(trie_reverts)
516 } else {
517 trie_reverts
518 };
519
520 let hashed_state_updates = if hashed_state_reverts.is_empty() {
521 overlay_state
522 } else if !overlay_state.is_empty() {
523 hashed_state_reverts.extend_ref_and_sort(&overlay_state);
524 Arc::new(hashed_state_reverts)
525 } else {
526 Arc::new(hashed_state_reverts)
527 };
528
529 let prefix_sets = if trie_changesets {
534 Default::default()
535 } else {
536 hashed_state_updates.construct_prefix_sets()
537 };
538
539 trie_updates_total_len = trie_updates.total_len();
540 hashed_state_updates_total_len = hashed_state_updates.total_len();
541
542 debug!(
543 target: "storage::overlay",
544 num_trie_updates = ?trie_updates_total_len,
545 num_state_updates = ?hashed_state_updates_total_len,
546 ?anchor,
547 "Reverted to anchor block",
548 );
549
550 (trie_updates, hashed_state_updates, prefix_sets)
551 }
552 AnchorForParent::NoReverts { anchor } => {
553 if self.should_skip_overlay_for_reused_sparse_trie(
557 state_trie_tip_block.hash,
558 finish_tip_block.hash,
559 ) {
560 self.metrics.sparse_trie_overlay_skips.increment(1);
561
562 return Ok(StateTrieOverlay::empty())
563 }
564
565 let (trie_updates, hashed_post_state) =
566 self.resolve_state_trie_overlays(anchor.hash)?;
567
568 retrieve_trie_reverts_duration = Duration::ZERO;
569 retrieve_hashed_state_reverts_duration = Duration::ZERO;
570 trie_updates_total_len = trie_updates.total_len();
571 hashed_state_updates_total_len = hashed_post_state.total_len();
572
573 debug!(
574 target: "storage::overlay",
575 num_trie_updates = trie_updates_total_len,
576 num_state_updates = hashed_state_updates_total_len,
577 ?anchor,
578 "Built overlay directly from durable frontier"
579 );
580
581 (trie_updates, hashed_post_state, Default::default())
582 }
583 };
584
585 self.metrics
586 .retrieve_trie_reverts_duration
587 .record(retrieve_trie_reverts_duration.as_secs_f64());
588 self.metrics
589 .retrieve_hashed_state_reverts_duration
590 .record(retrieve_hashed_state_reverts_duration.as_secs_f64());
591 self.metrics.trie_updates_size.record(trie_updates_total_len as f64);
592 self.metrics.hashed_state_size.record(hashed_state_updates_total_len as f64);
593
594 Ok(StateTrieOverlay::new(TrieInputSorted::new(
595 trie_updates,
596 hashed_post_state,
597 prefix_sets,
598 )))
599 }
600
601 #[cfg(test)]
603 #[instrument(level = "debug", target = "storage::overlay", skip_all)]
604 fn execution_overlay<Provider>(
605 &self,
606 provider: &Provider,
607 ) -> ProviderResult<(Arc<ExecutionOverlay>, Option<BlockNumber>)>
608 where
609 Provider: StageCheckpointReader
610 + PruneCheckpointReader
611 + ChangeSetReader
612 + StorageChangeSetReader
613 + DBProvider
614 + BlockNumReader,
615 {
616 let (state_trie_tip_block, finish_tip_block) = database_state_frontiers(provider)?;
617 self.execution_overlay_at_frontiers(provider, state_trie_tip_block, finish_tip_block)
618 }
619
620 #[instrument(
622 level = "trace",
623 target = "storage::overlay",
624 skip_all,
625 fields(?state_trie_tip_block, ?finish_tip_block, parent_hash = ?self.parent_hash)
626 )]
627 pub(crate) fn execution_overlay_at_frontiers<Provider>(
628 &self,
629 provider: &Provider,
630 state_trie_tip_block: BlockNumHash,
631 finish_tip_block: BlockNumHash,
632 ) -> ProviderResult<(Arc<ExecutionOverlay>, Option<BlockNumber>)>
633 where
634 Provider: ChangeSetReader
635 + StorageChangeSetReader
636 + DBProvider
637 + BlockNumReader
638 + PruneCheckpointReader,
639 {
640 let anchor_for_parent =
641 self.anchor_at_parent_with_frontiers(provider, state_trie_tip_block, finish_tip_block)?;
642 let (anchor_hash, fallback_block_number) = match anchor_for_parent {
643 AnchorForParent::RevertsRequired { anchor, .. } => {
644 (anchor.hash, Some(anchor.number + 1))
645 }
646 AnchorForParent::NoReverts { anchor } => (anchor.hash, None),
647 };
648 Ok((self.resolve_execution_overlay(anchor_hash)?, fallback_block_number))
649 }
650
651 fn resolve_state_trie_overlays(
653 &self,
654 anchor_hash: BlockHash,
655 ) -> ProviderResult<(Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>)> {
656 if anchor_hash == self.parent_hash {
657 Ok((Arc::new(TrieUpdatesSorted::default()), Arc::new(HashedPostStateSorted::default())))
658 } else {
659 let parent_state = self.parent_state.as_deref().ok_or_else(|| {
660 ProviderError::other(std::io::Error::other(
661 "state trie overlay cannot be anchored without in-memory parent state",
662 ))
663 })?;
664 self.overlay_manager
665 .overlay_for_parent(parent_state, anchor_hash, self.overlay_cache_config)
666 .map_err(ProviderError::other)
667 }
668 }
669
670 fn resolve_execution_overlay(
672 &self,
673 anchor_hash: BlockHash,
674 ) -> ProviderResult<Arc<ExecutionOverlay>> {
675 if anchor_hash == self.parent_hash {
676 Ok(Arc::new(ExecutionOverlay::default()))
677 } else {
678 let parent_state = self.parent_state.as_deref().ok_or_else(|| {
679 ProviderError::other(std::io::Error::other("missing in-memory parent state"))
680 })?;
681 self.overlay_manager
682 .execution_overlay_for_block_state(
683 parent_state,
684 anchor_hash,
685 self.overlay_cache_config,
686 )
687 .map_err(ProviderError::other)
688 }
689 }
690
691 fn revert_blocks(
693 &self,
694 anchor_for_parent: &AnchorForParent,
695 ) -> ProviderResult<Option<RangeInclusive<BlockNumber>>> {
696 match anchor_for_parent {
697 AnchorForParent::NoReverts { .. } => Ok(None),
698 AnchorForParent::RevertsRequired { anchor, finish, .. } => {
699 if self.no_reverts {
700 return Err(ProviderError::other(std::io::Error::other(format!(
701 "reverts are disabled, but overlay for parent {} requires reverting Finish #{} ({}) to anchor #{} ({})",
702 self.parent_hash, finish.number, finish.hash, anchor.number, anchor.hash,
703 ))))
704 }
705 Ok(Some(anchor.number + 1..=finish.number))
706 }
707 }
708 }
709
710 fn should_skip_overlay_for_reused_sparse_trie(
712 &self,
713 state_trie_tip_hash: B256,
714 finish_tip_hash: B256,
715 ) -> bool {
716 let Some(anchor_hash) = self.reused_sparse_trie_anchor_hash else { return false };
717
718 self.contains_hash(anchor_hash, state_trie_tip_hash) &&
719 self.contains_hash(anchor_hash, finish_tip_hash)
720 }
721
722 fn contains_hash(&self, anchor_hash: B256, hash: B256) -> bool {
723 let mut current_hash = self.parent_hash;
724 let mut blocks = self.parent_state.iter().flat_map(|state| state.chain());
725
726 loop {
727 if current_hash == hash {
728 return true
729 }
730 if current_hash == anchor_hash {
731 return false
732 }
733
734 let Some(block) = blocks.next() else { return false };
735 current_hash = block.block_ref().recovered_block().parent_hash();
736 }
737 }
738}
739
740pub(crate) fn database_state_frontiers<Provider>(
743 provider: &Provider,
744) -> ProviderResult<(BlockNumHash, BlockNumHash)>
745where
746 Provider: StageCheckpointReader + BlockNumReader,
747{
748 let checkpoint = provider
749 .get_stage_checkpoint(StageId::Finish)?
750 .ok_or_else(|| ProviderError::InsufficientChangesets { requested: 0, available: 0..=0 })?;
751 let state_trie_tip_number = checkpoint
752 .finish_stage_checkpoint()
753 .and_then(|finish| finish.partial_state_trie())
754 .unwrap_or(checkpoint.block_number);
755 let state_trie_tip_hash = provider
756 .convert_number(state_trie_tip_number.into())?
757 .ok_or_else(|| ProviderError::HeaderNotFound(state_trie_tip_number.into()))?;
758 let finish_tip_number = checkpoint.block_number;
759 let finish_tip_hash = provider
760 .convert_number(finish_tip_number.into())?
761 .ok_or_else(|| ProviderError::HeaderNotFound(finish_tip_number.into()))?;
762
763 Ok((
764 BlockNumHash::new(state_trie_tip_number, state_trie_tip_hash),
765 BlockNumHash::new(finish_tip_number, finish_tip_hash),
766 ))
767}
768
769#[derive(Clone, Metrics)]
771#[metrics(scope = "storage.overlay.builder")]
772struct OverlayBuilderMetrics {
773 retrieve_trie_reverts_duration: Histogram,
775 retrieve_hashed_state_reverts_duration: Histogram,
777 trie_updates_size: Histogram,
779 hashed_state_size: Histogram,
781 sparse_trie_overlay_skips: Counter,
784}
785
786fn anchor_for_parent_in<'a, N: NodePrimitives + 'a>(
787 parent_hash: B256,
788 in_mem_chain: impl Iterator<Item = &'a ExecutedBlock<N>>,
789 preferred_anchor: BlockNumHash,
790) -> Option<BlockNumHash> {
791 if parent_hash == preferred_anchor.hash {
792 return Some(preferred_anchor)
793 }
794
795 let mut anchor = None;
796
797 for block in in_mem_chain {
798 let block_parent = block.recovered_block().parent_num_hash();
799
800 if block_parent.hash == preferred_anchor.hash {
801 return Some(preferred_anchor)
802 }
803 anchor = Some(block_parent);
804 }
805
806 anchor
807}
808
809#[derive(Debug)]
811enum AnchorForParent {
812 NoReverts {
814 anchor: BlockNumHash,
816 },
817 RevertsRequired {
819 anchor: BlockNumHash,
821 finish: BlockNumHash,
823 },
824}
825
826#[cfg(test)]
827mod tests {
828 use super::*;
829 use alloy_primitives::{keccak256, map::HashMap, Address, U256};
830 use reth_chain_state::{
831 test_utils::TestBlockBuilder, CanonicalInMemoryState, ExecutedBlock, NewCanonicalChain,
832 };
833 use reth_db::{
834 models::{AccountBeforeTx, BlockNumberAddress},
835 tables,
836 transaction::DbTxMut,
837 };
838 use reth_primitives_traits::{Account, StorageEntry};
839 use reth_provider::{
840 test_utils::{create_test_provider_factory, MockNodeTypesWithDB},
841 BlockWriter, ProviderFactory,
842 };
843 use reth_stages_types::{FinishCheckpoint, StageCheckpoint};
844 use reth_storage_api::StageCheckpointWriter;
845 use reth_trie::{BranchNodeCompact, HashedPostState, HashedStorage, Nibbles, SortedTrieData};
846 use revm::{
847 bytecode::Bytecode,
848 database::{AccountStatus, BundleAccount, BundleState},
849 state::{AccountId, AccountInfo},
850 };
851
852 fn with_unique_trie_data(
853 block: &ExecutedBlock<EthPrimitives>,
854 id: u8,
855 ) -> ExecutedBlock<EthPrimitives> {
856 let hashed_address = B256::with_last_byte(id);
857 let hashed_slot = B256::with_last_byte(id.saturating_add(32));
858 let hashed_state = HashedPostState::default()
859 .with_accounts([(hashed_address, Some(Account::default()))])
860 .with_storages([(
861 hashed_address,
862 HashedStorage::from_iter([(hashed_slot, U256::from(id))]),
863 )])
864 .into_sorted();
865 let trie_updates = TrieUpdatesSorted::new(
866 vec![(
867 Nibbles::from_nibbles([id]),
868 Some(BranchNodeCompact::new(0, 0, 0, vec![], None)),
869 )],
870 Default::default(),
871 );
872 let address = Address::with_last_byte(id);
873 let slot = U256::from(id);
874 let code_hash = B256::with_last_byte(id.saturating_add(64));
875 let state = BundleState::builder(block.block_number()..=block.block_number())
876 .state_present_account_info(
877 address,
878 AccountInfo {
879 nonce: id as u64,
880 balance: U256::from(id),
881 account_id: AccountId::new(id as usize),
882 ..Default::default()
883 },
884 )
885 .state_storage(address, HashMap::from_iter([(slot, (U256::ZERO, U256::from(id)))]))
886 .contract(code_hash, Bytecode::new_raw(vec![id].into()))
887 .build();
888 let mut execution_output = (*block.execution_output).clone();
889 execution_output.state = state;
890
891 ExecutedBlock::new(
892 Arc::clone(&block.recovered_block),
893 Arc::new(execution_output),
894 SortedTrieData::new(Arc::new(hashed_state), Arc::new(trie_updates)),
895 )
896 }
897
898 fn test_blocks() -> Vec<ExecutedBlock<EthPrimitives>> {
899 TestBlockBuilder::eth()
900 .get_executed_blocks(0..5)
901 .enumerate()
902 .map(|(index, block)| with_unique_trie_data(&block, index as u8 + 1))
903 .collect()
904 }
905
906 fn setup_frontiers(
907 state_trie_tip_index: usize,
908 finish_tip_index: usize,
909 ) -> (ProviderFactory<MockNodeTypesWithDB>, Vec<ExecutedBlock<EthPrimitives>>) {
910 let factory = create_test_provider_factory();
911 let blocks = test_blocks();
912 let provider_rw = factory.provider_rw().unwrap();
913 for block in &blocks[..=finish_tip_index] {
914 provider_rw.insert_block(block.recovered_block()).unwrap();
915 }
916 provider_rw
917 .save_stage_checkpoint(
918 StageId::Finish,
919 StageCheckpoint::new(blocks[finish_tip_index].block_number())
920 .with_finish_stage_checkpoint(FinishCheckpoint {
921 partial_state_trie: Some(blocks[state_trie_tip_index].block_number()),
922 }),
923 )
924 .unwrap();
925 provider_rw.commit().unwrap();
926
927 (factory, blocks)
928 }
929
930 fn canonical_in_memory_state(
933 blocks: &[ExecutedBlock<EthPrimitives>],
934 ) -> CanonicalInMemoryState<EthPrimitives> {
935 let state = CanonicalInMemoryState::empty();
936 state.update_chain(NewCanonicalChain::Commit { new: blocks.to_vec() });
937 state
938 }
939
940 const fn anchor_num_hash(anchor: &AnchorForParent) -> BlockNumHash {
941 match anchor {
942 AnchorForParent::NoReverts { anchor } |
943 AnchorForParent::RevertsRequired { anchor, .. } => *anchor,
944 }
945 }
946
947 fn account_keys(overlay: &StateTrieOverlay) -> Vec<B256> {
948 overlay.input().state.accounts.iter().map(|(key, _)| *key).collect()
949 }
950
951 fn account_node_paths(overlay: &StateTrieOverlay) -> Vec<Nibbles> {
952 overlay.input().nodes.account_nodes_ref().iter().map(|(path, _)| *path).collect()
953 }
954
955 #[test]
956 fn execution_overlay_extends_bundle_state_without_account_ids() {
957 let address = Address::with_last_byte(1);
958 let slot = U256::from(2);
959 let value = U256::from(3);
960 let code = Bytecode::new_raw(vec![0x60, 0x00].into());
961 let code_hash = code.hash_slow();
962 let account = AccountInfo {
963 account_id: AccountId::new(6),
964 ..AccountInfo::new(U256::from(5), 4, code_hash, code.clone())
965 };
966 let state = BundleState::builder(0..=0)
967 .state_present_account_info(address, account.clone())
968 .state_storage(address, HashMap::from_iter([(slot, (U256::ZERO, value))]))
969 .contract(code_hash, code.clone())
970 .build();
971 assert!(state.state()[&address].info.as_ref().unwrap().account_id.is_some());
972
973 let mut overlay = ExecutionOverlay::default();
974 overlay.extend_state(&state);
975
976 let stored_account = overlay.accounts[&address].as_ref().unwrap();
977 assert_eq!(stored_account.account_id, None);
978 assert_eq!(
979 stored_account,
980 &AccountInfo { account_id: None, ..account },
981 "normalization must preserve durable account fields"
982 );
983 assert_eq!(stored_account.code, Some(code.clone()));
984 assert_eq!(overlay.storage[&address][&slot], value);
985 assert_eq!(overlay.code_hashes[&code_hash], code);
986 }
987
988 #[test]
989 fn execution_overlay_zeroes_unobserved_storage_for_destroyed_accounts() {
990 let address = Address::with_last_byte(1);
991 let mut state = BundleState::default();
992 state.state.insert(
993 address,
994 BundleAccount::new(
995 Some(AccountInfo::default()),
996 None,
997 Default::default(),
998 AccountStatus::Destroyed,
999 ),
1000 );
1001
1002 let mut overlay = ExecutionOverlay::default();
1003 overlay.extend_state(&state);
1004
1005 assert_eq!(overlay.storage_value(address, U256::ZERO), Some(U256::ZERO));
1006 }
1007
1008 #[test]
1009 fn overlay_builder_for_state_matches_hash_lookup() {
1010 let (factory, blocks) = setup_frontiers(1, 3);
1013 let manager = OverlayManager::default();
1014 for block in &blocks[2..=4] {
1015 manager.insert_block(block.clone());
1016 }
1017 let canonical = canonical_in_memory_state(&blocks[2..=4]);
1018 let provider = factory.provider().unwrap();
1019
1020 for index in [4usize, 3, 2] {
1023 let hash = blocks[index].recovered_block().hash();
1024 let state = canonical.state_by_hash(hash).expect("canonical state for in-memory block");
1025 assert_eq!(state.hash(), hash);
1026
1027 let from_hash = manager.overlay_builder(hash);
1028 let from_state = manager.overlay_builder_for_state(state);
1029
1030 let anchor = anchor_num_hash(&from_hash.anchor_at_parent(&provider).unwrap());
1031 assert_eq!(
1032 anchor_num_hash(&from_state.anchor_at_parent(&provider).unwrap()),
1033 anchor,
1034 "block {index} must resolve the same anchor from both builders"
1035 );
1036
1037 let (hash_overlay, hash_fallback) = from_hash.execution_overlay(&provider).unwrap();
1038 let (state_overlay, state_fallback) = from_state.execution_overlay(&provider).unwrap();
1039 assert_eq!(state_fallback, hash_fallback);
1040 assert!(
1041 Arc::ptr_eq(&hash_overlay, &state_overlay),
1042 "block {index} must resolve the cached execution overlay"
1043 );
1044
1045 let (hash_nodes, hash_state) =
1046 from_hash.resolve_state_trie_overlays(anchor.hash).unwrap();
1047 let (state_nodes, state_state) =
1048 from_state.resolve_state_trie_overlays(anchor.hash).unwrap();
1049 assert!(
1050 Arc::ptr_eq(&hash_nodes, &state_nodes),
1051 "block {index} must resolve the cached trie overlay nodes"
1052 );
1053 assert!(
1054 Arc::ptr_eq(&hash_state, &state_state),
1055 "block {index} must resolve the cached trie overlay state"
1056 );
1057
1058 let hash_trie = from_hash.build_state_trie_overlay(&provider, false).unwrap();
1061 let state_trie = from_state.build_state_trie_overlay(&provider, false).unwrap();
1062 assert_eq!(account_keys(&state_trie), account_keys(&hash_trie), "block {index}");
1063 assert_eq!(
1064 account_node_paths(&state_trie),
1065 account_node_paths(&hash_trie),
1066 "block {index}"
1067 );
1068 }
1069 }
1070
1071 #[test]
1072 fn managed_overlay_starts_at_state_trie_frontier() {
1073 let (factory, blocks) = setup_frontiers(1, 3);
1074 let manager = OverlayManager::default();
1075 for block in &blocks[2..=4] {
1076 manager.insert_block(block.clone());
1077 }
1078 let provider = factory.provider().unwrap();
1079
1080 for (parent_index, expected_ids) in [(3, vec![3, 4]), (4, vec![3, 4, 5])] {
1081 let overlay = manager
1082 .overlay_builder(blocks[parent_index].recovered_block().hash())
1083 .build_state_trie_overlay(&provider, true)
1084 .unwrap();
1085
1086 assert_eq!(
1087 account_keys(&overlay),
1088 expected_ids.iter().copied().map(B256::with_last_byte).collect::<Vec<_>>()
1089 );
1090 assert_eq!(
1091 account_node_paths(&overlay),
1092 expected_ids
1093 .iter()
1094 .copied()
1095 .map(|id| Nibbles::from_nibbles([id]))
1096 .collect::<Vec<_>>()
1097 );
1098 }
1099 }
1100
1101 #[test]
1102 fn managed_overlay_skips_when_finish_is_the_anchor() {
1103 let (factory, blocks) = setup_frontiers(3, 3);
1104 let manager = OverlayManager::default();
1105 manager.insert_block(blocks[4].clone());
1106 let provider = factory.provider().unwrap();
1107
1108 let overlay = manager
1109 .overlay_builder(blocks[4].recovered_block().hash())
1110 .with_skip_overlay_for_reused_sparse_trie(blocks[3].recovered_block().hash())
1111 .build_state_trie_overlay(&provider, true)
1112 .unwrap();
1113
1114 assert!(overlay.input().state.is_empty());
1115 assert!(overlay.input().nodes.is_empty());
1116 }
1117
1118 #[test]
1119 fn no_reverts_errors_when_reverts_are_required() {
1120 let (factory, blocks) = setup_frontiers(2, 3);
1121 let provider = factory.provider().unwrap();
1122
1123 let builder = OverlayManager::<EthPrimitives>::default()
1124 .overlay_builder(blocks[1].recovered_block().hash())
1125 .with_no_reverts();
1126 let error = builder.build_state_trie_overlay(&provider, true).unwrap_err();
1127
1128 assert!(error.to_string().contains("reverts are disabled"));
1129 }
1130
1131 #[test]
1132 fn appended_overlay_rejects_noncanonical_anchor_when_reverts_are_required() {
1133 let (factory, blocks) = setup_frontiers(1, 3);
1134 let provider = factory.provider().unwrap();
1135 let parent_hash = B256::with_last_byte(100);
1136 assert_ne!(parent_hash, blocks[1].recovered_block().hash());
1137 let block = TestBlockBuilder::eth()
1138 .get_executed_block_with_number(blocks[2].block_number(), parent_hash);
1139 let builder =
1140 OverlayManager::default().overlay_builder(parent_hash).with_appended_block(block);
1141
1142 assert!(matches!(
1143 builder.execution_overlay(&provider),
1144 Err(ProviderError::BlockHashNotFound(hash)) if hash == parent_hash
1145 ));
1146 assert!(matches!(
1147 builder.build_state_trie_overlay(&provider, true),
1148 Err(ProviderError::BlockHashNotFound(hash)) if hash == parent_hash
1149 ));
1150 }
1151
1152 #[test]
1153 fn state_trie_overlay_invalidates_reverts_and_fork_without_trie_changesets() {
1154 let (factory, blocks) = setup_frontiers(3, 3);
1155 let provider_rw = factory.provider_rw().unwrap();
1156 provider_rw
1157 .tx_ref()
1158 .put::<tables::AccountChangeSets>(
1159 3,
1160 AccountBeforeTx {
1161 address: Address::with_last_byte(1),
1162 info: Some(Account::default()),
1163 },
1164 )
1165 .unwrap();
1166 provider_rw.commit().unwrap();
1167
1168 let fork = TestBlockBuilder::eth()
1170 .get_executed_block_with_number(2, blocks[1].recovered_block().hash());
1171 let fork = with_unique_trie_data(&fork, 2);
1172 let provider = factory.provider().unwrap();
1173 let overlay = OverlayManager::<EthPrimitives>::default()
1174 .overlay_builder(blocks[1].recovered_block().hash())
1175 .with_appended_block(fork)
1176 .build_state_trie_overlay(&provider, false)
1177 .unwrap();
1178
1179 let mut prefix_sets = overlay.input().prefix_sets.clone().freeze();
1180 assert!(prefix_sets
1181 .account_prefix_set
1182 .contains(&Nibbles::unpack(keccak256(Address::with_last_byte(1)))));
1183 assert!(prefix_sets.account_prefix_set.contains(&Nibbles::unpack(B256::with_last_byte(2))));
1184 assert!(prefix_sets
1185 .storage_prefix_sets
1186 .get_mut(&B256::with_last_byte(2))
1187 .unwrap()
1188 .contains(&Nibbles::unpack(B256::with_last_byte(34))));
1189 }
1190
1191 #[test]
1192 fn execution_overlay_marks_historical_fallback() {
1193 let (factory, blocks) = setup_frontiers(1, 3);
1194 let provider_rw = factory.provider_rw().unwrap();
1195 let address = Address::with_last_byte(1);
1196 let slot = U256::from(5);
1197
1198 provider_rw
1199 .tx_ref()
1200 .put::<tables::AccountChangeSets>(
1201 2,
1202 AccountBeforeTx {
1203 address,
1204 info: Some(Account { balance: U256::from(10), ..Default::default() }),
1205 },
1206 )
1207 .unwrap();
1208 provider_rw
1209 .tx_ref()
1210 .put::<tables::AccountChangeSets>(
1211 3,
1212 AccountBeforeTx {
1213 address,
1214 info: Some(Account { balance: U256::from(20), ..Default::default() }),
1215 },
1216 )
1217 .unwrap();
1218 for (block_number, value) in [(2, 10), (3, 15)] {
1219 provider_rw
1220 .tx_ref()
1221 .put::<tables::StorageChangeSets>(
1222 BlockNumberAddress((block_number, address)),
1223 StorageEntry { key: B256::from(slot), value: U256::from(value) },
1224 )
1225 .unwrap();
1226 }
1227 provider_rw.commit().unwrap();
1228
1229 let provider = factory.provider().unwrap();
1230 let (overlay, fallback_block_number) = OverlayManager::<EthPrimitives>::default()
1231 .overlay_builder(blocks[1].recovered_block().hash())
1232 .execution_overlay(&provider)
1233 .unwrap();
1234
1235 assert_eq!(fallback_block_number, Some(2));
1236 assert!(overlay.accounts.is_empty());
1237 assert!(overlay.storage.is_empty());
1238 assert!(overlay.code_hashes.is_empty());
1239 }
1240
1241 #[test]
1242 fn execution_overlay_uses_managed_blocks_after_the_anchor() {
1243 let (factory, blocks) = setup_frontiers(1, 3);
1244 let manager = OverlayManager::default();
1245 for block in &blocks[2..=4] {
1246 manager.insert_block(block.clone());
1247 }
1248 let provider = factory.provider().unwrap();
1249
1250 let (overlay, fallback_block_number) = manager
1251 .overlay_builder(blocks[3].recovered_block().hash())
1252 .execution_overlay(&provider)
1253 .unwrap();
1254
1255 assert_eq!(fallback_block_number, None);
1256
1257 for id in [3, 4] {
1258 let address = Address::with_last_byte(id);
1259 let slot = U256::from(id);
1260 assert_eq!(overlay.accounts[&address].as_ref().unwrap().balance, U256::from(id));
1261 assert_eq!(overlay.accounts[&address].as_ref().unwrap().account_id, None);
1262 assert_eq!(overlay.storage[&address][&slot], U256::from(id));
1263 assert!(overlay.code_hashes.contains_key(&B256::with_last_byte(id + 64)));
1264 }
1265 assert_eq!(
1266 overlay.block_hashes,
1267 blocks[2..=3]
1268 .iter()
1269 .map(|block| block.recovered_block().num_hash())
1270 .collect::<Vec<_>>()
1271 );
1272 }
1273
1274 #[test]
1275 fn execution_overlay_marks_historical_fallback_for_managed_fork() {
1276 let (factory, blocks) = setup_frontiers(1, 3);
1277 let address = Address::with_last_byte(1);
1278 let slot = U256::ONE;
1279 let provider_rw = factory.provider_rw().unwrap();
1280 for (block_number, balance, storage_value) in [(2u64, 10u64, 10u64), (3u64, 20u64, 15u64)] {
1281 provider_rw
1282 .tx_ref()
1283 .put::<tables::AccountChangeSets>(
1284 block_number,
1285 AccountBeforeTx {
1286 address,
1287 info: Some(Account { balance: U256::from(balance), ..Default::default() }),
1288 },
1289 )
1290 .unwrap();
1291 provider_rw
1292 .tx_ref()
1293 .put::<tables::StorageChangeSets>(
1294 BlockNumberAddress((block_number, address)),
1295 StorageEntry { key: B256::from(slot), value: U256::from(storage_value) },
1296 )
1297 .unwrap();
1298 }
1299 provider_rw.commit().unwrap();
1300
1301 let mut side_chain_builder = TestBlockBuilder::eth();
1302 let side_block_two = side_chain_builder.get_executed_block_with_number(
1303 blocks[2].block_number(),
1304 blocks[1].recovered_block().hash(),
1305 );
1306 let side_block_two = with_unique_trie_data(&side_block_two, 1);
1307 let side_block_three = side_chain_builder.get_executed_block_with_number(
1308 blocks[3].block_number(),
1309 side_block_two.recovered_block().hash(),
1310 );
1311 let side_block_three = with_unique_trie_data(&side_block_three, 1);
1312 assert_ne!(
1313 side_block_three.recovered_block().hash(),
1314 blocks[3].recovered_block().hash(),
1315 "the managed chain must not contain the durable Finish block"
1316 );
1317
1318 let manager = OverlayManager::default();
1319 manager.insert_block(side_block_two.clone());
1320 manager.insert_block(side_block_three.clone());
1321 let provider = factory.provider().unwrap();
1322
1323 let (overlay, fallback_block_number) = manager
1324 .overlay_builder(side_block_three.recovered_block().hash())
1325 .execution_overlay(&provider)
1326 .unwrap();
1327
1328 assert_eq!(fallback_block_number, Some(2));
1329
1330 assert_eq!(overlay.accounts[&address].as_ref().unwrap().balance, U256::ONE);
1331 assert_eq!(overlay.accounts[&address].as_ref().unwrap().account_id, None);
1332 assert_eq!(overlay.storage[&address][&slot], U256::ONE);
1333 assert_eq!(
1334 overlay.block_hashes,
1335 [side_block_two, side_block_three]
1336 .iter()
1337 .map(|block| block.recovered_block().num_hash())
1338 .collect::<Vec<_>>()
1339 );
1340 }
1341
1342 #[test]
1343 fn managed_overlay_uses_persisted_parent_even_if_retained() {
1344 let (factory, blocks) = setup_frontiers(2, 3);
1345 let manager = OverlayManager::default();
1346 manager.insert_block(blocks[1].clone());
1347 let provider = factory.provider().unwrap();
1348 let builder = manager.overlay_builder(blocks[1].recovered_block().hash());
1349 match builder.anchor_at_parent(&provider).unwrap() {
1350 AnchorForParent::RevertsRequired { anchor, finish } => {
1351 assert_eq!(anchor, blocks[1].recovered_block().num_hash());
1352 assert_eq!(finish, blocks[3].recovered_block().num_hash());
1353 }
1354 AnchorForParent::NoReverts { .. } => {
1355 panic!("persisted parent below Finish must require reverts")
1356 }
1357 }
1358 }
1359
1360 #[test]
1361 fn builder_appends_block_to_parent_state() {
1362 let manager = OverlayManager::default();
1363 let blocks = test_blocks();
1364 for block in &blocks[2..=4] {
1365 manager.insert_block(block.clone());
1366 }
1367
1368 let block = TestBlockBuilder::eth().get_executed_block_with_number(
1369 blocks[4].recovered_block().number() + 1,
1370 blocks[4].recovered_block().hash(),
1371 );
1372 let builder = manager
1373 .overlay_builder(block.recovered_block().parent_hash())
1374 .with_appended_block(block.clone());
1375
1376 assert_eq!(builder.parent_hash, block.recovered_block().hash());
1377 assert_eq!(
1378 builder.parent_state.unwrap().chain().map(BlockState::hash).collect::<Vec<_>>(),
1379 std::iter::once(&block)
1380 .chain(blocks[2..=4].iter().rev())
1381 .map(|block| block.recovered_block().hash())
1382 .collect::<Vec<_>>(),
1383 );
1384 }
1385
1386 #[test]
1387 fn overlay_after_state_trie_frontier_requires_managed_coverage() {
1388 let (factory, blocks) = setup_frontiers(1, 3);
1389 let provider = factory.provider().unwrap();
1390 let error = OverlayManager::<EthPrimitives>::default()
1391 .overlay_builder(blocks[3].recovered_block().hash())
1392 .build_state_trie_overlay(&provider, true)
1393 .unwrap_err();
1394
1395 assert!(
1396 error.to_string().contains("is after partial state trie frontier"),
1397 "unexpected error: {error}"
1398 );
1399 }
1400
1401 #[test]
1402 fn managed_overlay_skips_manager_for_persisted_parent() {
1403 let parent_hash = B256::with_last_byte(1);
1404 let builder = OverlayManager::<EthPrimitives>::default().overlay_builder(parent_hash);
1405
1406 let (trie, state) = builder.resolve_state_trie_overlays(parent_hash).unwrap();
1407 assert!(trie.is_empty());
1408 assert!(state.is_empty());
1409 }
1410
1411 #[test]
1412 fn managed_overlay_errors_if_parent_is_not_persisted_or_managed() {
1413 let parent_hash = B256::with_last_byte(1);
1414 let anchor_hash = B256::with_last_byte(2);
1415 let builder = OverlayManager::<EthPrimitives>::default().overlay_builder(parent_hash);
1416
1417 let err = builder.resolve_state_trie_overlays(anchor_hash).unwrap_err();
1418
1419 assert!(err.to_string().contains("cannot be anchored"));
1420 }
1421
1422 #[test]
1423 fn managed_overlay_skip_requires_both_frontiers() {
1424 let parent_hash = B256::with_last_byte(1);
1425 let builder = OverlayManager::<EthPrimitives>::default().overlay_builder(parent_hash);
1426 assert!(!builder.should_skip_overlay_for_reused_sparse_trie(parent_hash, parent_hash));
1427
1428 let builder = builder.with_skip_overlay_for_reused_sparse_trie(parent_hash);
1429 assert!(builder.should_skip_overlay_for_reused_sparse_trie(parent_hash, parent_hash));
1430 assert!(!builder
1431 .should_skip_overlay_for_reused_sparse_trie(B256::with_last_byte(3), parent_hash,));
1432
1433 let blocks = test_blocks();
1434 let manager = OverlayManager::default();
1435 for block in &blocks[2..=4] {
1436 manager.insert_block(block.clone());
1437 }
1438 let builder = manager
1439 .overlay_builder(blocks[4].recovered_block().hash())
1440 .with_skip_overlay_for_reused_sparse_trie(blocks[1].recovered_block().hash());
1441 assert!(builder.should_skip_overlay_for_reused_sparse_trie(
1442 blocks[1].recovered_block().hash(),
1443 blocks[3].recovered_block().hash(),
1444 ));
1445
1446 let builder =
1447 builder.with_skip_overlay_for_reused_sparse_trie(blocks[2].recovered_block().hash());
1448 assert!(!builder.should_skip_overlay_for_reused_sparse_trie(
1449 blocks[1].recovered_block().hash(),
1450 blocks[3].recovered_block().hash(),
1451 ));
1452 }
1453}