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