1use crate::{
2 providers::{
3 state::latest::LatestStateProvider, NodeTypesForProvider, RocksDBProvider,
4 StaticFileProvider, StaticFileProviderRWRefMut,
5 },
6 to_range,
7 traits::{BlockSource, ReceiptProvider},
8 BalProvider, BalStoreHandle, BlockHashReader, BlockNumReader, BlockReader, ChainSpecProvider,
9 DatabaseProviderFactory, EitherWriterDestination, HeaderProvider, HeaderSyncGapProvider,
10 InMemoryBalStore, MetadataProvider, ProviderError, PruneCheckpointReader,
11 RocksDBProviderFactory, StageCheckpointReader, StateProviderBox, StaticFileProviderFactory,
12 StaticFileWriter, TransactionVariant, TransactionsProvider,
13};
14use alloy_consensus::transaction::TransactionMeta;
15use alloy_eips::BlockHashOrNumber;
16use alloy_primitives::{Address, BlockHash, BlockNumber, TxHash, TxNumber, B256};
17use core::fmt;
18use notify::{RecommendedWatcher, RecursiveMode, Watcher};
19use parking_lot::RwLock;
20use reth_chainspec::ChainInfo;
21use reth_db::{init_db, mdbx::DatabaseArguments, DatabaseEnv};
22use reth_db_api::{database::Database, models::StoredBlockBodyIndices};
23use reth_errors::{RethError, RethResult};
24use reth_execution_types::RecoveredBlockAndExecutionOutput;
25use reth_node_types::{
26 BlockTy, HeaderTy, NodeTypesWithDB, NodeTypesWithDBAdapter, ReceiptTy, TxTy,
27};
28use reth_primitives_traits::{RecoveredBlock, SealedHeader};
29use reth_prune_types::{PruneCheckpoint, PruneModes, PruneSegment, MINIMUM_UNWIND_SAFE_DISTANCE};
30use reth_stages_types::{PipelineTarget, StageCheckpoint, StageId};
31use reth_static_file_types::StaticFileSegment;
32use reth_storage_api::{
33 BlockBodyIndicesProvider, ChainStateBlockReader, ChainStateBlockWriter, DBProvider,
34 NodePrimitivesProvider, StorageSettings, StorageSettingsCache,
35};
36use reth_storage_errors::provider::ProviderResult;
37use reth_storage_overlay::OverlayManager;
38use std::{
39 ops::{RangeBounds, RangeInclusive},
40 path::Path,
41 sync::{
42 atomic::{AtomicU64, Ordering},
43 Arc, Mutex,
44 },
45};
46use tracing::{info, instrument, trace, warn};
47
48mod provider;
49pub use provider::{CommitOrder, DatabaseProvider, DatabaseProviderRO, DatabaseProviderRW};
50
51mod save_blocks;
52pub use save_blocks::SaveBlocksInput;
53
54use super::ProviderNodeTypes;
55mod builder;
56pub use builder::{ProviderFactoryBuilder, ReadOnlyConfig};
57
58mod metrics;
59pub use metrics::DatabaseProviderMetrics;
60
61mod chain;
62pub use chain::*;
63
64struct ReadOnlySyncState {
66 last_synced_txnid: AtomicU64,
68 sync_lock: Mutex<()>,
70}
71
72pub struct ProviderFactory<N: NodeTypesWithDB> {
76 db: N::DB,
78 chain_spec: Arc<N::ChainSpec>,
80 static_file_provider: StaticFileProvider<N::Primitives>,
82 prune_modes: PruneModes,
84 storage: Arc<N::Storage>,
86 storage_settings: Arc<RwLock<StorageSettings>>,
88 rocksdb_provider: RocksDBProvider,
90 overlay_manager: OverlayManager<N::Primitives>,
92 bal_store: BalStoreHandle,
94 runtime: reth_tasks::Runtime,
96 minimum_pruning_distance: u64,
98 database_provider_metrics: Arc<DatabaseProviderMetrics>,
100 read_only_sync: Option<Arc<ReadOnlySyncState>>,
105}
106
107impl<N: NodeTypesForProvider> ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>> {
108 pub fn builder() -> ProviderFactoryBuilder<N> {
110 ProviderFactoryBuilder::default()
111 }
112}
113
114impl<N: ProviderNodeTypes> ProviderFactory<N> {
115 pub fn new(
123 db: N::DB,
124 chain_spec: Arc<N::ChainSpec>,
125 static_file_provider: StaticFileProvider<N::Primitives>,
126 rocksdb_provider: RocksDBProvider,
127 runtime: reth_tasks::Runtime,
128 ) -> ProviderResult<Self> {
129 let legacy_settings = StorageSettings::v1();
134 let database_provider_metrics = Arc::new(DatabaseProviderMetrics::default());
135 let overlay_manager = OverlayManager::default();
136 let storage_settings = DatabaseProvider::<_, N>::new(
137 db.tx()?,
138 chain_spec.clone(),
139 static_file_provider.clone(),
140 Default::default(),
141 Default::default(),
142 Arc::new(RwLock::new(legacy_settings)),
143 rocksdb_provider.clone(),
144 overlay_manager.clone(),
145 runtime.clone(),
146 db.path(),
147 database_provider_metrics.clone(),
148 )
149 .storage_settings()?
150 .unwrap_or(legacy_settings);
151
152 Ok(Self {
153 db,
154 chain_spec,
155 static_file_provider,
156 prune_modes: PruneModes::default(),
157 storage: Default::default(),
158 storage_settings: Arc::new(RwLock::new(storage_settings)),
159 rocksdb_provider,
160 overlay_manager,
161 bal_store: BalStoreHandle::new(InMemoryBalStore::default()),
162 runtime,
163 minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE,
164 database_provider_metrics,
165 read_only_sync: None,
166 })
167 }
168
169 pub fn new_checked(
176 db: N::DB,
177 chain_spec: Arc<N::ChainSpec>,
178 static_file_provider: StaticFileProvider<N::Primitives>,
179 rocksdb_provider: RocksDBProvider,
180 runtime: reth_tasks::Runtime,
181 ) -> ProviderResult<Self> {
182 Self::new(db, chain_spec, static_file_provider, rocksdb_provider, runtime)
183 .and_then(Self::assert_consistent)
184 }
185}
186
187impl<N: NodeTypesWithDB> ProviderFactory<N> {
188 pub fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self {
190 self.prune_modes = prune_modes;
191 self
192 }
193
194 pub fn with_bal_store(mut self, bal_store: BalStoreHandle) -> Self {
196 self.bal_store = bal_store;
197 self
198 }
199
200 pub fn with_overlay_manager(mut self, overlay_manager: OverlayManager<N::Primitives>) -> Self {
202 self.overlay_manager = overlay_manager;
203 self
204 }
205
206 pub const fn overlay_manager(&self) -> &OverlayManager<N::Primitives> {
208 &self.overlay_manager
209 }
210
211 pub const fn with_minimum_pruning_distance(mut self, distance: u64) -> Self {
216 self.minimum_pruning_distance = distance;
217 self
218 }
219
220 pub fn with_read_only_sync(mut self, watch: bool) -> Self
226 where
227 N::DB: Database,
228 {
229 let state = Arc::new(ReadOnlySyncState {
233 last_synced_txnid: AtomicU64::new(0),
234 sync_lock: Mutex::new(()),
235 });
236 self.read_only_sync = Some(state);
237
238 if watch {
239 self.watch_db_directory();
240 }
241 self
242 }
243
244 fn watch_db_directory(&self)
247 where
248 N::DB: Database,
249 {
250 let factory = self.clone();
251 let db_path = self.db.path();
252 reth_tasks::spawn_os_thread("ro-sync", move || {
253 let (tx, rx) = std::sync::mpsc::channel();
254 let mut watcher = RecommendedWatcher::new(
255 move |res| {
256 let _ = tx.send(res);
257 },
258 notify::Config::default(),
259 )
260 .expect("failed to create watcher");
261
262 watcher
263 .watch(&db_path, RecursiveMode::NonRecursive)
264 .expect("failed to watch MDBX path");
265
266 while let Ok(res) = rx.recv() {
267 match res {
268 Ok(event) => {
269 if !matches!(
270 event.kind,
271 notify::EventKind::Modify(_) | notify::EventKind::Create(_)
272 ) {
273 continue;
274 }
275
276 if let Err(err) = factory.sync_providers_if_needed() {
277 warn!(target: "reth::provider", %err, "background ro-sync failed");
278 }
279 }
280 Err(err) => {
281 warn!(target: "reth::provider", ?err, "MDBX directory watcher error");
282 }
283 }
284 }
285 });
286 }
287
288 pub fn sync_providers_if_needed(&self) -> ProviderResult<()> {
294 let Some(sync_state) = &self.read_only_sync else { return Ok(()) };
295 let current_txnid = self.db.last_txnid().unwrap_or(0);
296
297 if current_txnid == sync_state.last_synced_txnid.load(Ordering::Relaxed) {
299 return Ok(());
300 }
301
302 let _guard = sync_state.sync_lock.lock().unwrap_or_else(|e| e.into_inner());
304
305 if current_txnid == sync_state.last_synced_txnid.load(Ordering::Relaxed) {
307 return Ok(());
308 }
309
310 self.rocksdb_provider.try_catch_up_with_primary()?;
311 self.static_file_provider.initialize_index()?;
312 sync_state.last_synced_txnid.store(current_txnid, Ordering::Relaxed);
313 Ok(())
314 }
315
316 pub const fn db_ref(&self) -> &N::DB {
318 &self.db
319 }
320
321 #[cfg(any(test, feature = "test-utils"))]
322 pub fn into_db(self) -> N::DB {
324 self.db
325 }
326}
327
328impl<N: NodeTypesWithDB> StorageSettingsCache for ProviderFactory<N> {
329 fn cached_storage_settings(&self) -> StorageSettings {
330 *self.storage_settings.read()
331 }
332
333 fn set_storage_settings_cache(&self, settings: StorageSettings) {
334 *self.storage_settings.write() = settings;
335 }
336}
337
338impl<N: NodeTypesWithDB> RocksDBProviderFactory for ProviderFactory<N> {
339 fn rocksdb_provider(&self) -> RocksDBProvider {
340 self.rocksdb_provider.clone()
341 }
342
343 fn set_pending_rocksdb_batch(&self, _batch: rocksdb::WriteBatchWithTransaction<true>) {
344 unimplemented!("ProviderFactory is a factory, not a provider - use DatabaseProvider::set_pending_rocksdb_batch instead")
345 }
346
347 fn commit_pending_rocksdb_batches(&self) -> ProviderResult<()> {
348 unimplemented!("ProviderFactory is a factory, not a provider - use DatabaseProvider::commit_pending_rocksdb_batches instead")
349 }
350}
351
352impl<N: ProviderNodeTypes<DB = DatabaseEnv>> ProviderFactory<N> {
353 pub fn new_with_database_path<P: AsRef<Path>>(
356 path: P,
357 chain_spec: Arc<N::ChainSpec>,
358 args: DatabaseArguments,
359 static_file_provider: StaticFileProvider<N::Primitives>,
360 rocksdb_provider: RocksDBProvider,
361 runtime: reth_tasks::Runtime,
362 ) -> RethResult<Self> {
363 Self::new(
364 init_db(path, args).map_err(RethError::msg)?,
365 chain_spec,
366 static_file_provider,
367 rocksdb_provider,
368 runtime,
369 )
370 .map_err(RethError::Provider)
371 }
372}
373
374impl<N: ProviderNodeTypes> ProviderFactory<N> {
375 #[track_caller]
382 pub fn provider(&self) -> ProviderResult<DatabaseProviderRO<N::DB, N>> {
383 let db_tx = self.db.tx()?;
384
385 self.sync_providers_if_needed()?;
391
392 Ok(DatabaseProvider::new(
393 db_tx,
394 self.chain_spec.clone(),
395 self.static_file_provider.clone(),
396 self.prune_modes.clone(),
397 self.storage.clone(),
398 self.storage_settings.clone(),
399 self.rocksdb_provider.clone(),
400 self.overlay_manager.clone(),
401 self.runtime.clone(),
402 self.db.path(),
403 self.database_provider_metrics.clone(),
404 )
405 .with_minimum_pruning_distance(self.minimum_pruning_distance))
406 }
407
408 #[track_caller]
413 pub fn provider_rw(&self) -> ProviderResult<DatabaseProviderRW<N::DB, N>> {
414 Ok(DatabaseProviderRW(
415 DatabaseProvider::new_rw(
416 self.db.tx_mut()?,
417 self.chain_spec.clone(),
418 self.static_file_provider.clone(),
419 self.prune_modes.clone(),
420 self.storage.clone(),
421 self.storage_settings.clone(),
422 self.rocksdb_provider.clone(),
423 self.overlay_manager.clone(),
424 self.runtime.clone(),
425 self.db.path(),
426 self.database_provider_metrics.clone(),
427 )
428 .with_reader_txn_tracker(self.db.clone())
429 .with_minimum_pruning_distance(self.minimum_pruning_distance),
430 ))
431 }
432
433 #[track_caller]
440 pub fn unwind_provider_rw(
441 &self,
442 ) -> ProviderResult<DatabaseProvider<<N::DB as Database>::TXMut, N>> {
443 Ok(DatabaseProvider::new_unwind_rw(
444 self.db.tx_mut()?,
445 self.chain_spec.clone(),
446 self.static_file_provider.clone(),
447 self.prune_modes.clone(),
448 self.storage.clone(),
449 self.storage_settings.clone(),
450 self.rocksdb_provider.clone(),
451 self.overlay_manager.clone(),
452 self.runtime.clone(),
453 self.db.path(),
454 self.database_provider_metrics.clone(),
455 )
456 .with_reader_txn_tracker(self.db.clone())
457 .with_minimum_pruning_distance(self.minimum_pruning_distance))
458 }
459
460 #[track_caller]
462 pub fn latest(&self) -> ProviderResult<StateProviderBox> {
463 trace!(target: "providers::db", "Returning latest state provider");
464 Ok(Box::new(LatestStateProvider::new(self.database_provider_ro()?)))
465 }
466
467 pub fn assert_consistent(self) -> ProviderResult<Self> {
472 if let Some(unwind) = self.provider()?.pipeline_unwind()? {
473 return Err(ProviderError::MustUnwind {
474 data_source: "interrupted pipeline unwind",
475 unwind_to: unwind.target,
476 })
477 }
478 let (rocksdb_unwind, static_file_unwind) = self.check_consistency()?;
479
480 let source = match (rocksdb_unwind, static_file_unwind) {
481 (None, None) => return Ok(self),
482 (Some(_), Some(_)) => "RocksDB and Static Files",
483 (Some(_), None) => "RocksDB",
484 (None, Some(_)) => "Static Files",
485 };
486
487 Err(ProviderError::MustUnwind {
488 data_source: source,
489 unwind_to: rocksdb_unwind
490 .into_iter()
491 .chain(static_file_unwind)
492 .min()
493 .expect("at least one unwind target must be Some"),
494 })
495 }
496
497 #[instrument(err, skip(self))]
501 pub fn check_consistency(&self) -> ProviderResult<(Option<u64>, Option<u64>)> {
502 let provider_ro = self
503 .database_provider_ro()?
504 .disable_long_read_transaction_safety();
508
509 self.static_file_provider().check_file_consistency(&provider_ro)?;
511
512 let rocksdb_unwind = self.rocksdb_provider().check_consistency(&provider_ro)?;
514
515 let static_file_unwind = self.static_file_provider().check_consistency(&provider_ro)?.map(
517 |target| match target {
518 PipelineTarget::Unwind(block) => block,
519 PipelineTarget::Sync(_) => unreachable!("check_consistency returns Unwind"),
520 },
521 );
522
523 if rocksdb_unwind.is_none() && static_file_unwind.is_none() {
528 self.heal_chain_state_block_numbers(&provider_ro)?;
529 }
530
531 Ok((rocksdb_unwind, static_file_unwind))
532 }
533
534 fn heal_chain_state_block_numbers(
537 &self,
538 provider_ro: &DatabaseProvider<<N::DB as Database>::TX, N>,
539 ) -> ProviderResult<()> {
540 let highest_header = self.last_block_number()?;
541
542 let finalized = provider_ro.last_finalized_block_number()?;
543 let safe = provider_ro.last_safe_block_number()?;
544
545 if finalized.is_none_or(|f| f <= highest_header) && safe.is_none_or(|s| s <= highest_header)
546 {
547 return Ok(());
548 }
549
550 let provider_rw = self.provider_rw()?;
551
552 if let Some(finalized) = finalized.filter(|&f| f > highest_header) {
553 info!(
554 target: "providers::db",
555 finalized,
556 highest_header,
557 "Healing finalized block number",
558 );
559 provider_rw.save_finalized_block_number(highest_header)?;
560 }
561
562 if let Some(safe) = safe.filter(|&s| s > highest_header) {
563 info!(
564 target: "providers::db",
565 safe,
566 highest_header,
567 "Healing safe block number",
568 );
569 provider_rw.save_safe_block_number(highest_header)?;
570 }
571
572 provider_rw.commit()?;
573
574 Ok(())
575 }
576
577 pub fn caught_up_static_file_provider(
580 &self,
581 ) -> ProviderResult<StaticFileProvider<N::Primitives>> {
582 self.sync_providers_if_needed()?;
583 Ok(self.static_file_provider.clone())
584 }
585}
586
587impl<N: NodeTypesWithDB> NodePrimitivesProvider for ProviderFactory<N> {
588 type Primitives = N::Primitives;
589}
590
591impl<N: ProviderNodeTypes> BalProvider for ProviderFactory<N> {
592 fn bal_store(&self) -> &BalStoreHandle {
593 &self.bal_store
594 }
595}
596
597impl<N: ProviderNodeTypes> DatabaseProviderFactory for ProviderFactory<N> {
598 type DB = N::DB;
599 type Provider = DatabaseProvider<<N::DB as Database>::TX, N>;
600 type ProviderRW = DatabaseProvider<<N::DB as Database>::TXMut, N>;
601
602 fn database_provider_ro(&self) -> ProviderResult<Self::Provider> {
603 self.provider()
604 }
605
606 fn database_provider_rw(&self) -> ProviderResult<Self::ProviderRW> {
607 self.provider_rw().map(|provider| provider.0)
608 }
609}
610
611impl<N: NodeTypesWithDB> StaticFileProviderFactory for ProviderFactory<N> {
612 fn static_file_provider(&self) -> StaticFileProvider<Self::Primitives> {
614 self.static_file_provider.clone()
615 }
616
617 fn get_static_file_writer(
618 &self,
619 block: BlockNumber,
620 segment: StaticFileSegment,
621 ) -> ProviderResult<StaticFileProviderRWRefMut<'_, Self::Primitives>> {
622 self.static_file_provider.get_writer(block, segment)
623 }
624}
625
626impl<N: ProviderNodeTypes> HeaderSyncGapProvider for ProviderFactory<N> {
627 type Header = HeaderTy<N>;
628 fn local_tip_header(
629 &self,
630 highest_uninterrupted_block: BlockNumber,
631 ) -> ProviderResult<SealedHeader<Self::Header>> {
632 self.provider()?.local_tip_header(highest_uninterrupted_block)
633 }
634}
635
636impl<N: ProviderNodeTypes> HeaderProvider for ProviderFactory<N> {
637 type Header = HeaderTy<N>;
638
639 fn header(&self, block_hash: BlockHash) -> ProviderResult<Option<Self::Header>> {
640 self.provider()?.header(block_hash)
641 }
642
643 fn header_by_number(&self, num: BlockNumber) -> ProviderResult<Option<Self::Header>> {
644 self.caught_up_static_file_provider()?.header_by_number(num)
645 }
646
647 fn headers_range(
648 &self,
649 range: impl RangeBounds<BlockNumber>,
650 ) -> ProviderResult<Vec<Self::Header>> {
651 self.caught_up_static_file_provider()?.headers_range(range)
652 }
653
654 fn sealed_header(
655 &self,
656 number: BlockNumber,
657 ) -> ProviderResult<Option<SealedHeader<Self::Header>>> {
658 self.caught_up_static_file_provider()?.sealed_header(number)
659 }
660
661 fn sealed_headers_range(
662 &self,
663 range: impl RangeBounds<BlockNumber>,
664 ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
665 self.caught_up_static_file_provider()?.sealed_headers_range(range)
666 }
667
668 fn sealed_headers_while(
669 &self,
670 range: impl RangeBounds<BlockNumber>,
671 predicate: impl FnMut(&SealedHeader<Self::Header>) -> bool,
672 ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
673 self.caught_up_static_file_provider()?.sealed_headers_while(range, predicate)
674 }
675}
676
677impl<N: ProviderNodeTypes> BlockHashReader for ProviderFactory<N> {
678 fn block_hash(&self, number: u64) -> ProviderResult<Option<B256>> {
679 self.caught_up_static_file_provider()?.block_hash(number)
680 }
681
682 fn canonical_hashes_range(
683 &self,
684 start: BlockNumber,
685 end: BlockNumber,
686 ) -> ProviderResult<Vec<B256>> {
687 self.caught_up_static_file_provider()?.canonical_hashes_range(start, end)
688 }
689}
690
691impl<N: ProviderNodeTypes> BlockNumReader for ProviderFactory<N> {
692 fn chain_info(&self) -> ProviderResult<ChainInfo> {
693 self.provider()?.chain_info()
694 }
695
696 fn best_block_number(&self) -> ProviderResult<BlockNumber> {
697 self.provider()?.best_block_number()
698 }
699
700 fn last_block_number(&self) -> ProviderResult<BlockNumber> {
701 self.caught_up_static_file_provider()?.last_block_number()
702 }
703
704 fn earliest_block_number(&self) -> ProviderResult<BlockNumber> {
705 Ok(self.caught_up_static_file_provider()?.earliest_history_height())
708 }
709
710 fn block_number(&self, hash: B256) -> ProviderResult<Option<BlockNumber>> {
711 self.provider()?.block_number(hash)
712 }
713}
714
715impl<N: ProviderNodeTypes> BlockReader for ProviderFactory<N> {
716 type Block = BlockTy<N>;
717
718 fn find_block_by_hash(
719 &self,
720 hash: B256,
721 source: BlockSource,
722 ) -> ProviderResult<Option<Self::Block>> {
723 self.provider()?.find_block_by_hash(hash, source)
724 }
725
726 fn block(&self, id: BlockHashOrNumber) -> ProviderResult<Option<Self::Block>> {
727 self.provider()?.block(id)
728 }
729
730 fn pending_block(&self) -> ProviderResult<Option<Arc<RecoveredBlock<Self::Block>>>> {
731 self.provider()?.pending_block()
732 }
733
734 fn pending_block_and_receipts(
735 &self,
736 ) -> ProviderResult<Option<RecoveredBlockAndExecutionOutput<Self::Block, Self::Receipt>>> {
737 self.provider()?.pending_block_and_receipts()
738 }
739
740 fn recovered_block(
741 &self,
742 id: BlockHashOrNumber,
743 transaction_kind: TransactionVariant,
744 ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
745 self.provider()?.recovered_block(id, transaction_kind)
746 }
747
748 fn sealed_block_with_senders(
749 &self,
750 id: BlockHashOrNumber,
751 transaction_kind: TransactionVariant,
752 ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
753 self.provider()?.sealed_block_with_senders(id, transaction_kind)
754 }
755
756 fn block_range(&self, range: RangeInclusive<BlockNumber>) -> ProviderResult<Vec<Self::Block>> {
757 self.provider()?.block_range(range)
758 }
759
760 fn block_with_senders_range(
761 &self,
762 range: RangeInclusive<BlockNumber>,
763 ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
764 self.provider()?.block_with_senders_range(range)
765 }
766
767 fn recovered_block_range(
768 &self,
769 range: RangeInclusive<BlockNumber>,
770 ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
771 self.provider()?.recovered_block_range(range)
772 }
773
774 fn block_by_transaction_id(&self, id: TxNumber) -> ProviderResult<Option<BlockNumber>> {
775 self.provider()?.block_by_transaction_id(id)
776 }
777}
778
779impl<N: ProviderNodeTypes> TransactionsProvider for ProviderFactory<N> {
780 type Transaction = TxTy<N>;
781
782 fn transaction_id(&self, tx_hash: TxHash) -> ProviderResult<Option<TxNumber>> {
783 self.provider()?.transaction_id(tx_hash)
784 }
785
786 fn transaction_by_id(&self, id: TxNumber) -> ProviderResult<Option<Self::Transaction>> {
787 self.caught_up_static_file_provider()?.transaction_by_id(id)
788 }
789
790 fn transaction_by_id_unhashed(
791 &self,
792 id: TxNumber,
793 ) -> ProviderResult<Option<Self::Transaction>> {
794 self.caught_up_static_file_provider()?.transaction_by_id_unhashed(id)
795 }
796
797 fn transaction_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Transaction>> {
798 self.provider()?.transaction_by_hash(hash)
799 }
800
801 fn transaction_by_hash_with_meta(
802 &self,
803 tx_hash: TxHash,
804 ) -> ProviderResult<Option<(Self::Transaction, TransactionMeta)>> {
805 self.provider()?.transaction_by_hash_with_meta(tx_hash)
806 }
807
808 fn transactions_by_block(
809 &self,
810 id: BlockHashOrNumber,
811 ) -> ProviderResult<Option<Vec<Self::Transaction>>> {
812 self.provider()?.transactions_by_block(id)
813 }
814
815 fn transactions_by_block_range(
816 &self,
817 range: impl RangeBounds<BlockNumber>,
818 ) -> ProviderResult<Vec<Vec<Self::Transaction>>> {
819 self.provider()?.transactions_by_block_range(range)
820 }
821
822 fn transactions_by_tx_range(
823 &self,
824 range: impl RangeBounds<TxNumber>,
825 ) -> ProviderResult<Vec<Self::Transaction>> {
826 self.caught_up_static_file_provider()?.transactions_by_tx_range(range)
827 }
828
829 fn senders_by_tx_range(
830 &self,
831 range: impl RangeBounds<TxNumber>,
832 ) -> ProviderResult<Vec<Address>> {
833 if EitherWriterDestination::senders(self).is_static_file() {
834 self.caught_up_static_file_provider()?.senders_by_tx_range(range)
835 } else {
836 self.provider()?.senders_by_tx_range(range)
837 }
838 }
839
840 fn transaction_sender(&self, id: TxNumber) -> ProviderResult<Option<Address>> {
841 if EitherWriterDestination::senders(self).is_static_file() {
842 self.caught_up_static_file_provider()?.transaction_sender(id)
843 } else {
844 self.provider()?.transaction_sender(id)
845 }
846 }
847}
848
849impl<N: ProviderNodeTypes> ReceiptProvider for ProviderFactory<N> {
850 type Receipt = ReceiptTy<N>;
851
852 fn receipt(&self, id: TxNumber) -> ProviderResult<Option<Self::Receipt>> {
853 self.caught_up_static_file_provider()?.get_with_static_file_or_database(
854 StaticFileSegment::Receipts,
855 id,
856 |static_file| static_file.receipt(id),
857 || self.provider()?.receipt(id),
858 )
859 }
860
861 fn receipt_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Receipt>> {
862 self.provider()?.receipt_by_hash(hash)
863 }
864
865 fn receipts_by_block(
866 &self,
867 block: BlockHashOrNumber,
868 ) -> ProviderResult<Option<Vec<Self::Receipt>>> {
869 self.provider()?.receipts_by_block(block)
870 }
871
872 fn receipts_by_tx_range(
873 &self,
874 range: impl RangeBounds<TxNumber>,
875 ) -> ProviderResult<Vec<Self::Receipt>> {
876 self.caught_up_static_file_provider()?.get_range_with_static_file_or_database(
877 StaticFileSegment::Receipts,
878 to_range(range),
879 |static_file, range, _| static_file.receipts_by_tx_range(range),
880 |range, _| self.provider()?.receipts_by_tx_range(range),
881 |_| true,
882 )
883 }
884
885 fn receipts_by_block_range(
886 &self,
887 block_range: RangeInclusive<BlockNumber>,
888 ) -> ProviderResult<Vec<Vec<Self::Receipt>>> {
889 self.provider()?.receipts_by_block_range(block_range)
890 }
891}
892
893impl<N: ProviderNodeTypes> BlockBodyIndicesProvider for ProviderFactory<N> {
894 fn block_body_indices(
895 &self,
896 number: BlockNumber,
897 ) -> ProviderResult<Option<StoredBlockBodyIndices>> {
898 self.provider()?.block_body_indices(number)
899 }
900
901 fn block_body_indices_range(
902 &self,
903 range: RangeInclusive<BlockNumber>,
904 ) -> ProviderResult<Vec<StoredBlockBodyIndices>> {
905 self.provider()?.block_body_indices_range(range)
906 }
907}
908
909impl<N: ProviderNodeTypes> StageCheckpointReader for ProviderFactory<N> {
910 fn get_stage_checkpoint(&self, id: StageId) -> ProviderResult<Option<StageCheckpoint>> {
911 self.provider()?.get_stage_checkpoint(id)
912 }
913
914 fn get_stage_checkpoint_progress(&self, id: StageId) -> ProviderResult<Option<Vec<u8>>> {
915 self.provider()?.get_stage_checkpoint_progress(id)
916 }
917 fn get_all_checkpoints(&self) -> ProviderResult<Vec<(String, StageCheckpoint)>> {
918 self.provider()?.get_all_checkpoints()
919 }
920}
921
922impl<N: NodeTypesWithDB> ChainSpecProvider for ProviderFactory<N> {
923 type ChainSpec = N::ChainSpec;
924
925 fn chain_spec(&self) -> Arc<N::ChainSpec> {
926 self.chain_spec.clone()
927 }
928}
929
930impl<N: ProviderNodeTypes> PruneCheckpointReader for ProviderFactory<N> {
931 fn get_prune_checkpoint(
932 &self,
933 segment: PruneSegment,
934 ) -> ProviderResult<Option<PruneCheckpoint>> {
935 self.provider()?.get_prune_checkpoint(segment)
936 }
937
938 fn get_prune_checkpoints(&self) -> ProviderResult<Vec<(PruneSegment, PruneCheckpoint)>> {
939 self.provider()?.get_prune_checkpoints()
940 }
941
942 fn get_prune_checkpoints_for_availability(
943 &self,
944 ) -> ProviderResult<Vec<(PruneSegment, PruneCheckpoint)>> {
945 self.provider()?.get_prune_checkpoints_for_availability()
946 }
947}
948
949impl<N: ProviderNodeTypes> MetadataProvider for ProviderFactory<N> {
950 fn get_metadata(&self, key: &str) -> ProviderResult<Option<Vec<u8>>> {
951 self.provider()?.get_metadata(key)
952 }
953}
954
955impl<N> fmt::Debug for ProviderFactory<N>
956where
957 N: NodeTypesWithDB<DB: fmt::Debug, ChainSpec: fmt::Debug, Storage: fmt::Debug>,
958{
959 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
960 let Self {
961 db,
962 chain_spec,
963 static_file_provider,
964 prune_modes,
965 storage,
966 storage_settings,
967 rocksdb_provider,
968 overlay_manager,
969 bal_store,
970 runtime,
971 minimum_pruning_distance,
972 database_provider_metrics: _,
973 read_only_sync,
974 } = self;
975 f.debug_struct("ProviderFactory")
976 .field("db", &db)
977 .field("chain_spec", &chain_spec)
978 .field("static_file_provider", &static_file_provider)
979 .field("prune_modes", &prune_modes)
980 .field("storage", &storage)
981 .field("storage_settings", &*storage_settings.read())
982 .field("rocksdb_provider", &rocksdb_provider)
983 .field("overlay_manager", &overlay_manager)
984 .field("bal_store", &bal_store)
985 .field("runtime", &runtime)
986 .field("minimum_pruning_distance", &minimum_pruning_distance)
987 .field(
988 "read_only_sync",
989 &read_only_sync.as_ref().map(|s| s.last_synced_txnid.load(Ordering::Relaxed)),
990 )
991 .finish()
992 }
993}
994
995impl<N: NodeTypesWithDB> Clone for ProviderFactory<N> {
996 fn clone(&self) -> Self {
997 Self {
998 db: self.db.clone(),
999 chain_spec: self.chain_spec.clone(),
1000 static_file_provider: self.static_file_provider.clone(),
1001 prune_modes: self.prune_modes.clone(),
1002 storage: self.storage.clone(),
1003 storage_settings: self.storage_settings.clone(),
1004 rocksdb_provider: self.rocksdb_provider.clone(),
1005 overlay_manager: self.overlay_manager.clone(),
1006 bal_store: self.bal_store.clone(),
1007 runtime: self.runtime.clone(),
1008 minimum_pruning_distance: self.minimum_pruning_distance,
1009 database_provider_metrics: self.database_provider_metrics.clone(),
1010 read_only_sync: self.read_only_sync.clone(),
1011 }
1012 }
1013}
1014
1015#[cfg(test)]
1016mod tests {
1017 use super::*;
1018 use crate::{
1019 providers::{StaticFileProvider, StaticFileProviderBuilder, StaticFileWriter},
1020 test_utils::{
1021 blocks::TEST_BLOCK, create_test_provider_factory,
1022 create_test_provider_factory_with_genesis_block_number, MockNodeTypesWithDB,
1023 },
1024 BlockHashReader, BlockNumReader, BlockWriter, DBProvider, HeaderSyncGapProvider,
1025 PruneCheckpointWriter, StageCheckpointWriter, TransactionsProvider,
1026 };
1027 use alloy_eips::BlockNumHash;
1028 use alloy_primitives::{TxNumber, B256};
1029 use assert_matches::assert_matches;
1030 use reth_chainspec::ChainSpecBuilder;
1031 use reth_db::{
1032 mdbx::DatabaseArguments,
1033 test_utils::{create_test_rocksdb_dir, create_test_static_files_dir, ERROR_TEMPDIR},
1034 };
1035 use reth_db_api::{models::SnapAttempt, tables, transaction::DbTxMut};
1036 use reth_ethereum_primitives::Receipt;
1037 use reth_primitives_traits::SignerRecoverable;
1038 use reth_prune_types::{PruneMode, PruneModes};
1039 use reth_storage_api::MetadataWriter;
1040 use reth_storage_errors::provider::ProviderError;
1041 use reth_testing_utils::generators::{self, random_block, random_header, BlockParams};
1042 use std::{ops::RangeInclusive, sync::Arc};
1043
1044 #[test]
1045 fn common_history_provider() {
1046 let factory = create_test_provider_factory();
1047 let _ = factory.latest();
1048 }
1049
1050 #[test]
1051 fn default_chain_info() {
1052 let factory = create_test_provider_factory();
1053 let provider = factory.provider().unwrap();
1054
1055 let chain_info = provider.chain_info().expect("should be ok");
1056 assert_eq!(chain_info.best_number, 0);
1057 assert_eq!(chain_info.best_hash, B256::ZERO);
1058 }
1059
1060 #[test]
1061 fn provider_flow() {
1062 let factory = create_test_provider_factory();
1063 let provider = factory.provider().unwrap();
1064 provider.block_hash(0).unwrap();
1065 let provider_rw = factory.provider_rw().unwrap();
1066 provider_rw.block_hash(0).unwrap();
1067 provider.block_hash(0).unwrap();
1068 }
1069
1070 #[test]
1071 fn provider_factory_with_database_path() {
1072 let chain_spec = ChainSpecBuilder::mainnet().build();
1073 let (_static_dir, static_dir_path) = create_test_static_files_dir();
1074 let (_rocksdb_dir, rocksdb_path) = create_test_rocksdb_dir();
1075 let _db_tempdir = tempfile::TempDir::new().expect(ERROR_TEMPDIR);
1076 let factory = ProviderFactory::<MockNodeTypesWithDB<DatabaseEnv>>::new_with_database_path(
1077 _db_tempdir.path(),
1078 Arc::new(chain_spec),
1079 DatabaseArguments::new(Default::default()),
1080 StaticFileProvider::read_write(static_dir_path).unwrap(),
1081 RocksDBProvider::builder(&rocksdb_path).build().unwrap(),
1082 reth_tasks::Runtime::test(),
1083 )
1084 .unwrap();
1085 let provider = factory.provider().unwrap();
1086 provider.block_hash(0).unwrap();
1087 let provider_rw = factory.provider_rw().unwrap();
1088 provider_rw.block_hash(0).unwrap();
1089 provider.block_hash(0).unwrap();
1090 }
1091
1092 #[test]
1093 fn block_range_readers_reject_expired_history() {
1094 use crate::BlockReader;
1095 use reth_static_file_types::{SegmentHeader, SegmentRangeInclusive, StaticFileSegment};
1096
1097 let factory = create_test_provider_factory();
1098 let mut rng = generators::rng();
1099 let provider_rw = factory.provider_rw().unwrap();
1100 let mut parent = None;
1101 for number in 0..6 {
1102 let block = random_block(
1103 &mut rng,
1104 number,
1105 BlockParams { parent, tx_count: Some(1), ..Default::default() },
1106 );
1107 parent = Some(block.hash());
1108 provider_rw.insert_block(&block.try_recover().unwrap()).unwrap();
1109 }
1110 provider_rw.commit().unwrap();
1111
1112 let static_provider = factory.static_file_provider();
1114 {
1115 let mut writer =
1116 static_provider.latest_writer(StaticFileSegment::Transactions).unwrap();
1117 let header = writer.user_header().clone();
1118 *writer.user_header_mut() = SegmentHeader::new(
1119 header.expected_block_range(),
1120 Some(SegmentRangeInclusive::new(3, 5)),
1121 header.tx_range(),
1122 StaticFileSegment::Transactions,
1123 );
1124 writer.inner().set_dirty();
1125 writer.commit().unwrap();
1126 }
1127 static_provider.initialize_index().unwrap();
1128 assert_eq!(static_provider.earliest_history_height(), 3);
1129
1130 let provider = factory.provider().unwrap();
1131 assert_matches!(
1133 provider.block(1.into()),
1134 Err(ProviderError::BlockExpired { requested: 1, earliest_available: 3 })
1135 );
1136 assert_matches!(
1137 provider.recovered_block(1.into(), Default::default()),
1138 Err(ProviderError::BlockExpired { requested: 1, earliest_available: 3 })
1139 );
1140 assert_matches!(
1142 provider.block_range(1..=4),
1143 Err(ProviderError::BlockExpired { requested: 1, earliest_available: 3 })
1144 );
1145 assert_matches!(
1146 provider.block_with_senders_range(1..=4),
1147 Err(ProviderError::BlockExpired { requested: 1, earliest_available: 3 })
1148 );
1149 assert_matches!(
1150 provider.recovered_block_range(1..=4),
1151 Err(ProviderError::BlockExpired { requested: 1, earliest_available: 3 })
1152 );
1153 assert_eq!(provider.block_range(3..=5).unwrap().len(), 3);
1155 assert_eq!(provider.recovered_block_range(3..=5).unwrap().len(), 3);
1156 }
1157
1158 #[test]
1159 fn insert_block_with_prune_modes() {
1160 let block = TEST_BLOCK.clone();
1161
1162 {
1163 let factory = create_test_provider_factory();
1164 let provider = factory.provider_rw().unwrap();
1165 assert_matches!(provider.insert_block(&block.clone().try_recover().unwrap()), Ok(_));
1166 assert_matches!(
1167 provider.transaction_sender(0), Ok(Some(sender))
1168 if sender == block.body().transactions[0].recover_signer().unwrap()
1169 );
1170 assert_matches!(
1171 provider.transaction_id(*block.body().transactions[0].tx_hash()),
1172 Ok(Some(0))
1173 );
1174 }
1175
1176 {
1177 let prune_modes = PruneModes {
1178 sender_recovery: Some(PruneMode::Full),
1179 transaction_lookup: Some(PruneMode::Full),
1180 ..PruneModes::default()
1181 };
1182 let factory = create_test_provider_factory().with_prune_modes(prune_modes);
1184 let provider = factory.provider_rw().unwrap();
1185 assert_matches!(provider.insert_block(&block.clone().try_recover().unwrap()), Ok(_));
1186 assert_matches!(provider.transaction_sender(0), Ok(None));
1187 assert_matches!(
1188 provider.transaction_id(*block.body().transactions[0].tx_hash()),
1189 Ok(None)
1190 );
1191 }
1192 }
1193
1194 #[test]
1195 fn take_block_transaction_range_recover_senders() {
1196 let mut rng = generators::rng();
1197 let block =
1198 random_block(&mut rng, 0, BlockParams { tx_count: Some(3), ..Default::default() });
1199
1200 let tx_ranges: Vec<RangeInclusive<TxNumber>> = vec![0..=0, 1..=1, 2..=2, 0..=1, 1..=2];
1201 for range in tx_ranges {
1202 let factory = create_test_provider_factory();
1203 let provider = factory.provider_rw().unwrap();
1204
1205 assert_matches!(provider.insert_block(&block.clone().try_recover().unwrap()), Ok(_));
1206
1207 let senders = provider.take::<tables::TransactionSenders>(range.clone()).unwrap();
1208 assert_eq!(
1209 senders,
1210 range
1211 .clone()
1212 .map(|tx_number| (
1213 tx_number,
1214 block.body().transactions[tx_number as usize].recover_signer().unwrap()
1215 ))
1216 .collect::<Vec<_>>()
1217 );
1218
1219 let db_senders = provider.senders_by_tx_range(range);
1220 assert!(matches!(db_senders, Ok(ref v) if v.is_empty()));
1221 }
1222 }
1223
1224 #[test]
1225 fn header_sync_gap_lookup() {
1226 let factory = create_test_provider_factory();
1227 let provider = factory.provider_rw().unwrap();
1228
1229 let mut rng = generators::rng();
1230
1231 let checkpoint = 0;
1233 let head = random_header(&mut rng, 0, None);
1234
1235 assert_matches!(
1237 provider.local_tip_header(checkpoint),
1238 Err(ProviderError::HeaderNotFound(block_number))
1239 if block_number.as_number().unwrap() == checkpoint
1240 );
1241
1242 let static_file_provider = provider.static_file_provider();
1244 let mut static_file_writer =
1245 static_file_provider.latest_writer(StaticFileSegment::Headers).unwrap();
1246 static_file_writer.append_header(head.header(), &head.hash()).unwrap();
1247 static_file_writer.commit().unwrap();
1248 drop(static_file_writer);
1249
1250 let local_head = provider.local_tip_header(checkpoint).unwrap();
1251
1252 assert_eq!(local_head, head);
1253 }
1254
1255 #[test]
1256 fn anchored_static_files_resume_after_the_pivot() {
1257 let factory = create_test_provider_factory();
1258 factory.set_storage_settings_cache(StorageSettings::v2());
1259 let provider = factory.database_provider_rw().unwrap();
1260 provider.anchor_pruned_static_files(10).unwrap();
1261 provider.commit().unwrap();
1262
1263 let static_files = factory.static_file_provider();
1264 for segment in StaticFileSegment::iter().filter(|segment| !segment.is_headers()) {
1265 assert_eq!(static_files.get_highest_static_file_block(segment), Some(10), "{segment}");
1266 }
1267 }
1268
1269 #[test]
1270 fn anchored_static_files_expire_history_below_the_pivot() {
1271 let factory = create_test_provider_factory();
1272 factory.set_storage_settings_cache(StorageSettings::v2());
1273 let provider = factory.database_provider_rw().unwrap();
1274 provider.anchor_pruned_static_files(10).unwrap();
1275 provider.commit().unwrap();
1276
1277 let static_files = factory.static_file_provider();
1278 assert_eq!(static_files.earliest_history_height(), 10);
1279 assert_matches!(
1280 factory.provider().unwrap().block(5.into()),
1281 Err(ProviderError::BlockExpired { requested: 5, earliest_available: 10 })
1282 );
1283
1284 static_files.initialize_index().unwrap();
1285 assert_eq!(static_files.earliest_history_height(), 10);
1286 }
1287
1288 #[test]
1289 fn anchoring_static_files_requires_storage_v2() {
1290 let factory = create_test_provider_factory();
1291 factory.set_storage_settings_cache(StorageSettings::v1());
1292 let provider = factory.database_provider_rw().unwrap();
1293 assert!(provider.anchor_pruned_static_files(10).is_err());
1294
1295 let static_files = factory.static_file_provider();
1296 for segment in StaticFileSegment::iter().filter(|segment| !segment.is_headers()) {
1297 assert_eq!(static_files.get_highest_static_file_block(segment), None, "{segment}");
1298 }
1299 }
1300
1301 #[test]
1302 fn rejected_unwind_below_the_anchor_leaves_the_database_untouched() {
1303 for segment in StaticFileSegment::iter().filter(|segment| !segment.is_headers()) {
1304 let factory = create_test_provider_factory();
1305 factory.set_storage_settings_cache(StorageSettings::v2());
1306 let provider = factory.database_provider_rw().unwrap();
1307 provider.anchor_pruned_static_files(10).unwrap();
1308 provider.save_stage_checkpoint(StageId::Execution, StageCheckpoint::new(10)).unwrap();
1309 provider.commit().unwrap();
1310
1311 let provider = factory.unwind_provider_rw().unwrap();
1313 provider.save_stage_checkpoint(StageId::Execution, StageCheckpoint::new(5)).unwrap();
1314 let queued = {
1315 let static_files = factory.static_file_provider();
1316 let mut writer = static_files.latest_writer(segment).unwrap();
1317 match segment {
1318 StaticFileSegment::Transactions => writer.prune_transactions(0, 5),
1319 StaticFileSegment::Receipts => writer.prune_receipts(0, 5),
1320 StaticFileSegment::TransactionSenders => writer.prune_transaction_senders(0, 5),
1321 StaticFileSegment::AccountChangeSets => writer.prune_account_changesets(5),
1322 StaticFileSegment::StorageChangeSets => writer.prune_storage_changesets(5),
1323 StaticFileSegment::Headers => unreachable!(),
1324 }
1325 };
1326 assert!(queued.and_then(|()| provider.commit()).is_err(), "{segment}");
1327
1328 let provider = factory.provider().unwrap();
1329 assert_eq!(
1330 provider.get_stage_checkpoint(StageId::Execution).unwrap(),
1331 Some(StageCheckpoint::new(10)),
1332 "{segment}"
1333 );
1334 assert_eq!(factory.static_file_provider().check_consistency(&provider).unwrap(), None);
1335 }
1336 }
1337
1338 #[test]
1339 fn a_sync_the_database_cannot_continue_is_refused() {
1340 let factory = create_test_provider_factory();
1341 factory.set_storage_settings_cache(StorageSettings::v2());
1342
1343 let provider = factory.database_provider_ro().unwrap();
1345 assert!(provider.ensure_sync_mode(false).is_ok());
1346 assert!(provider.ensure_sync_mode(true).is_ok());
1347 drop(provider);
1348
1349 let unfinished = SnapAttempt::start(
1350 None,
1351 BlockNumHash::new(10, B256::repeat_byte(1)),
1352 B256::repeat_byte(2),
1353 );
1354 let mut abandoned = unfinished;
1355 abandoned.abandon();
1356 let mut verified = unfinished;
1357 verified.verify();
1358
1359 for attempt in [unfinished, abandoned] {
1361 let provider = factory.database_provider_rw().unwrap();
1362 provider.write_snap_attempt(&attempt).unwrap();
1363 provider.commit().unwrap();
1364
1365 let provider = factory.database_provider_ro().unwrap();
1366 assert!(matches!(
1367 provider.ensure_sync_mode(false).err(),
1368 Some(ProviderError::SnapStateRequiresSnapSync { attempt: 0 })
1369 ));
1370 assert!(provider.ensure_sync_mode(true).is_ok());
1371 }
1372
1373 let provider = factory.database_provider_rw().unwrap();
1375 provider.write_snap_attempt(&verified).unwrap();
1376 provider.commit().unwrap();
1377 assert!(factory.database_provider_ro().unwrap().ensure_sync_mode(false).is_ok());
1378 }
1379
1380 #[test]
1381 fn the_legacy_layout_refuses_snap_until_execution_passes_genesis() {
1382 let factory = create_test_provider_factory_with_genesis_block_number(5);
1384 let provider = factory.database_provider_rw().unwrap();
1385 provider.save_stage_checkpoint(StageId::Execution, StageCheckpoint::new(5)).unwrap();
1386 provider.commit().unwrap();
1387
1388 assert!(matches!(
1390 factory.database_provider_ro().unwrap().ensure_sync_mode(true).err(),
1391 Some(ProviderError::SnapStorageLayoutUnsupported)
1392 ));
1393
1394 let provider = factory.database_provider_rw().unwrap();
1396 provider.save_stage_checkpoint(StageId::Execution, StageCheckpoint::new(42)).unwrap();
1397 provider.commit().unwrap();
1398 assert!(factory.database_provider_ro().unwrap().ensure_sync_mode(true).is_ok());
1399
1400 let provider = factory.database_provider_rw().unwrap();
1402 provider
1403 .write_snap_attempt(&SnapAttempt::start(
1404 None,
1405 BlockNumHash::new(10, B256::repeat_byte(1)),
1406 B256::repeat_byte(2),
1407 ))
1408 .unwrap();
1409 provider.commit().unwrap();
1410 assert!(matches!(
1411 factory.database_provider_ro().unwrap().ensure_sync_mode(true).err(),
1412 Some(ProviderError::SnapStorageLayoutUnsupported)
1413 ));
1414 }
1415
1416 #[test]
1417 fn a_database_without_an_execution_checkpoint_needs_the_snap_layout() {
1418 let factory = create_test_provider_factory();
1420 assert!(matches!(
1421 factory.database_provider_ro().unwrap().ensure_sync_mode(true).err(),
1422 Some(ProviderError::SnapStorageLayoutUnsupported)
1423 ));
1424
1425 factory.set_storage_settings_cache(StorageSettings::v2());
1426 assert!(factory.database_provider_ro().unwrap().ensure_sync_mode(true).is_ok());
1427 }
1428
1429 #[test]
1430 fn receipt_availability_preserves_prune_checkpoints() {
1431 let moved = PruneCheckpoint {
1432 block_number: Some(7),
1433 tx_number: Some(15),
1434 prune_mode: PruneMode::Before(8),
1435 };
1436 let available = PruneCheckpoint { block_number: None, tx_number: None, ..moved };
1437 let pruned = PruneCheckpoint { block_number: Some(4), tx_number: Some(9), ..moved };
1438 let covered = PruneCheckpoint { block_number: Some(2), tx_number: Some(5), ..moved };
1439 let anchor = PruneCheckpoint { tx_number: None, ..moved };
1440
1441 for (case, blocks_per_file, receipt_blocks, stored, expected) in [
1442 ("continuous", 3, vec![(0, 9)], [moved; 2], [available; 2]),
1443 ("pruned prefix", 10, vec![(5, 9)], [pruned; 2], [pruned; 2]),
1444 ("padded prefix", 10, vec![(5, 9)], [moved; 2], [pruned; 2]),
1445 ("above tip", 3, vec![(0, 4)], [moved; 2], [moved; 2]),
1446 ("gap", 3, vec![(0, 2), (4, 9)], [moved; 2], [moved; 2]),
1447 ("empty file gap", 3, vec![(0, 2), (6, 9)], [moved; 2], [moved; 2]),
1448 ("covered receipts", 3, vec![(0, 2), (4, 9)], [covered, moved], [available, moved]),
1449 ("covered logs", 3, vec![(0, 2), (4, 9)], [moved, covered], [moved, available]),
1450 ("snap anchor", 3, vec![(0, 9)], [anchor; 2], [anchor; 2]),
1451 ("empty first file", 3, vec![(5, 9)], [moved; 2], [moved; 2]),
1452 ("no files", 3, vec![], [moved; 2], [moved; 2]),
1453 ] {
1454 let factory =
1455 receipt_availability_test_factory(blocks_per_file, &receipt_blocks, stored);
1456 let before = factory.get_prune_checkpoints().unwrap();
1457 assert_eq!(
1458 factory.get_prune_checkpoints_for_availability().unwrap(),
1459 [(PruneSegment::Receipts, expected[0]), (PruneSegment::ContractLogs, expected[1])],
1460 "{case}",
1461 );
1462 assert_eq!(factory.get_prune_checkpoints().unwrap(), before);
1463 }
1464
1465 let factory = receipt_availability_test_factory(3, &[(0, 9)], [moved; 2]);
1467 factory.set_storage_settings_cache(StorageSettings::v1());
1468 assert_eq!(
1469 factory.get_prune_checkpoints_for_availability().unwrap(),
1470 factory.get_prune_checkpoints().unwrap(),
1471 );
1472 }
1473
1474 #[test]
1475 fn receipt_availability_requires_body_indices() {
1476 let stored = PruneCheckpoint {
1477 block_number: Some(7),
1478 tx_number: Some(15),
1479 prune_mode: PruneMode::Before(8),
1480 };
1481 let factory = receipt_availability_test_factory(10, &[(5, 9)], [stored; 2]);
1482 let unchanged = || {
1483 assert_eq!(
1484 factory.get_prune_checkpoints_for_availability().unwrap(),
1485 factory.get_prune_checkpoints().unwrap(),
1486 );
1487 };
1488
1489 let provider = factory.provider_rw().unwrap();
1490 provider.tx_ref().delete::<tables::BlockBodyIndices>(4, None).unwrap();
1491 provider.commit().unwrap();
1492 unchanged();
1493
1494 let provider = factory.provider_rw().unwrap();
1496 provider.tx_ref().delete::<tables::TransactionBlocks>(9, None).unwrap();
1497 provider.commit().unwrap();
1498 unchanged();
1499
1500 let provider = factory.provider_rw().unwrap();
1501 provider.tx_ref().clear::<tables::TransactionBlocks>().unwrap();
1502 provider.commit().unwrap();
1503 unchanged();
1504 }
1505
1506 fn receipt_availability_test_factory(
1507 blocks_per_file: u64,
1508 receipt_blocks: &[(u64, u64)],
1509 checkpoints: [PruneCheckpoint; 2],
1510 ) -> ProviderFactory<MockNodeTypesWithDB> {
1511 let mut factory = create_test_provider_factory().with_prune_modes(PruneModes {
1512 receipts: Some(PruneMode::Distance(100)),
1513 ..PruneModes::default()
1514 });
1515 factory.set_storage_settings_cache(StorageSettings::v2());
1516 factory.static_file_provider =
1517 StaticFileProviderBuilder::read_write(factory.db.path().join("receipt_files"))
1518 .with_blocks_per_file(blocks_per_file)
1519 .build()
1520 .unwrap();
1521 assert_eq!(factory.get_prune_checkpoints_for_availability().unwrap(), Vec::new());
1522
1523 let provider = factory.provider_rw().unwrap();
1524 for block in 0..=9 {
1526 provider
1527 .tx_ref()
1528 .put::<tables::BlockBodyIndices>(
1529 block,
1530 StoredBlockBodyIndices { first_tx_num: 2 * block, tx_count: 2 },
1531 )
1532 .unwrap();
1533 provider.tx_ref().put::<tables::TransactionBlocks>(2 * block + 1, block).unwrap();
1534 }
1535 for (segment, checkpoint) in
1536 [PruneSegment::Receipts, PruneSegment::ContractLogs].into_iter().zip(checkpoints)
1537 {
1538 provider.save_prune_checkpoint(segment, checkpoint).unwrap();
1539 }
1540 provider.commit().unwrap();
1541
1542 for &(start, end) in receipt_blocks {
1543 let static_file_provider = factory.static_file_provider();
1544 let mut writer =
1545 static_file_provider.latest_writer(StaticFileSegment::Receipts).unwrap();
1546 if let Some(last_pruned) = start.checked_sub(1) {
1547 writer.ensure_at_block(last_pruned).unwrap();
1548 }
1549 for block in start..=end {
1550 writer.increment_block(block).unwrap();
1551 for tx in 2 * block..2 * block + 2 {
1552 writer.append_receipt(tx, &Receipt::default()).unwrap();
1553 }
1554 }
1555 writer.commit().unwrap();
1556 }
1557 factory
1558 }
1559}