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