1use alloy_eips::BlockNumHash;
2use alloy_primitives::{BlockHash, BlockNumber, B256};
3use metrics::{Counter, Histogram};
4use reth_chain_state::{EthPrimitives, StateTrieOverlayManager};
5use reth_db_api::{tables, transaction::DbTx, DatabaseError};
6use reth_errors::{ProviderError, ProviderResult};
7use reth_metrics::Metrics;
8use reth_primitives_traits::{
9 dashmap::{self, DashMap},
10 NodePrimitives,
11};
12use reth_prune_types::PruneSegment;
13use reth_stages_types::StageId;
14use reth_storage_api::{
15 BlockNumReader, ChangeSetReader, DBProvider, DatabaseProviderFactory,
16 DatabaseProviderROFactory, PruneCheckpointReader, StageCheckpointReader,
17 StorageChangeSetReader, StorageSettingsCache,
18};
19use reth_trie::{
20 hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory},
21 trie_cursor::{InMemoryTrieCursor, TrieCursor, TrieCursorFactory, TrieStorageCursor},
22 updates::TrieUpdatesSorted,
23 HashedPostStateSorted,
24};
25use reth_trie_db::{
26 ChangesetCache, DatabaseAccountTrieCursor, DatabaseHashedCursorFactory,
27 DatabaseStorageTrieCursor, LegacyKeyAdapter, PackedAccountsTrie, PackedKeyAdapter,
28 PackedStoragesTrie,
29};
30use std::{
31 ops::RangeInclusive,
32 sync::Arc,
33 time::{Duration, Instant},
34};
35use tracing::{debug, debug_span, instrument};
36
37#[derive(Clone, Metrics)]
39#[metrics(scope = "storage.providers.overlay")]
40pub(crate) struct OverlayStateProviderMetrics {
41 create_provider_duration: Histogram,
43 retrieve_trie_reverts_duration: Histogram,
45 retrieve_hashed_state_reverts_duration: Histogram,
47 trie_updates_size: Histogram,
49 hashed_state_size: Histogram,
51 database_provider_ro_duration: Histogram,
53 overlay_cache_misses: Counter,
55}
56
57#[derive(Debug, Clone)]
59pub(super) struct Overlay {
60 pub(super) trie_updates: Arc<TrieUpdatesSorted>,
61 pub(super) hashed_post_state: Arc<HashedPostStateSorted>,
62}
63
64#[derive(Debug, Clone)]
66pub(super) enum OverlaySource<N: NodePrimitives = EthPrimitives> {
67 Immediate {
69 trie: Arc<TrieUpdatesSorted>,
74 state: Arc<HashedPostStateSorted>,
76 },
77 Managed {
79 manager: StateTrieOverlayManager<N>,
81 state: Arc<HashedPostStateSorted>,
85 },
86}
87
88#[derive(Debug, Clone)]
93pub struct OverlayBuilder<N: NodePrimitives = EthPrimitives> {
94 parent_hash: B256,
96 overlay_source: Option<OverlaySource<N>>,
98 changeset_cache: ChangesetCache,
100 metrics: OverlayStateProviderMetrics,
102}
103
104impl<N: NodePrimitives> OverlayBuilder<N> {
105 pub fn new(parent_hash: B256, changeset_cache: ChangesetCache) -> Self {
107 Self {
108 parent_hash,
109 overlay_source: None,
110 changeset_cache,
111 metrics: OverlayStateProviderMetrics::default(),
112 }
113 }
114
115 pub(super) fn with_overlay_source(mut self, source: Option<OverlaySource<N>>) -> Self {
119 self.overlay_source = source;
120 self
121 }
122
123 pub fn with_state_trie_overlay_manager(
125 mut self,
126 state_trie_overlay_manager: StateTrieOverlayManager<N>,
127 ) -> Self {
128 self.overlay_source = Some(OverlaySource::Managed {
129 manager: state_trie_overlay_manager,
130 state: Arc::new(HashedPostStateSorted::default()),
131 });
132 self
133 }
134
135 pub fn with_hashed_state_overlay(
137 mut self,
138 hashed_state_overlay: Option<Arc<HashedPostStateSorted>>,
139 ) -> Self {
140 if let Some(state) = hashed_state_overlay {
141 match &mut self.overlay_source {
142 Some(OverlaySource::Managed { state: managed_state, .. }) => {
143 *managed_state = state;
144 }
145 _ => {
146 self.overlay_source = Some(OverlaySource::Immediate {
147 trie: Arc::new(TrieUpdatesSorted::default()),
148 state,
149 });
150 }
151 }
152 }
153 self
154 }
155
156 pub fn with_extended_hashed_state_overlay(mut self, other: HashedPostStateSorted) -> Self {
160 match &mut self.overlay_source {
161 Some(OverlaySource::Immediate { state, .. } | OverlaySource::Managed { state, .. }) => {
162 Arc::make_mut(state).extend_ref_and_sort(&other);
163 }
164 None => {
165 self.overlay_source = Some(OverlaySource::Immediate {
166 trie: Arc::new(TrieUpdatesSorted::default()),
167 state: Arc::new(other),
168 });
169 }
170 }
171 self
172 }
173
174 fn resolve_overlays(
176 &self,
177 anchor_hash: BlockHash,
178 ) -> ProviderResult<(Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>)> {
179 match &self.overlay_source {
180 Some(OverlaySource::Managed { manager, state }) => {
181 let (trie, mut overlay_state) = if anchor_hash == self.parent_hash {
182 (
183 Arc::new(TrieUpdatesSorted::default()),
184 Arc::new(HashedPostStateSorted::default()),
185 )
186 } else {
187 manager
188 .overlay_for_parent(self.parent_hash, anchor_hash)
189 .map_err(ProviderError::other)?
190 };
191
192 if overlay_state.is_empty() {
193 overlay_state = Arc::clone(state);
194 } else if !state.is_empty() {
195 Arc::make_mut(&mut overlay_state).extend_ref_and_sort(state);
196 }
197
198 Ok((trie, overlay_state))
199 }
200 Some(OverlaySource::Immediate { trie, state }) => {
201 if anchor_hash != self.parent_hash {
202 return Err(ProviderError::other(std::io::Error::other(format!(
203 "anchor_hash {anchor_hash} doesn't match OverlayBuilder's configured parent ({})",
204 self.parent_hash
205 ))))
206 }
207 Ok((Arc::clone(trie), Arc::clone(state)))
208 }
209 None => Ok((
210 Arc::new(TrieUpdatesSorted::default()),
211 Arc::new(HashedPostStateSorted::default()),
212 )),
213 }
214 }
215
216 fn get_db_tip_block<Provider>(&self, provider: &Provider) -> ProviderResult<BlockNumHash>
219 where
220 Provider: StageCheckpointReader + BlockNumReader,
221 {
222 let block_number = provider
223 .get_stage_checkpoint(StageId::Finish)?
224 .as_ref()
225 .map(|chk| chk.block_number)
226 .ok_or_else(|| ProviderError::InsufficientChangesets {
227 requested: 0,
228 available: 0..=0,
229 })?;
230 let hash = provider
231 .convert_number(block_number.into())?
232 .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?;
233 Ok(BlockNumHash::new(block_number, hash))
234 }
235
236 fn reverts_required<Provider>(
242 &self,
243 provider: &Provider,
244 db_tip_block: BlockNumHash,
245 anchor_hash: B256,
246 ) -> ProviderResult<Option<RangeInclusive<BlockNumber>>>
247 where
248 Provider: BlockNumReader + PruneCheckpointReader,
249 {
250 if db_tip_block.hash == anchor_hash {
252 return Ok(None)
253 }
254
255 let anchor_number = provider
256 .convert_hash_or_number(anchor_hash.into())?
257 .ok_or(ProviderError::BlockHashNotFound(anchor_hash))?;
258
259 let prune_checkpoint = provider.get_prune_checkpoint(PruneSegment::AccountHistory)?;
263 let lower_bound = prune_checkpoint
264 .and_then(|chk| chk.block_number)
265 .map(|block_number| block_number + 1)
266 .unwrap_or_default();
267
268 let available_range = lower_bound..=db_tip_block.number;
269
270 if !available_range.contains(&anchor_number) {
272 return Err(ProviderError::InsufficientChangesets {
273 requested: anchor_number,
274 available: available_range,
275 });
276 }
277
278 Ok(Some(anchor_number + 1..=db_tip_block.number))
279 }
280
281 #[instrument(
283 level = "debug",
284 target = "providers::state::overlay",
285 skip_all,
286 fields(?db_tip_block, parent_hash = ?self.parent_hash)
287 )]
288 fn calculate_overlay<Provider>(
289 &self,
290 provider: &Provider,
291 db_tip_block: BlockNumHash,
292 ) -> ProviderResult<Overlay>
293 where
294 Provider: ChangeSetReader
295 + StorageChangeSetReader
296 + DBProvider
297 + BlockNumReader
298 + StageCheckpointReader
299 + PruneCheckpointReader
300 + StorageSettingsCache,
301 {
302 let retrieve_trie_reverts_duration;
306 let retrieve_hashed_state_reverts_duration;
307 let trie_updates_total_len;
308 let hashed_state_updates_total_len;
309 let anchor_hash = match &self.overlay_source {
310 Some(OverlaySource::Managed { manager, .. }) => {
311 let parent_is_persisted = provider
312 .convert_hash_or_number(self.parent_hash.into())?
313 .is_some_and(|parent_number| parent_number <= db_tip_block.number);
314 if parent_is_persisted {
315 self.parent_hash
316 } else {
317 manager
318 .anchor_for_parent(self.parent_hash, db_tip_block.hash)
319 .ok_or(ProviderError::BlockHashNotFound(self.parent_hash))?
320 }
321 }
322 _ => self.parent_hash,
323 };
324
325 let (trie_updates, hashed_post_state) = if let Some(revert_blocks) =
327 self.reverts_required(provider, db_tip_block, anchor_hash)?
328 {
329 debug!(
330 target: "providers::state::overlay",
331 ?revert_blocks,
332 "Collecting trie reverts for overlay state provider"
333 );
334
335 let mut trie_reverts = {
337 let _guard =
338 debug_span!(target: "providers::state::overlay", "retrieving_trie_reverts")
339 .entered();
340
341 let start = Instant::now();
342
343 let accumulated_reverts =
346 self.changeset_cache.get_or_compute_range(provider, revert_blocks.clone())?;
347
348 retrieve_trie_reverts_duration = start.elapsed();
349 accumulated_reverts
350 };
351
352 let mut hashed_state_reverts = {
354 let _guard = debug_span!(target: "providers::state::overlay", "retrieving_hashed_state_reverts").entered();
355
356 let start = Instant::now();
357 let res = reth_trie_db::from_reverts_auto(provider, revert_blocks)?;
358 retrieve_hashed_state_reverts_duration = start.elapsed();
359 res
360 };
361
362 let (overlay_trie, overlay_state) = self.resolve_overlays(anchor_hash)?;
365
366 let trie_updates = if trie_reverts.is_empty() {
367 overlay_trie
368 } else if !overlay_trie.is_empty() {
369 trie_reverts.extend_ref_and_sort(&overlay_trie);
370 Arc::new(trie_reverts)
371 } else {
372 Arc::new(trie_reverts)
373 };
374
375 let hashed_state_updates = if hashed_state_reverts.is_empty() {
376 overlay_state
377 } else if !overlay_state.is_empty() {
378 hashed_state_reverts.extend_ref_and_sort(&overlay_state);
379 Arc::new(hashed_state_reverts)
380 } else {
381 Arc::new(hashed_state_reverts)
382 };
383
384 trie_updates_total_len = trie_updates.total_len();
385 hashed_state_updates_total_len = hashed_state_updates.total_len();
386
387 debug!(
388 target: "providers::state::overlay",
389 num_trie_updates = ?trie_updates_total_len,
390 num_state_updates = ?hashed_state_updates_total_len,
391 "Reverted to anchor block",
392 );
393
394 (trie_updates, hashed_state_updates)
395 } else {
396 let (trie_updates, hashed_state) = self.resolve_overlays(db_tip_block.hash)?;
398
399 retrieve_trie_reverts_duration = Duration::ZERO;
400 retrieve_hashed_state_reverts_duration = Duration::ZERO;
401 trie_updates_total_len = trie_updates.total_len();
402 hashed_state_updates_total_len = hashed_state.total_len();
403
404 (trie_updates, hashed_state)
405 };
406
407 self.metrics
409 .retrieve_trie_reverts_duration
410 .record(retrieve_trie_reverts_duration.as_secs_f64());
411 self.metrics
412 .retrieve_hashed_state_reverts_duration
413 .record(retrieve_hashed_state_reverts_duration.as_secs_f64());
414 self.metrics.trie_updates_size.record(trie_updates_total_len as f64);
415 self.metrics.hashed_state_size.record(hashed_state_updates_total_len as f64);
416
417 Ok(Overlay { trie_updates, hashed_post_state })
418 }
419
420 #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
422 pub(super) fn build_overlay<Provider>(&self, provider: &Provider) -> ProviderResult<Overlay>
423 where
424 Provider: StageCheckpointReader
425 + PruneCheckpointReader
426 + ChangeSetReader
427 + StorageChangeSetReader
428 + DBProvider
429 + BlockNumReader
430 + StorageSettingsCache,
431 {
432 let db_tip_block = self.get_db_tip_block(provider)?;
433 self.calculate_overlay(provider, db_tip_block)
434 }
435}
436
437#[derive(Debug, Clone)]
442pub struct OverlayStateProviderFactory<F, N: NodePrimitives = EthPrimitives> {
443 factory: F,
445 overlay_builder: OverlayBuilder<N>,
447 overlay_cache: Arc<DashMap<BlockHash, Overlay>>,
450}
451
452impl<F, N: NodePrimitives> OverlayStateProviderFactory<F, N> {
453 pub fn new(factory: F, overlay_builder: OverlayBuilder<N>) -> Self {
455 Self { factory, overlay_builder, overlay_cache: Default::default() }
456 }
457
458 pub fn with_hashed_state_overlay(
460 mut self,
461 hashed_state_overlay: Option<Arc<HashedPostStateSorted>>,
462 ) -> Self {
463 self.overlay_builder = self.overlay_builder.with_hashed_state_overlay(hashed_state_overlay);
464 self.overlay_cache = Default::default();
465 self
466 }
467
468 pub fn with_extended_hashed_state_overlay(mut self, other: HashedPostStateSorted) -> Self {
470 self.overlay_builder = self.overlay_builder.with_extended_hashed_state_overlay(other);
471 self.overlay_cache = Default::default();
472 self
473 }
474
475 #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
478 fn get_overlay<Provider>(&self, provider: &Provider) -> ProviderResult<Overlay>
479 where
480 Provider: StageCheckpointReader
481 + PruneCheckpointReader
482 + ChangeSetReader
483 + StorageChangeSetReader
484 + DBProvider
485 + BlockNumReader
486 + StorageSettingsCache,
487 {
488 let db_tip_block = self.overlay_builder.get_db_tip_block(provider)?;
489
490 let overlay = match self.overlay_cache.entry(db_tip_block.hash) {
491 dashmap::Entry::Occupied(entry) => entry.get().clone(),
492 dashmap::Entry::Vacant(entry) => {
493 self.overlay_builder.metrics.overlay_cache_misses.increment(1);
494 let overlay = self.overlay_builder.build_overlay(provider)?;
495 entry.insert(overlay.clone());
496 overlay
497 }
498 };
499
500 Ok(overlay)
501 }
502}
503
504impl<F, N> DatabaseProviderROFactory for OverlayStateProviderFactory<F, N>
505where
506 N: NodePrimitives,
507 F: DatabaseProviderFactory,
508 F::Provider: StageCheckpointReader
509 + PruneCheckpointReader
510 + DBProvider
511 + BlockNumReader
512 + ChangeSetReader
513 + StorageChangeSetReader
514 + StorageSettingsCache,
515{
516 type Provider = OverlayStateProvider<F::Provider>;
517
518 #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
520 fn database_provider_ro(&self) -> ProviderResult<OverlayStateProvider<F::Provider>> {
521 let overall_start = Instant::now();
522
523 let provider = {
525 let start = Instant::now();
526 let res = self.factory.database_provider_ro()?;
527 self.overlay_builder.metrics.create_provider_duration.record(start.elapsed());
528 res
529 };
530
531 let Overlay { trie_updates, hashed_post_state } = self.get_overlay(&provider)?;
532
533 let is_v2 = provider.cached_storage_settings().is_v2();
534 self.overlay_builder.metrics.database_provider_ro_duration.record(overall_start.elapsed());
535 Ok(OverlayStateProvider::new(provider, trie_updates, hashed_post_state, is_v2))
536 }
537}
538
539#[derive(Debug)]
545pub struct OverlayStateProvider<Provider: DBProvider> {
546 provider: Provider,
547 trie_updates: Arc<TrieUpdatesSorted>,
548 hashed_post_state: Arc<HashedPostStateSorted>,
549 is_v2: bool,
550}
551
552impl<Provider> OverlayStateProvider<Provider>
553where
554 Provider: DBProvider,
555{
556 pub const fn new(
559 provider: Provider,
560 trie_updates: Arc<TrieUpdatesSorted>,
561 hashed_post_state: Arc<HashedPostStateSorted>,
562 is_v2: bool,
563 ) -> Self {
564 Self { provider, trie_updates, hashed_post_state, is_v2 }
565 }
566}
567
568impl<Provider> TrieCursorFactory for OverlayStateProvider<Provider>
569where
570 Provider: DBProvider,
571 Provider::Tx: DbTx,
572{
573 type AccountTrieCursor<'a>
574 = InMemoryTrieCursor<'a, Box<dyn TrieCursor + Send + 'a>>
575 where
576 Self: 'a;
577
578 type StorageTrieCursor<'a>
579 = InMemoryTrieCursor<'a, Box<dyn TrieStorageCursor + Send + 'a>>
580 where
581 Self: 'a;
582
583 fn account_trie_cursor(&self) -> Result<Self::AccountTrieCursor<'_>, DatabaseError> {
584 let tx = self.provider.tx_ref();
585 let trie_updates = self.trie_updates.as_ref();
586 let cursor: Box<dyn TrieCursor + Send> = if self.is_v2 {
587 Box::new(DatabaseAccountTrieCursor::<_, PackedKeyAdapter>::new(
588 tx.cursor_read::<PackedAccountsTrie>()?,
589 ))
590 } else {
591 Box::new(DatabaseAccountTrieCursor::<_, LegacyKeyAdapter>::new(
592 tx.cursor_read::<tables::AccountsTrie>()?,
593 ))
594 };
595 Ok(InMemoryTrieCursor::new_account(cursor, trie_updates))
596 }
597
598 fn storage_trie_cursor(
599 &self,
600 hashed_address: B256,
601 ) -> Result<Self::StorageTrieCursor<'_>, DatabaseError> {
602 let tx = self.provider.tx_ref();
603 let trie_updates = self.trie_updates.as_ref();
604 let cursor: Box<dyn TrieStorageCursor + Send> = if self.is_v2 {
605 Box::new(DatabaseStorageTrieCursor::<_, PackedKeyAdapter>::new(
606 tx.cursor_dup_read::<PackedStoragesTrie>()?,
607 hashed_address,
608 ))
609 } else {
610 Box::new(DatabaseStorageTrieCursor::<_, LegacyKeyAdapter>::new(
611 tx.cursor_dup_read::<tables::StoragesTrie>()?,
612 hashed_address,
613 ))
614 };
615 Ok(InMemoryTrieCursor::new_storage(cursor, trie_updates, hashed_address))
616 }
617}
618
619impl<Provider> HashedCursorFactory for OverlayStateProvider<Provider>
620where
621 Provider: DBProvider,
622{
623 type AccountCursor<'a>
624 = <HashedPostStateCursorFactory<
625 DatabaseHashedCursorFactory<&'a Provider::Tx>,
626 &'a Arc<HashedPostStateSorted>,
627 > as HashedCursorFactory>::AccountCursor<'a>
628 where
629 Self: 'a;
630
631 type StorageCursor<'a>
632 = <HashedPostStateCursorFactory<
633 DatabaseHashedCursorFactory<&'a Provider::Tx>,
634 &'a Arc<HashedPostStateSorted>,
635 > as HashedCursorFactory>::StorageCursor<'a>
636 where
637 Self: 'a;
638
639 fn hashed_account_cursor(&self) -> Result<Self::AccountCursor<'_>, DatabaseError> {
640 let db_hashed_cursor_factory = DatabaseHashedCursorFactory::new(self.provider.tx_ref());
641 let hashed_cursor_factory =
642 HashedPostStateCursorFactory::new(db_hashed_cursor_factory, &self.hashed_post_state);
643 hashed_cursor_factory.hashed_account_cursor()
644 }
645
646 fn hashed_storage_cursor(
647 &self,
648 hashed_address: B256,
649 ) -> Result<Self::StorageCursor<'_>, DatabaseError> {
650 let db_hashed_cursor_factory = DatabaseHashedCursorFactory::new(self.provider.tx_ref());
651 let hashed_cursor_factory =
652 HashedPostStateCursorFactory::new(db_hashed_cursor_factory, &self.hashed_post_state);
653 hashed_cursor_factory.hashed_storage_cursor(hashed_address)
654 }
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660 use reth_primitives_traits::Account;
661 use reth_trie::HashedPostState;
662
663 #[test]
664 fn managed_overlay_skips_manager_for_persisted_parent() {
665 let parent_hash = B256::with_last_byte(1);
666 let builder = OverlayBuilder::<EthPrimitives>::new(parent_hash, ChangesetCache::default())
667 .with_state_trie_overlay_manager(StateTrieOverlayManager::default());
668
669 let (trie, state) = builder.resolve_overlays(parent_hash).unwrap();
670 assert!(trie.is_empty());
671 assert!(state.is_empty());
672 }
673
674 #[test]
675 fn managed_overlay_errors_if_parent_is_not_persisted_or_managed() {
676 let parent_hash = B256::with_last_byte(1);
677 let anchor_hash = B256::with_last_byte(2);
678 let builder = OverlayBuilder::<EthPrimitives>::new(parent_hash, ChangesetCache::default())
679 .with_state_trie_overlay_manager(StateTrieOverlayManager::default());
680
681 let err = builder.resolve_overlays(anchor_hash).unwrap_err();
682
683 assert!(err.to_string().contains("cannot be anchored"));
684 }
685
686 #[test]
687 fn extending_hashed_state_keeps_managed_overlay_source() {
688 let parent_hash = B256::with_last_byte(1);
689 let hashed_state = HashedPostState::default()
690 .with_accounts([(B256::with_last_byte(2), Some(Account::default()))])
691 .into_sorted();
692 let builder = OverlayBuilder::<EthPrimitives>::new(parent_hash, ChangesetCache::default())
693 .with_state_trie_overlay_manager(StateTrieOverlayManager::default())
694 .with_extended_hashed_state_overlay(hashed_state);
695
696 let Some(OverlaySource::Managed { state, .. }) = builder.overlay_source else {
697 panic!("expected managed overlay source")
698 };
699 assert_eq!(state.total_len(), 1);
700 }
701}