Skip to main content

reth_provider/providers/database/
mod.rs

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, HashedPostStateProvider, HeaderProvider,
10    HeaderSyncGapProvider, InMemoryBalStore, MetadataProvider, ProviderError,
11    PruneCheckpointReader, RocksDBProviderFactory, StageCheckpointReader, StateProviderBox,
12    StaticFileProviderFactory, 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_node_types::{
25    BlockTy, HeaderTy, NodeTypesWithDB, NodeTypesWithDBAdapter, ReceiptTy, TxTy,
26};
27use reth_primitives_traits::{RecoveredBlock, SealedHeader};
28use reth_prune_types::{PruneCheckpoint, PruneModes, PruneSegment, MINIMUM_UNWIND_SAFE_DISTANCE};
29use reth_stages_types::{PipelineTarget, StageCheckpoint, StageId};
30use reth_static_file_types::StaticFileSegment;
31use reth_storage_api::{
32    BlockBodyIndicesProvider, ChainStateBlockReader, ChainStateBlockWriter, DBProvider,
33    NodePrimitivesProvider, StorageSettings, StorageSettingsCache, TryIntoHistoricalStateProvider,
34};
35use reth_storage_errors::provider::ProviderResult;
36use reth_trie::HashedPostState;
37use reth_trie_db::ChangesetCache;
38use revm::database::BundleState;
39use std::{
40    ops::{RangeBounds, RangeInclusive},
41    path::Path,
42    sync::{
43        atomic::{AtomicU64, Ordering},
44        Arc, Mutex,
45    },
46};
47use tracing::{info, instrument, trace, warn};
48
49mod provider;
50pub use provider::{
51    CommitOrder, DatabaseProvider, DatabaseProviderRO, DatabaseProviderRW, SaveBlocksMode,
52};
53
54use super::ProviderNodeTypes;
55use reth_trie::KeccakKeyHasher;
56
57mod builder;
58pub use builder::{ProviderFactoryBuilder, ReadOnlyConfig};
59
60mod metrics;
61pub use metrics::DatabaseProviderMetrics;
62
63mod chain;
64pub use chain::*;
65
66/// Sync state for read-only [`ProviderFactory`] instances.
67struct ReadOnlySyncState {
68    /// Last MDBX txn ID we synced `RocksDB` secondary / static file indexes to.
69    last_synced_txnid: AtomicU64,
70    /// Serializes the slow-path catch-up (`RocksDB` + static file re-init).
71    sync_lock: Mutex<()>,
72}
73
74/// A common provider that fetches data from a database or static file.
75///
76/// This provider implements most provider or provider factory traits.
77pub struct ProviderFactory<N: NodeTypesWithDB> {
78    /// Database instance
79    db: N::DB,
80    /// Chain spec
81    chain_spec: Arc<N::ChainSpec>,
82    /// Static File Provider
83    static_file_provider: StaticFileProvider<N::Primitives>,
84    /// Optional pruning configuration
85    prune_modes: PruneModes,
86    /// The node storage handler.
87    storage: Arc<N::Storage>,
88    /// Storage configuration settings for this node
89    storage_settings: Arc<RwLock<StorageSettings>>,
90    /// `RocksDB` provider
91    rocksdb_provider: RocksDBProvider,
92    /// Changeset cache for trie unwinding
93    changeset_cache: ChangesetCache,
94    /// Store for block access lists.
95    bal_store: BalStoreHandle,
96    /// Task runtime for spawning parallel I/O work.
97    runtime: reth_tasks::Runtime,
98    /// Minimum distance from tip required before pruning can occur.
99    minimum_pruning_distance: u64,
100    /// Database provider metrics shared by providers created from this factory.
101    database_provider_metrics: Arc<DatabaseProviderMetrics>,
102    /// State for on-demand syncing of `RocksDB` secondary and static file indexes.
103    ///
104    /// Only set for read-only factories. Can be disabled if there is no concurrent read-write
105    /// factory writing to the database (e.g as part of a running reth node).
106    read_only_sync: Option<Arc<ReadOnlySyncState>>,
107}
108
109impl<N: NodeTypesForProvider> ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>> {
110    /// Instantiates the builder for this type
111    pub fn builder() -> ProviderFactoryBuilder<N> {
112        ProviderFactoryBuilder::default()
113    }
114}
115
116impl<N: ProviderNodeTypes> ProviderFactory<N> {
117    /// Create new database provider factory.
118    ///
119    /// The storage backends used by the produced factory MAY be inconsistent.
120    /// It is recommended to call [`Self::check_consistency`] after
121    /// creation to ensure consistency between the database and static files.
122    /// If the function returns unwind targets, the caller MUST unwind the
123    /// inner database to the minimum of the two targets to ensure consistency.
124    pub fn new(
125        db: N::DB,
126        chain_spec: Arc<N::ChainSpec>,
127        static_file_provider: StaticFileProvider<N::Primitives>,
128        rocksdb_provider: RocksDBProvider,
129        runtime: reth_tasks::Runtime,
130    ) -> ProviderResult<Self> {
131        // Load storage settings from database at init time. Creates a temporary provider
132        // to read persisted settings, falling back to legacy defaults if none exist.
133        //
134        // Both factory and all providers it creates should share these cached settings.
135        let legacy_settings = StorageSettings::v1();
136        let database_provider_metrics = Arc::new(DatabaseProviderMetrics::default());
137        let storage_settings = DatabaseProvider::<_, N>::new(
138            db.tx()?,
139            chain_spec.clone(),
140            static_file_provider.clone(),
141            Default::default(),
142            Default::default(),
143            Arc::new(RwLock::new(legacy_settings)),
144            rocksdb_provider.clone(),
145            ChangesetCache::new(),
146            runtime.clone(),
147            db.path(),
148            database_provider_metrics.clone(),
149        )
150        .storage_settings()?
151        .unwrap_or(legacy_settings);
152
153        Ok(Self {
154            db,
155            chain_spec,
156            static_file_provider,
157            prune_modes: PruneModes::default(),
158            storage: Default::default(),
159            storage_settings: Arc::new(RwLock::new(storage_settings)),
160            rocksdb_provider,
161            changeset_cache: ChangesetCache::new(),
162            bal_store: BalStoreHandle::new(InMemoryBalStore::default()),
163            runtime,
164            minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE,
165            database_provider_metrics,
166            read_only_sync: None,
167        })
168    }
169
170    /// Create new database provider factory and perform consistency checks.
171    ///
172    /// This will call [`Self::check_consistency`] internally and return
173    /// [`ProviderError::MustUnwind`] if inconsistencies are found. It may also
174    /// return any [`ProviderError`] that [`Self::new`] may return, or that are
175    /// encountered during consistency checks.
176    pub fn new_checked(
177        db: N::DB,
178        chain_spec: Arc<N::ChainSpec>,
179        static_file_provider: StaticFileProvider<N::Primitives>,
180        rocksdb_provider: RocksDBProvider,
181        runtime: reth_tasks::Runtime,
182    ) -> ProviderResult<Self> {
183        Self::new(db, chain_spec, static_file_provider, rocksdb_provider, runtime)
184            .and_then(Self::assert_consistent)
185    }
186}
187
188impl<N: NodeTypesWithDB> ProviderFactory<N> {
189    /// Sets the pruning configuration for an existing [`ProviderFactory`].
190    pub fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self {
191        self.prune_modes = prune_modes;
192        self
193    }
194
195    /// Sets the BAL store for an existing [`ProviderFactory`].
196    pub fn with_bal_store(mut self, bal_store: BalStoreHandle) -> Self {
197        self.bal_store = bal_store;
198        self
199    }
200
201    /// Sets the changeset cache for an existing [`ProviderFactory`].
202    pub fn with_changeset_cache(mut self, changeset_cache: ChangesetCache) -> Self {
203        self.changeset_cache = changeset_cache;
204        self
205    }
206
207    /// Returns the shared changeset cache.
208    pub(crate) fn changeset_cache(&self) -> ChangesetCache {
209        self.changeset_cache.clone()
210    }
211
212    /// Sets the minimum pruning distance for an existing [`ProviderFactory`].
213    ///
214    /// This controls the minimum distance from tip required before pruning can occur.
215    /// The default is [`MINIMUM_UNWIND_SAFE_DISTANCE`].
216    pub const fn with_minimum_pruning_distance(mut self, distance: u64) -> Self {
217        self.minimum_pruning_distance = distance;
218        self
219    }
220
221    /// Enables on-demand syncing of `RocksDB` secondary and static file indexes for read-only
222    /// factories. Initializes the tracker to the current MDBX txn ID.
223    ///
224    /// Should be used for read-only factories that are running concurrently to a reth node writing
225    /// new data to the database. Would effectively be a no-op if database directory is unchanged.
226    pub fn with_read_only_sync(mut self, watch: bool) -> Self
227    where
228        N::DB: Database,
229    {
230        // Initialize to 0 so the first `sync_providers_if_needed` call always
231        // triggers a RocksDB/static-file catch-up, regardless of what MDBX txnid
232        // the database was at when we opened it.
233        let state = Arc::new(ReadOnlySyncState {
234            last_synced_txnid: AtomicU64::new(0),
235            sync_lock: Mutex::new(()),
236        });
237        self.read_only_sync = Some(state);
238
239        if watch {
240            self.watch_db_directory();
241        }
242        self
243    }
244
245    /// Watches the MDBX data directory for changes and eagerly syncs `RocksDB` secondary and
246    /// static file indexes when modifications are detected.
247    fn watch_db_directory(&self)
248    where
249        N::DB: Database,
250    {
251        let factory = self.clone();
252        let db_path = self.db.path();
253        reth_tasks::spawn_os_thread("ro-sync", move || {
254            let (tx, rx) = std::sync::mpsc::channel();
255            let mut watcher = RecommendedWatcher::new(
256                move |res| {
257                    let _ = tx.send(res);
258                },
259                notify::Config::default(),
260            )
261            .expect("failed to create watcher");
262
263            watcher
264                .watch(&db_path, RecursiveMode::NonRecursive)
265                .expect("failed to watch MDBX path");
266
267            while let Ok(res) = rx.recv() {
268                match res {
269                    Ok(event) => {
270                        if !matches!(
271                            event.kind,
272                            notify::EventKind::Modify(_) | notify::EventKind::Create(_)
273                        ) {
274                            continue;
275                        }
276
277                        if let Err(err) = factory.sync_providers_if_needed() {
278                            warn!(target: "reth::provider", %err, "background ro-sync failed");
279                        }
280                    }
281                    Err(err) => {
282                        warn!(target: "reth::provider", ?err, "MDBX directory watcher error");
283                    }
284                }
285            }
286        });
287    }
288
289    /// For read-only factories, checks whether the MDBX committed txn ID has advanced since the
290    /// last sync and, if so, catches up the `RocksDB` secondary instance and re-initializes the
291    /// static file index.
292    ///
293    /// No-op for read-write factories.
294    pub fn sync_providers_if_needed(&self) -> ProviderResult<()> {
295        let Some(sync_state) = &self.read_only_sync else { return Ok(()) };
296        let current_txnid = self.db.last_txnid().unwrap_or(0);
297
298        // Fast path: no contention when nothing changed.
299        if current_txnid == sync_state.last_synced_txnid.load(Ordering::Relaxed) {
300            return Ok(());
301        }
302
303        // Slow path: serialize the actual catch-up I/O.
304        let _guard = sync_state.sync_lock.lock().unwrap_or_else(|e| e.into_inner());
305
306        // Double-check after acquiring the lock — another thread may have already synced.
307        if current_txnid == sync_state.last_synced_txnid.load(Ordering::Relaxed) {
308            return Ok(());
309        }
310
311        self.rocksdb_provider.try_catch_up_with_primary()?;
312        self.static_file_provider.initialize_index()?;
313        sync_state.last_synced_txnid.store(current_txnid, Ordering::Relaxed);
314        Ok(())
315    }
316
317    /// Returns reference to the underlying database.
318    pub const fn db_ref(&self) -> &N::DB {
319        &self.db
320    }
321
322    #[cfg(any(test, feature = "test-utils"))]
323    /// Consumes Self and returns DB
324    pub fn into_db(self) -> N::DB {
325        self.db
326    }
327}
328
329impl<N: NodeTypesWithDB> StorageSettingsCache for ProviderFactory<N> {
330    fn cached_storage_settings(&self) -> StorageSettings {
331        *self.storage_settings.read()
332    }
333
334    fn set_storage_settings_cache(&self, settings: StorageSettings) {
335        *self.storage_settings.write() = settings;
336    }
337}
338
339impl<N: NodeTypesWithDB> RocksDBProviderFactory for ProviderFactory<N> {
340    fn rocksdb_provider(&self) -> RocksDBProvider {
341        self.rocksdb_provider.clone()
342    }
343
344    fn set_pending_rocksdb_batch(&self, _batch: rocksdb::WriteBatchWithTransaction<true>) {
345        unimplemented!("ProviderFactory is a factory, not a provider - use DatabaseProvider::set_pending_rocksdb_batch instead")
346    }
347
348    fn commit_pending_rocksdb_batches(&self) -> ProviderResult<()> {
349        unimplemented!("ProviderFactory is a factory, not a provider - use DatabaseProvider::commit_pending_rocksdb_batches instead")
350    }
351}
352
353impl<N: ProviderNodeTypes<DB = DatabaseEnv>> ProviderFactory<N> {
354    /// Create new database provider by passing a path. [`ProviderFactory`] will own the database
355    /// instance.
356    pub fn new_with_database_path<P: AsRef<Path>>(
357        path: P,
358        chain_spec: Arc<N::ChainSpec>,
359        args: DatabaseArguments,
360        static_file_provider: StaticFileProvider<N::Primitives>,
361        rocksdb_provider: RocksDBProvider,
362        runtime: reth_tasks::Runtime,
363    ) -> RethResult<Self> {
364        Self::new(
365            init_db(path, args).map_err(RethError::msg)?,
366            chain_spec,
367            static_file_provider,
368            rocksdb_provider,
369            runtime,
370        )
371        .map_err(RethError::Provider)
372    }
373}
374
375impl<N: ProviderNodeTypes> ProviderFactory<N> {
376    /// Returns a provider with a created `DbTx` inside, which allows fetching data from the
377    /// database using different types of providers. Example: [`HeaderProvider`]
378    /// [`BlockHashReader`]. This may fail if the inner read database transaction fails to open.
379    ///
380    /// This sets the [`PruneModes`] to [`None`], because they should only be relevant for writing
381    /// data.
382    #[track_caller]
383    pub fn provider(&self) -> ProviderResult<DatabaseProviderRO<N::DB, N>> {
384        let db_tx = self.db.tx()?;
385
386        // Sync providers after opening the database transaction to make
387        // sure that no data is pruned from rocksdb or static files.
388        //
389        // Reorg logic ensures that no data is pruned from rocksdb or static files while there is an
390        // mdbx transaction open that might rely on this data.
391        self.sync_providers_if_needed()?;
392
393        Ok(DatabaseProvider::new(
394            db_tx,
395            self.chain_spec.clone(),
396            self.static_file_provider.clone(),
397            self.prune_modes.clone(),
398            self.storage.clone(),
399            self.storage_settings.clone(),
400            self.rocksdb_provider.clone(),
401            self.changeset_cache.clone(),
402            self.runtime.clone(),
403            self.db.path(),
404            self.database_provider_metrics.clone(),
405        )
406        .with_minimum_pruning_distance(self.minimum_pruning_distance))
407    }
408
409    /// Returns a provider with a created `DbTxMut` inside, which allows fetching and updating
410    /// data from the database using different types of providers. Example: [`HeaderProvider`]
411    /// [`BlockHashReader`].  This may fail if the inner read/write database transaction fails to
412    /// open.
413    #[track_caller]
414    pub fn provider_rw(&self) -> ProviderResult<DatabaseProviderRW<N::DB, N>> {
415        Ok(DatabaseProviderRW(
416            DatabaseProvider::new_rw(
417                self.db.tx_mut()?,
418                self.chain_spec.clone(),
419                self.static_file_provider.clone(),
420                self.prune_modes.clone(),
421                self.storage.clone(),
422                self.storage_settings.clone(),
423                self.rocksdb_provider.clone(),
424                self.changeset_cache.clone(),
425                self.runtime.clone(),
426                self.db.path(),
427                self.database_provider_metrics.clone(),
428            )
429            .with_reader_txn_tracker(self.db.clone())
430            .with_minimum_pruning_distance(self.minimum_pruning_distance),
431        ))
432    }
433
434    /// Returns a provider with a created `DbTxMut` inside, configured for unwind operations.
435    /// Uses unwind commit order (MDBX first, then `RocksDB`, then static files) to allow
436    /// recovery by truncating static files on restart if interrupted.
437    ///
438    /// Unwind commits may wait for pre-existing readers to drain before finishing later
439    /// cross-store steps. Drop any long-lived read providers before committing this provider.
440    #[track_caller]
441    pub fn unwind_provider_rw(
442        &self,
443    ) -> ProviderResult<DatabaseProvider<<N::DB as Database>::TXMut, N>> {
444        Ok(DatabaseProvider::new_unwind_rw(
445            self.db.tx_mut()?,
446            self.chain_spec.clone(),
447            self.static_file_provider.clone(),
448            self.prune_modes.clone(),
449            self.storage.clone(),
450            self.storage_settings.clone(),
451            self.rocksdb_provider.clone(),
452            self.changeset_cache.clone(),
453            self.runtime.clone(),
454            self.db.path(),
455            self.database_provider_metrics.clone(),
456        )
457        .with_reader_txn_tracker(self.db.clone())
458        .with_minimum_pruning_distance(self.minimum_pruning_distance))
459    }
460
461    /// State provider for latest block
462    #[track_caller]
463    pub fn latest(&self) -> ProviderResult<StateProviderBox> {
464        trace!(target: "providers::db", "Returning latest state provider");
465        Ok(Box::new(LatestStateProvider::new(self.database_provider_ro()?)))
466    }
467
468    /// Storage provider for state at that given block
469    pub fn history_by_block_number(
470        &self,
471        block_number: BlockNumber,
472    ) -> ProviderResult<StateProviderBox> {
473        let state_provider = self.provider()?.try_into_history_at_block(block_number)?;
474        trace!(target: "providers::db", ?block_number, "Returning historical state provider for block number");
475        Ok(state_provider)
476    }
477
478    /// Storage provider for state at that given block hash
479    pub fn history_by_block_hash(&self, block_hash: BlockHash) -> ProviderResult<StateProviderBox> {
480        let provider = self.provider()?;
481
482        let block_number = provider
483            .block_number(block_hash)?
484            .ok_or(ProviderError::BlockHashNotFound(block_hash))?;
485
486        let state_provider = provider.try_into_history_at_block(block_number)?;
487        trace!(target: "providers::db", ?block_number, %block_hash, "Returning historical state provider for block hash");
488        Ok(state_provider)
489    }
490
491    /// Asserts that the static files and database are consistent. If not,
492    /// returns [`ProviderError::MustUnwind`] with the appropriate unwind
493    /// target. May also return any [`ProviderError`] that
494    /// [`Self::check_consistency`] may return.
495    pub fn assert_consistent(self) -> ProviderResult<Self> {
496        let (rocksdb_unwind, static_file_unwind) = self.check_consistency()?;
497
498        let source = match (rocksdb_unwind, static_file_unwind) {
499            (None, None) => return Ok(self),
500            (Some(_), Some(_)) => "RocksDB and Static Files",
501            (Some(_), None) => "RocksDB",
502            (None, Some(_)) => "Static Files",
503        };
504
505        Err(ProviderError::MustUnwind {
506            data_source: source,
507            unwind_to: rocksdb_unwind
508                .into_iter()
509                .chain(static_file_unwind)
510                .min()
511                .expect("at least one unwind target must be Some"),
512        })
513    }
514
515    /// Checks the consistency between the static files and the database. This
516    /// may result in static files being pruned or otherwise healed to ensure
517    /// consistency. I.e. this MAY result in writes to the static files.
518    #[instrument(err, skip(self))]
519    pub fn check_consistency(&self) -> ProviderResult<(Option<u64>, Option<u64>)> {
520        let provider_ro = self
521            .database_provider_ro()?
522            // Healing can run long-lived read transactions (e.g., iterating changesets
523            // over millions of blocks). Disable the default timeout so MDBX doesn't
524            // kill the transaction mid-heal, which causes a crash loop on startup.
525            .disable_long_read_transaction_safety();
526
527        // Step 1: heal file-level inconsistencies (no pruning)
528        self.static_file_provider().check_file_consistency(&provider_ro)?;
529
530        // Step 2: RocksDB consistency check (needs static files tx data)
531        let rocksdb_unwind = self.rocksdb_provider().check_consistency(&provider_ro)?;
532
533        // Step 3: Static file checkpoint consistency (may prune)
534        let static_file_unwind = self.static_file_provider().check_consistency(&provider_ro)?.map(
535            |target| match target {
536                PipelineTarget::Unwind(block) => block,
537                PipelineTarget::Sync(_) => unreachable!("check_consistency returns Unwind"),
538            },
539        );
540
541        // Step 4: Heal finalized/safe block numbers that may be ahead of the
542        // highest header on nodes coming from <=1.10.2.
543        //
544        // Unwinds already set it to the target block.
545        if rocksdb_unwind.is_none() && static_file_unwind.is_none() {
546            self.heal_chain_state_block_numbers(&provider_ro)?;
547        }
548
549        Ok((rocksdb_unwind, static_file_unwind))
550    }
551
552    /// If the stored finalized or safe block number is ahead of the highest
553    /// header, resets it to the highest header.
554    fn heal_chain_state_block_numbers(
555        &self,
556        provider_ro: &DatabaseProvider<<N::DB as Database>::TX, N>,
557    ) -> ProviderResult<()> {
558        let highest_header = self.last_block_number()?;
559
560        let finalized = provider_ro.last_finalized_block_number()?;
561        let safe = provider_ro.last_safe_block_number()?;
562
563        if finalized.is_none_or(|f| f <= highest_header) && safe.is_none_or(|s| s <= highest_header)
564        {
565            return Ok(());
566        }
567
568        let provider_rw = self.provider_rw()?;
569
570        if let Some(finalized) = finalized.filter(|&f| f > highest_header) {
571            info!(
572                target: "providers::db",
573                finalized,
574                highest_header,
575                "Healing finalized block number",
576            );
577            provider_rw.save_finalized_block_number(highest_header)?;
578        }
579
580        if let Some(safe) = safe.filter(|&s| s > highest_header) {
581            info!(
582                target: "providers::db",
583                safe,
584                highest_header,
585                "Healing safe block number",
586            );
587            provider_rw.save_safe_block_number(highest_header)?;
588        }
589
590        provider_rw.commit()?;
591
592        Ok(())
593    }
594
595    /// Returns a static file provider. For read-only instances, this will also invoke
596    /// [`Self::sync_providers_if_needed`] to make sure that the static file provider is up to date.
597    pub fn caught_up_static_file_provider(
598        &self,
599    ) -> ProviderResult<StaticFileProvider<N::Primitives>> {
600        self.sync_providers_if_needed()?;
601        Ok(self.static_file_provider.clone())
602    }
603}
604
605impl<N: NodeTypesWithDB> NodePrimitivesProvider for ProviderFactory<N> {
606    type Primitives = N::Primitives;
607}
608
609impl<N: NodeTypesWithDB> BalProvider for ProviderFactory<N> {
610    fn bal_store(&self) -> &BalStoreHandle {
611        &self.bal_store
612    }
613}
614
615impl<N: ProviderNodeTypes> DatabaseProviderFactory for ProviderFactory<N> {
616    type DB = N::DB;
617    type Provider = DatabaseProvider<<N::DB as Database>::TX, N>;
618    type ProviderRW = DatabaseProvider<<N::DB as Database>::TXMut, N>;
619
620    fn database_provider_ro(&self) -> ProviderResult<Self::Provider> {
621        self.provider()
622    }
623
624    fn database_provider_rw(&self) -> ProviderResult<Self::ProviderRW> {
625        self.provider_rw().map(|provider| provider.0)
626    }
627}
628
629impl<N: NodeTypesWithDB> StaticFileProviderFactory for ProviderFactory<N> {
630    /// Returns static file provider
631    fn static_file_provider(&self) -> StaticFileProvider<Self::Primitives> {
632        self.static_file_provider.clone()
633    }
634
635    fn get_static_file_writer(
636        &self,
637        block: BlockNumber,
638        segment: StaticFileSegment,
639    ) -> ProviderResult<StaticFileProviderRWRefMut<'_, Self::Primitives>> {
640        self.static_file_provider.get_writer(block, segment)
641    }
642}
643
644impl<N: ProviderNodeTypes> HeaderSyncGapProvider for ProviderFactory<N> {
645    type Header = HeaderTy<N>;
646    fn local_tip_header(
647        &self,
648        highest_uninterrupted_block: BlockNumber,
649    ) -> ProviderResult<SealedHeader<Self::Header>> {
650        self.provider()?.local_tip_header(highest_uninterrupted_block)
651    }
652}
653
654impl<N: ProviderNodeTypes> HeaderProvider for ProviderFactory<N> {
655    type Header = HeaderTy<N>;
656
657    fn header(&self, block_hash: BlockHash) -> ProviderResult<Option<Self::Header>> {
658        self.provider()?.header(block_hash)
659    }
660
661    fn header_by_number(&self, num: BlockNumber) -> ProviderResult<Option<Self::Header>> {
662        self.caught_up_static_file_provider()?.header_by_number(num)
663    }
664
665    fn headers_range(
666        &self,
667        range: impl RangeBounds<BlockNumber>,
668    ) -> ProviderResult<Vec<Self::Header>> {
669        self.caught_up_static_file_provider()?.headers_range(range)
670    }
671
672    fn sealed_header(
673        &self,
674        number: BlockNumber,
675    ) -> ProviderResult<Option<SealedHeader<Self::Header>>> {
676        self.caught_up_static_file_provider()?.sealed_header(number)
677    }
678
679    fn sealed_headers_range(
680        &self,
681        range: impl RangeBounds<BlockNumber>,
682    ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
683        self.caught_up_static_file_provider()?.sealed_headers_range(range)
684    }
685
686    fn sealed_headers_while(
687        &self,
688        range: impl RangeBounds<BlockNumber>,
689        predicate: impl FnMut(&SealedHeader<Self::Header>) -> bool,
690    ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
691        self.caught_up_static_file_provider()?.sealed_headers_while(range, predicate)
692    }
693}
694
695impl<N: ProviderNodeTypes> BlockHashReader for ProviderFactory<N> {
696    fn block_hash(&self, number: u64) -> ProviderResult<Option<B256>> {
697        self.caught_up_static_file_provider()?.block_hash(number)
698    }
699
700    fn canonical_hashes_range(
701        &self,
702        start: BlockNumber,
703        end: BlockNumber,
704    ) -> ProviderResult<Vec<B256>> {
705        self.caught_up_static_file_provider()?.canonical_hashes_range(start, end)
706    }
707}
708
709impl<N: ProviderNodeTypes> BlockNumReader for ProviderFactory<N> {
710    fn chain_info(&self) -> ProviderResult<ChainInfo> {
711        self.provider()?.chain_info()
712    }
713
714    fn best_block_number(&self) -> ProviderResult<BlockNumber> {
715        self.provider()?.best_block_number()
716    }
717
718    fn last_block_number(&self) -> ProviderResult<BlockNumber> {
719        self.caught_up_static_file_provider()?.last_block_number()
720    }
721
722    fn earliest_block_number(&self) -> ProviderResult<BlockNumber> {
723        // earliest history height tracks the lowest block number that has __not__ been expired, in
724        // other words, the first/earliest available block.
725        Ok(self.caught_up_static_file_provider()?.earliest_history_height())
726    }
727
728    fn block_number(&self, hash: B256) -> ProviderResult<Option<BlockNumber>> {
729        self.provider()?.block_number(hash)
730    }
731}
732
733impl<N: ProviderNodeTypes> BlockReader for ProviderFactory<N> {
734    type Block = BlockTy<N>;
735
736    fn find_block_by_hash(
737        &self,
738        hash: B256,
739        source: BlockSource,
740    ) -> ProviderResult<Option<Self::Block>> {
741        self.provider()?.find_block_by_hash(hash, source)
742    }
743
744    fn block(&self, id: BlockHashOrNumber) -> ProviderResult<Option<Self::Block>> {
745        self.provider()?.block(id)
746    }
747
748    fn pending_block(&self) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
749        self.provider()?.pending_block()
750    }
751
752    fn pending_block_and_receipts(
753        &self,
754    ) -> ProviderResult<Option<(RecoveredBlock<Self::Block>, Vec<Self::Receipt>)>> {
755        self.provider()?.pending_block_and_receipts()
756    }
757
758    fn recovered_block(
759        &self,
760        id: BlockHashOrNumber,
761        transaction_kind: TransactionVariant,
762    ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
763        self.provider()?.recovered_block(id, transaction_kind)
764    }
765
766    fn sealed_block_with_senders(
767        &self,
768        id: BlockHashOrNumber,
769        transaction_kind: TransactionVariant,
770    ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
771        self.provider()?.sealed_block_with_senders(id, transaction_kind)
772    }
773
774    fn block_range(&self, range: RangeInclusive<BlockNumber>) -> ProviderResult<Vec<Self::Block>> {
775        self.provider()?.block_range(range)
776    }
777
778    fn block_with_senders_range(
779        &self,
780        range: RangeInclusive<BlockNumber>,
781    ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
782        self.provider()?.block_with_senders_range(range)
783    }
784
785    fn recovered_block_range(
786        &self,
787        range: RangeInclusive<BlockNumber>,
788    ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
789        self.provider()?.recovered_block_range(range)
790    }
791
792    fn block_by_transaction_id(&self, id: TxNumber) -> ProviderResult<Option<BlockNumber>> {
793        self.provider()?.block_by_transaction_id(id)
794    }
795}
796
797impl<N: ProviderNodeTypes> TransactionsProvider for ProviderFactory<N> {
798    type Transaction = TxTy<N>;
799
800    fn transaction_id(&self, tx_hash: TxHash) -> ProviderResult<Option<TxNumber>> {
801        self.provider()?.transaction_id(tx_hash)
802    }
803
804    fn transaction_by_id(&self, id: TxNumber) -> ProviderResult<Option<Self::Transaction>> {
805        self.caught_up_static_file_provider()?.transaction_by_id(id)
806    }
807
808    fn transaction_by_id_unhashed(
809        &self,
810        id: TxNumber,
811    ) -> ProviderResult<Option<Self::Transaction>> {
812        self.caught_up_static_file_provider()?.transaction_by_id_unhashed(id)
813    }
814
815    fn transaction_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Transaction>> {
816        self.provider()?.transaction_by_hash(hash)
817    }
818
819    fn transaction_by_hash_with_meta(
820        &self,
821        tx_hash: TxHash,
822    ) -> ProviderResult<Option<(Self::Transaction, TransactionMeta)>> {
823        self.provider()?.transaction_by_hash_with_meta(tx_hash)
824    }
825
826    fn transactions_by_block(
827        &self,
828        id: BlockHashOrNumber,
829    ) -> ProviderResult<Option<Vec<Self::Transaction>>> {
830        self.provider()?.transactions_by_block(id)
831    }
832
833    fn transactions_by_block_range(
834        &self,
835        range: impl RangeBounds<BlockNumber>,
836    ) -> ProviderResult<Vec<Vec<Self::Transaction>>> {
837        self.provider()?.transactions_by_block_range(range)
838    }
839
840    fn transactions_by_tx_range(
841        &self,
842        range: impl RangeBounds<TxNumber>,
843    ) -> ProviderResult<Vec<Self::Transaction>> {
844        self.caught_up_static_file_provider()?.transactions_by_tx_range(range)
845    }
846
847    fn senders_by_tx_range(
848        &self,
849        range: impl RangeBounds<TxNumber>,
850    ) -> ProviderResult<Vec<Address>> {
851        if EitherWriterDestination::senders(self).is_static_file() {
852            self.caught_up_static_file_provider()?.senders_by_tx_range(range)
853        } else {
854            self.provider()?.senders_by_tx_range(range)
855        }
856    }
857
858    fn transaction_sender(&self, id: TxNumber) -> ProviderResult<Option<Address>> {
859        if EitherWriterDestination::senders(self).is_static_file() {
860            self.caught_up_static_file_provider()?.transaction_sender(id)
861        } else {
862            self.provider()?.transaction_sender(id)
863        }
864    }
865}
866
867impl<N: ProviderNodeTypes> ReceiptProvider for ProviderFactory<N> {
868    type Receipt = ReceiptTy<N>;
869
870    fn receipt(&self, id: TxNumber) -> ProviderResult<Option<Self::Receipt>> {
871        self.caught_up_static_file_provider()?.get_with_static_file_or_database(
872            StaticFileSegment::Receipts,
873            id,
874            |static_file| static_file.receipt(id),
875            || self.provider()?.receipt(id),
876        )
877    }
878
879    fn receipt_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Receipt>> {
880        self.provider()?.receipt_by_hash(hash)
881    }
882
883    fn receipts_by_block(
884        &self,
885        block: BlockHashOrNumber,
886    ) -> ProviderResult<Option<Vec<Self::Receipt>>> {
887        self.provider()?.receipts_by_block(block)
888    }
889
890    fn receipts_by_tx_range(
891        &self,
892        range: impl RangeBounds<TxNumber>,
893    ) -> ProviderResult<Vec<Self::Receipt>> {
894        self.caught_up_static_file_provider()?.get_range_with_static_file_or_database(
895            StaticFileSegment::Receipts,
896            to_range(range),
897            |static_file, range, _| static_file.receipts_by_tx_range(range),
898            |range, _| self.provider()?.receipts_by_tx_range(range),
899            |_| true,
900        )
901    }
902
903    fn receipts_by_block_range(
904        &self,
905        block_range: RangeInclusive<BlockNumber>,
906    ) -> ProviderResult<Vec<Vec<Self::Receipt>>> {
907        self.provider()?.receipts_by_block_range(block_range)
908    }
909}
910
911impl<N: ProviderNodeTypes> BlockBodyIndicesProvider for ProviderFactory<N> {
912    fn block_body_indices(
913        &self,
914        number: BlockNumber,
915    ) -> ProviderResult<Option<StoredBlockBodyIndices>> {
916        self.provider()?.block_body_indices(number)
917    }
918
919    fn block_body_indices_range(
920        &self,
921        range: RangeInclusive<BlockNumber>,
922    ) -> ProviderResult<Vec<StoredBlockBodyIndices>> {
923        self.provider()?.block_body_indices_range(range)
924    }
925}
926
927impl<N: ProviderNodeTypes> StageCheckpointReader for ProviderFactory<N> {
928    fn get_stage_checkpoint(&self, id: StageId) -> ProviderResult<Option<StageCheckpoint>> {
929        self.provider()?.get_stage_checkpoint(id)
930    }
931
932    fn get_stage_checkpoint_progress(&self, id: StageId) -> ProviderResult<Option<Vec<u8>>> {
933        self.provider()?.get_stage_checkpoint_progress(id)
934    }
935    fn get_all_checkpoints(&self) -> ProviderResult<Vec<(String, StageCheckpoint)>> {
936        self.provider()?.get_all_checkpoints()
937    }
938}
939
940impl<N: NodeTypesWithDB> ChainSpecProvider for ProviderFactory<N> {
941    type ChainSpec = N::ChainSpec;
942
943    fn chain_spec(&self) -> Arc<N::ChainSpec> {
944        self.chain_spec.clone()
945    }
946}
947
948impl<N: ProviderNodeTypes> PruneCheckpointReader for ProviderFactory<N> {
949    fn get_prune_checkpoint(
950        &self,
951        segment: PruneSegment,
952    ) -> ProviderResult<Option<PruneCheckpoint>> {
953        self.provider()?.get_prune_checkpoint(segment)
954    }
955
956    fn get_prune_checkpoints(&self) -> ProviderResult<Vec<(PruneSegment, PruneCheckpoint)>> {
957        self.provider()?.get_prune_checkpoints()
958    }
959}
960
961impl<N: ProviderNodeTypes> HashedPostStateProvider for ProviderFactory<N> {
962    fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState {
963        HashedPostState::from_bundle_state::<KeccakKeyHasher>(bundle_state.state())
964    }
965}
966
967impl<N: ProviderNodeTypes> MetadataProvider for ProviderFactory<N> {
968    fn get_metadata(&self, key: &str) -> ProviderResult<Option<Vec<u8>>> {
969        self.provider()?.get_metadata(key)
970    }
971}
972
973impl<N> fmt::Debug for ProviderFactory<N>
974where
975    N: NodeTypesWithDB<DB: fmt::Debug, ChainSpec: fmt::Debug, Storage: fmt::Debug>,
976{
977    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
978        let Self {
979            db,
980            chain_spec,
981            static_file_provider,
982            prune_modes,
983            storage,
984            storage_settings,
985            rocksdb_provider,
986            changeset_cache,
987            bal_store,
988            runtime,
989            minimum_pruning_distance,
990            database_provider_metrics: _,
991            read_only_sync,
992        } = self;
993        f.debug_struct("ProviderFactory")
994            .field("db", &db)
995            .field("chain_spec", &chain_spec)
996            .field("static_file_provider", &static_file_provider)
997            .field("prune_modes", &prune_modes)
998            .field("storage", &storage)
999            .field("storage_settings", &*storage_settings.read())
1000            .field("rocksdb_provider", &rocksdb_provider)
1001            .field("changeset_cache", &changeset_cache)
1002            .field("bal_store", &bal_store)
1003            .field("runtime", &runtime)
1004            .field("minimum_pruning_distance", &minimum_pruning_distance)
1005            .field(
1006                "read_only_sync",
1007                &read_only_sync.as_ref().map(|s| s.last_synced_txnid.load(Ordering::Relaxed)),
1008            )
1009            .finish()
1010    }
1011}
1012
1013impl<N: NodeTypesWithDB> Clone for ProviderFactory<N> {
1014    fn clone(&self) -> Self {
1015        Self {
1016            db: self.db.clone(),
1017            chain_spec: self.chain_spec.clone(),
1018            static_file_provider: self.static_file_provider.clone(),
1019            prune_modes: self.prune_modes.clone(),
1020            storage: self.storage.clone(),
1021            storage_settings: self.storage_settings.clone(),
1022            rocksdb_provider: self.rocksdb_provider.clone(),
1023            changeset_cache: self.changeset_cache.clone(),
1024            bal_store: self.bal_store.clone(),
1025            runtime: self.runtime.clone(),
1026            minimum_pruning_distance: self.minimum_pruning_distance,
1027            database_provider_metrics: self.database_provider_metrics.clone(),
1028            read_only_sync: self.read_only_sync.clone(),
1029        }
1030    }
1031}
1032
1033#[cfg(test)]
1034mod tests {
1035    use super::*;
1036    use crate::{
1037        providers::{StaticFileProvider, StaticFileWriter},
1038        test_utils::{blocks::TEST_BLOCK, create_test_provider_factory, MockNodeTypesWithDB},
1039        BlockHashReader, BlockNumReader, BlockWriter, DBProvider, HeaderSyncGapProvider,
1040        TransactionsProvider,
1041    };
1042    use alloy_primitives::{TxNumber, B256};
1043    use assert_matches::assert_matches;
1044    use reth_chainspec::ChainSpecBuilder;
1045    use reth_db::{
1046        mdbx::DatabaseArguments,
1047        test_utils::{create_test_rocksdb_dir, create_test_static_files_dir, ERROR_TEMPDIR},
1048    };
1049    use reth_db_api::tables;
1050    use reth_primitives_traits::SignerRecoverable;
1051    use reth_prune_types::{PruneMode, PruneModes};
1052    use reth_storage_errors::provider::ProviderError;
1053    use reth_testing_utils::generators::{self, random_block, random_header, BlockParams};
1054    use std::{ops::RangeInclusive, sync::Arc};
1055
1056    #[test]
1057    fn common_history_provider() {
1058        let factory = create_test_provider_factory();
1059        let _ = factory.latest();
1060    }
1061
1062    #[test]
1063    fn default_chain_info() {
1064        let factory = create_test_provider_factory();
1065        let provider = factory.provider().unwrap();
1066
1067        let chain_info = provider.chain_info().expect("should be ok");
1068        assert_eq!(chain_info.best_number, 0);
1069        assert_eq!(chain_info.best_hash, B256::ZERO);
1070    }
1071
1072    #[test]
1073    fn provider_flow() {
1074        let factory = create_test_provider_factory();
1075        let provider = factory.provider().unwrap();
1076        provider.block_hash(0).unwrap();
1077        let provider_rw = factory.provider_rw().unwrap();
1078        provider_rw.block_hash(0).unwrap();
1079        provider.block_hash(0).unwrap();
1080    }
1081
1082    #[test]
1083    fn provider_factory_with_database_path() {
1084        let chain_spec = ChainSpecBuilder::mainnet().build();
1085        let (_static_dir, static_dir_path) = create_test_static_files_dir();
1086        let (_rocksdb_dir, rocksdb_path) = create_test_rocksdb_dir();
1087        let _db_tempdir = tempfile::TempDir::new().expect(ERROR_TEMPDIR);
1088        let factory = ProviderFactory::<MockNodeTypesWithDB<DatabaseEnv>>::new_with_database_path(
1089            _db_tempdir.path(),
1090            Arc::new(chain_spec),
1091            DatabaseArguments::new(Default::default()),
1092            StaticFileProvider::read_write(static_dir_path).unwrap(),
1093            RocksDBProvider::builder(&rocksdb_path).build().unwrap(),
1094            reth_tasks::Runtime::test(),
1095        )
1096        .unwrap();
1097        let provider = factory.provider().unwrap();
1098        provider.block_hash(0).unwrap();
1099        let provider_rw = factory.provider_rw().unwrap();
1100        provider_rw.block_hash(0).unwrap();
1101        provider.block_hash(0).unwrap();
1102    }
1103
1104    #[test]
1105    fn insert_block_with_prune_modes() {
1106        let block = TEST_BLOCK.clone();
1107
1108        {
1109            let factory = create_test_provider_factory();
1110            let provider = factory.provider_rw().unwrap();
1111            assert_matches!(provider.insert_block(&block.clone().try_recover().unwrap()), Ok(_));
1112            assert_matches!(
1113                provider.transaction_sender(0), Ok(Some(sender))
1114                if sender == block.body().transactions[0].recover_signer().unwrap()
1115            );
1116            assert_matches!(
1117                provider.transaction_id(*block.body().transactions[0].tx_hash()),
1118                Ok(Some(0))
1119            );
1120        }
1121
1122        {
1123            let prune_modes = PruneModes {
1124                sender_recovery: Some(PruneMode::Full),
1125                transaction_lookup: Some(PruneMode::Full),
1126                ..PruneModes::default()
1127            };
1128            // Keep factory alive until provider is dropped to prevent TempDatabase cleanup
1129            let factory = create_test_provider_factory().with_prune_modes(prune_modes);
1130            let provider = factory.provider_rw().unwrap();
1131            assert_matches!(provider.insert_block(&block.clone().try_recover().unwrap()), Ok(_));
1132            assert_matches!(provider.transaction_sender(0), Ok(None));
1133            assert_matches!(
1134                provider.transaction_id(*block.body().transactions[0].tx_hash()),
1135                Ok(None)
1136            );
1137        }
1138    }
1139
1140    #[test]
1141    fn take_block_transaction_range_recover_senders() {
1142        let mut rng = generators::rng();
1143        let block =
1144            random_block(&mut rng, 0, BlockParams { tx_count: Some(3), ..Default::default() });
1145
1146        let tx_ranges: Vec<RangeInclusive<TxNumber>> = vec![0..=0, 1..=1, 2..=2, 0..=1, 1..=2];
1147        for range in tx_ranges {
1148            let factory = create_test_provider_factory();
1149            let provider = factory.provider_rw().unwrap();
1150
1151            assert_matches!(provider.insert_block(&block.clone().try_recover().unwrap()), Ok(_));
1152
1153            let senders = provider.take::<tables::TransactionSenders>(range.clone()).unwrap();
1154            assert_eq!(
1155                senders,
1156                range
1157                    .clone()
1158                    .map(|tx_number| (
1159                        tx_number,
1160                        block.body().transactions[tx_number as usize].recover_signer().unwrap()
1161                    ))
1162                    .collect::<Vec<_>>()
1163            );
1164
1165            let db_senders = provider.senders_by_tx_range(range);
1166            assert!(matches!(db_senders, Ok(ref v) if v.is_empty()));
1167        }
1168    }
1169
1170    #[test]
1171    fn header_sync_gap_lookup() {
1172        let factory = create_test_provider_factory();
1173        let provider = factory.provider_rw().unwrap();
1174
1175        let mut rng = generators::rng();
1176
1177        // Genesis
1178        let checkpoint = 0;
1179        let head = random_header(&mut rng, 0, None);
1180
1181        // Empty database
1182        assert_matches!(
1183            provider.local_tip_header(checkpoint),
1184            Err(ProviderError::HeaderNotFound(block_number))
1185                if block_number.as_number().unwrap() == checkpoint
1186        );
1187
1188        // Checkpoint and no gap
1189        let static_file_provider = provider.static_file_provider();
1190        let mut static_file_writer =
1191            static_file_provider.latest_writer(StaticFileSegment::Headers).unwrap();
1192        static_file_writer.append_header(head.header(), &head.hash()).unwrap();
1193        static_file_writer.commit().unwrap();
1194        drop(static_file_writer);
1195
1196        let local_head = provider.local_tip_header(checkpoint).unwrap();
1197
1198        assert_eq!(local_head, head);
1199    }
1200}