reth_provider/providers/database/
mod.rs

1use crate::{
2    providers::{state::latest::LatestStateProvider, StaticFileProvider},
3    to_range,
4    traits::{BlockSource, ReceiptProvider},
5    BlockHashReader, BlockNumReader, BlockReader, ChainSpecProvider, DatabaseProviderFactory,
6    HashedPostStateProvider, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, ProviderError,
7    PruneCheckpointReader, StageCheckpointReader, StateProviderBox, StaticFileProviderFactory,
8    TransactionVariant, TransactionsProvider, WithdrawalsProvider,
9};
10use alloy_consensus::transaction::TransactionMeta;
11use alloy_eips::{eip4895::Withdrawals, BlockHashOrNumber};
12use alloy_primitives::{Address, BlockHash, BlockNumber, TxHash, TxNumber, B256, U256};
13use core::fmt;
14use reth_chainspec::ChainInfo;
15use reth_db::{init_db, mdbx::DatabaseArguments, DatabaseEnv};
16use reth_db_api::{database::Database, models::StoredBlockBodyIndices};
17use reth_errors::{RethError, RethResult};
18use reth_node_types::{
19    BlockTy, HeaderTy, NodeTypes, NodeTypesWithDB, NodeTypesWithDBAdapter, ReceiptTy, TxTy,
20};
21use reth_primitives_traits::{RecoveredBlock, SealedBlock, SealedHeader};
22use reth_prune_types::{PruneCheckpoint, PruneModes, PruneSegment};
23use reth_stages_types::{StageCheckpoint, StageId};
24use reth_static_file_types::StaticFileSegment;
25use reth_storage_api::{
26    BlockBodyIndicesProvider, NodePrimitivesProvider, OmmersProvider, StateCommitmentProvider,
27    TryIntoHistoricalStateProvider,
28};
29use reth_storage_errors::provider::ProviderResult;
30use reth_trie::HashedPostState;
31use reth_trie_db::StateCommitment;
32use revm_database::BundleState;
33use std::{
34    ops::{RangeBounds, RangeInclusive},
35    path::Path,
36    sync::Arc,
37};
38use tokio::sync::watch;
39use tracing::trace;
40
41mod provider;
42pub use provider::{DatabaseProvider, DatabaseProviderRO, DatabaseProviderRW};
43
44use super::ProviderNodeTypes;
45
46mod builder;
47pub use builder::{ProviderFactoryBuilder, ReadOnlyConfig};
48
49mod metrics;
50
51mod chain;
52pub use chain::*;
53
54/// A common provider that fetches data from a database or static file.
55///
56/// This provider implements most provider or provider factory traits.
57pub struct ProviderFactory<N: NodeTypesWithDB> {
58    /// Database instance
59    db: N::DB,
60    /// Chain spec
61    chain_spec: Arc<N::ChainSpec>,
62    /// Static File Provider
63    static_file_provider: StaticFileProvider<N::Primitives>,
64    /// Optional pruning configuration
65    prune_modes: PruneModes,
66    /// The node storage handler.
67    storage: Arc<N::Storage>,
68}
69
70impl<N: NodeTypes> ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>> {
71    /// Instantiates the builder for this type
72    pub fn builder() -> ProviderFactoryBuilder<N> {
73        ProviderFactoryBuilder::default()
74    }
75}
76
77impl<N: NodeTypesWithDB> ProviderFactory<N> {
78    /// Create new database provider factory.
79    pub fn new(
80        db: N::DB,
81        chain_spec: Arc<N::ChainSpec>,
82        static_file_provider: StaticFileProvider<N::Primitives>,
83    ) -> Self {
84        Self {
85            db,
86            chain_spec,
87            static_file_provider,
88            prune_modes: PruneModes::none(),
89            storage: Default::default(),
90        }
91    }
92
93    /// Enables metrics on the static file provider.
94    pub fn with_static_files_metrics(mut self) -> Self {
95        self.static_file_provider = self.static_file_provider.with_metrics();
96        self
97    }
98
99    /// Sets the pruning configuration for an existing [`ProviderFactory`].
100    pub fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self {
101        self.prune_modes = prune_modes;
102        self
103    }
104
105    /// Returns reference to the underlying database.
106    pub const fn db_ref(&self) -> &N::DB {
107        &self.db
108    }
109
110    #[cfg(any(test, feature = "test-utils"))]
111    /// Consumes Self and returns DB
112    pub fn into_db(self) -> N::DB {
113        self.db
114    }
115}
116
117impl<N: NodeTypesWithDB<DB = Arc<DatabaseEnv>>> ProviderFactory<N> {
118    /// Create new database provider by passing a path. [`ProviderFactory`] will own the database
119    /// instance.
120    pub fn new_with_database_path<P: AsRef<Path>>(
121        path: P,
122        chain_spec: Arc<N::ChainSpec>,
123        args: DatabaseArguments,
124        static_file_provider: StaticFileProvider<N::Primitives>,
125    ) -> RethResult<Self> {
126        Ok(Self {
127            db: Arc::new(init_db(path, args).map_err(RethError::msg)?),
128            chain_spec,
129            static_file_provider,
130            prune_modes: PruneModes::none(),
131            storage: Default::default(),
132        })
133    }
134}
135
136impl<N: ProviderNodeTypes> ProviderFactory<N> {
137    /// Returns a provider with a created `DbTx` inside, which allows fetching data from the
138    /// database using different types of providers. Example: [`HeaderProvider`]
139    /// [`BlockHashReader`]. This may fail if the inner read database transaction fails to open.
140    ///
141    /// This sets the [`PruneModes`] to [`None`], because they should only be relevant for writing
142    /// data.
143    #[track_caller]
144    pub fn provider(&self) -> ProviderResult<DatabaseProviderRO<N::DB, N>> {
145        Ok(DatabaseProvider::new(
146            self.db.tx()?,
147            self.chain_spec.clone(),
148            self.static_file_provider.clone(),
149            self.prune_modes.clone(),
150            self.storage.clone(),
151        ))
152    }
153
154    /// Returns a provider with a created `DbTxMut` inside, which allows fetching and updating
155    /// data from the database using different types of providers. Example: [`HeaderProvider`]
156    /// [`BlockHashReader`].  This may fail if the inner read/write database transaction fails to
157    /// open.
158    #[track_caller]
159    pub fn provider_rw(&self) -> ProviderResult<DatabaseProviderRW<N::DB, N>> {
160        Ok(DatabaseProviderRW(DatabaseProvider::new_rw(
161            self.db.tx_mut()?,
162            self.chain_spec.clone(),
163            self.static_file_provider.clone(),
164            self.prune_modes.clone(),
165            self.storage.clone(),
166        )))
167    }
168
169    /// State provider for latest block
170    #[track_caller]
171    pub fn latest(&self) -> ProviderResult<StateProviderBox> {
172        trace!(target: "providers::db", "Returning latest state provider");
173        Ok(Box::new(LatestStateProvider::new(self.database_provider_ro()?)))
174    }
175
176    /// Storage provider for state at that given block
177    pub fn history_by_block_number(
178        &self,
179        block_number: BlockNumber,
180    ) -> ProviderResult<StateProviderBox> {
181        let state_provider = self.provider()?.try_into_history_at_block(block_number)?;
182        trace!(target: "providers::db", ?block_number, "Returning historical state provider for block number");
183        Ok(state_provider)
184    }
185
186    /// Storage provider for state at that given block hash
187    pub fn history_by_block_hash(&self, block_hash: BlockHash) -> ProviderResult<StateProviderBox> {
188        let provider = self.provider()?;
189
190        let block_number = provider
191            .block_number(block_hash)?
192            .ok_or(ProviderError::BlockHashNotFound(block_hash))?;
193
194        let state_provider = provider.try_into_history_at_block(block_number)?;
195        trace!(target: "providers::db", ?block_number, %block_hash, "Returning historical state provider for block hash");
196        Ok(state_provider)
197    }
198}
199
200impl<N: NodeTypesWithDB> NodePrimitivesProvider for ProviderFactory<N> {
201    type Primitives = N::Primitives;
202}
203
204impl<N: ProviderNodeTypes> DatabaseProviderFactory for ProviderFactory<N> {
205    type DB = N::DB;
206    type Provider = DatabaseProvider<<N::DB as Database>::TX, N>;
207    type ProviderRW = DatabaseProvider<<N::DB as Database>::TXMut, N>;
208
209    fn database_provider_ro(&self) -> ProviderResult<Self::Provider> {
210        self.provider()
211    }
212
213    fn database_provider_rw(&self) -> ProviderResult<Self::ProviderRW> {
214        self.provider_rw().map(|provider| provider.0)
215    }
216}
217
218impl<N: NodeTypesWithDB> StateCommitmentProvider for ProviderFactory<N> {
219    type StateCommitment = N::StateCommitment;
220}
221
222impl<N: NodeTypesWithDB> StaticFileProviderFactory for ProviderFactory<N> {
223    /// Returns static file provider
224    fn static_file_provider(&self) -> StaticFileProvider<Self::Primitives> {
225        self.static_file_provider.clone()
226    }
227}
228
229impl<N: ProviderNodeTypes> HeaderSyncGapProvider for ProviderFactory<N> {
230    type Header = HeaderTy<N>;
231    fn sync_gap(
232        &self,
233        tip: watch::Receiver<B256>,
234        highest_uninterrupted_block: BlockNumber,
235    ) -> ProviderResult<HeaderSyncGap<Self::Header>> {
236        self.provider()?.sync_gap(tip, highest_uninterrupted_block)
237    }
238}
239
240impl<N: ProviderNodeTypes> HeaderProvider for ProviderFactory<N> {
241    type Header = HeaderTy<N>;
242
243    fn header(&self, block_hash: &BlockHash) -> ProviderResult<Option<Self::Header>> {
244        self.provider()?.header(block_hash)
245    }
246
247    fn header_by_number(&self, num: BlockNumber) -> ProviderResult<Option<Self::Header>> {
248        self.static_file_provider.get_with_static_file_or_database(
249            StaticFileSegment::Headers,
250            num,
251            |static_file| static_file.header_by_number(num),
252            || self.provider()?.header_by_number(num),
253        )
254    }
255
256    fn header_td(&self, hash: &BlockHash) -> ProviderResult<Option<U256>> {
257        self.provider()?.header_td(hash)
258    }
259
260    fn header_td_by_number(&self, number: BlockNumber) -> ProviderResult<Option<U256>> {
261        self.provider()?.header_td_by_number(number)
262    }
263
264    fn headers_range(
265        &self,
266        range: impl RangeBounds<BlockNumber>,
267    ) -> ProviderResult<Vec<Self::Header>> {
268        self.static_file_provider.get_range_with_static_file_or_database(
269            StaticFileSegment::Headers,
270            to_range(range),
271            |static_file, range, _| static_file.headers_range(range),
272            |range, _| self.provider()?.headers_range(range),
273            |_| true,
274        )
275    }
276
277    fn sealed_header(
278        &self,
279        number: BlockNumber,
280    ) -> ProviderResult<Option<SealedHeader<Self::Header>>> {
281        self.static_file_provider.get_with_static_file_or_database(
282            StaticFileSegment::Headers,
283            number,
284            |static_file| static_file.sealed_header(number),
285            || self.provider()?.sealed_header(number),
286        )
287    }
288
289    fn sealed_headers_range(
290        &self,
291        range: impl RangeBounds<BlockNumber>,
292    ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
293        self.sealed_headers_while(range, |_| true)
294    }
295
296    fn sealed_headers_while(
297        &self,
298        range: impl RangeBounds<BlockNumber>,
299        predicate: impl FnMut(&SealedHeader<Self::Header>) -> bool,
300    ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
301        self.static_file_provider.get_range_with_static_file_or_database(
302            StaticFileSegment::Headers,
303            to_range(range),
304            |static_file, range, predicate| static_file.sealed_headers_while(range, predicate),
305            |range, predicate| self.provider()?.sealed_headers_while(range, predicate),
306            predicate,
307        )
308    }
309}
310
311impl<N: ProviderNodeTypes> BlockHashReader for ProviderFactory<N> {
312    fn block_hash(&self, number: u64) -> ProviderResult<Option<B256>> {
313        self.static_file_provider.get_with_static_file_or_database(
314            StaticFileSegment::Headers,
315            number,
316            |static_file| static_file.block_hash(number),
317            || self.provider()?.block_hash(number),
318        )
319    }
320
321    fn canonical_hashes_range(
322        &self,
323        start: BlockNumber,
324        end: BlockNumber,
325    ) -> ProviderResult<Vec<B256>> {
326        self.static_file_provider.get_range_with_static_file_or_database(
327            StaticFileSegment::Headers,
328            start..end,
329            |static_file, range, _| static_file.canonical_hashes_range(range.start, range.end),
330            |range, _| self.provider()?.canonical_hashes_range(range.start, range.end),
331            |_| true,
332        )
333    }
334}
335
336impl<N: ProviderNodeTypes> BlockNumReader for ProviderFactory<N> {
337    fn chain_info(&self) -> ProviderResult<ChainInfo> {
338        self.provider()?.chain_info()
339    }
340
341    fn best_block_number(&self) -> ProviderResult<BlockNumber> {
342        self.provider()?.best_block_number()
343    }
344
345    fn last_block_number(&self) -> ProviderResult<BlockNumber> {
346        self.provider()?.last_block_number()
347    }
348
349    fn block_number(&self, hash: B256) -> ProviderResult<Option<BlockNumber>> {
350        self.provider()?.block_number(hash)
351    }
352}
353
354impl<N: ProviderNodeTypes> BlockReader for ProviderFactory<N> {
355    type Block = BlockTy<N>;
356
357    fn find_block_by_hash(
358        &self,
359        hash: B256,
360        source: BlockSource,
361    ) -> ProviderResult<Option<Self::Block>> {
362        self.provider()?.find_block_by_hash(hash, source)
363    }
364
365    fn block(&self, id: BlockHashOrNumber) -> ProviderResult<Option<Self::Block>> {
366        self.provider()?.block(id)
367    }
368
369    fn pending_block(&self) -> ProviderResult<Option<SealedBlock<Self::Block>>> {
370        self.provider()?.pending_block()
371    }
372
373    fn pending_block_with_senders(&self) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
374        self.provider()?.pending_block_with_senders()
375    }
376
377    fn pending_block_and_receipts(
378        &self,
379    ) -> ProviderResult<Option<(SealedBlock<Self::Block>, Vec<Self::Receipt>)>> {
380        self.provider()?.pending_block_and_receipts()
381    }
382
383    fn recovered_block(
384        &self,
385        id: BlockHashOrNumber,
386        transaction_kind: TransactionVariant,
387    ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
388        self.provider()?.recovered_block(id, transaction_kind)
389    }
390
391    fn sealed_block_with_senders(
392        &self,
393        id: BlockHashOrNumber,
394        transaction_kind: TransactionVariant,
395    ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
396        self.provider()?.sealed_block_with_senders(id, transaction_kind)
397    }
398
399    fn block_range(&self, range: RangeInclusive<BlockNumber>) -> ProviderResult<Vec<Self::Block>> {
400        self.provider()?.block_range(range)
401    }
402
403    fn block_with_senders_range(
404        &self,
405        range: RangeInclusive<BlockNumber>,
406    ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
407        self.provider()?.block_with_senders_range(range)
408    }
409
410    fn recovered_block_range(
411        &self,
412        range: RangeInclusive<BlockNumber>,
413    ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
414        self.provider()?.recovered_block_range(range)
415    }
416}
417
418impl<N: ProviderNodeTypes> TransactionsProvider for ProviderFactory<N> {
419    type Transaction = TxTy<N>;
420
421    fn transaction_id(&self, tx_hash: TxHash) -> ProviderResult<Option<TxNumber>> {
422        self.provider()?.transaction_id(tx_hash)
423    }
424
425    fn transaction_by_id(&self, id: TxNumber) -> ProviderResult<Option<Self::Transaction>> {
426        self.static_file_provider.get_with_static_file_or_database(
427            StaticFileSegment::Transactions,
428            id,
429            |static_file| static_file.transaction_by_id(id),
430            || self.provider()?.transaction_by_id(id),
431        )
432    }
433
434    fn transaction_by_id_unhashed(
435        &self,
436        id: TxNumber,
437    ) -> ProviderResult<Option<Self::Transaction>> {
438        self.static_file_provider.get_with_static_file_or_database(
439            StaticFileSegment::Transactions,
440            id,
441            |static_file| static_file.transaction_by_id_unhashed(id),
442            || self.provider()?.transaction_by_id_unhashed(id),
443        )
444    }
445
446    fn transaction_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Transaction>> {
447        self.provider()?.transaction_by_hash(hash)
448    }
449
450    fn transaction_by_hash_with_meta(
451        &self,
452        tx_hash: TxHash,
453    ) -> ProviderResult<Option<(Self::Transaction, TransactionMeta)>> {
454        self.provider()?.transaction_by_hash_with_meta(tx_hash)
455    }
456
457    fn transaction_block(&self, id: TxNumber) -> ProviderResult<Option<BlockNumber>> {
458        self.provider()?.transaction_block(id)
459    }
460
461    fn transactions_by_block(
462        &self,
463        id: BlockHashOrNumber,
464    ) -> ProviderResult<Option<Vec<Self::Transaction>>> {
465        self.provider()?.transactions_by_block(id)
466    }
467
468    fn transactions_by_block_range(
469        &self,
470        range: impl RangeBounds<BlockNumber>,
471    ) -> ProviderResult<Vec<Vec<Self::Transaction>>> {
472        self.provider()?.transactions_by_block_range(range)
473    }
474
475    fn transactions_by_tx_range(
476        &self,
477        range: impl RangeBounds<TxNumber>,
478    ) -> ProviderResult<Vec<Self::Transaction>> {
479        self.provider()?.transactions_by_tx_range(range)
480    }
481
482    fn senders_by_tx_range(
483        &self,
484        range: impl RangeBounds<TxNumber>,
485    ) -> ProviderResult<Vec<Address>> {
486        self.provider()?.senders_by_tx_range(range)
487    }
488
489    fn transaction_sender(&self, id: TxNumber) -> ProviderResult<Option<Address>> {
490        self.provider()?.transaction_sender(id)
491    }
492}
493
494impl<N: ProviderNodeTypes> ReceiptProvider for ProviderFactory<N> {
495    type Receipt = ReceiptTy<N>;
496    fn receipt(&self, id: TxNumber) -> ProviderResult<Option<Self::Receipt>> {
497        self.static_file_provider.get_with_static_file_or_database(
498            StaticFileSegment::Receipts,
499            id,
500            |static_file| static_file.receipt(id),
501            || self.provider()?.receipt(id),
502        )
503    }
504
505    fn receipt_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Receipt>> {
506        self.provider()?.receipt_by_hash(hash)
507    }
508
509    fn receipts_by_block(
510        &self,
511        block: BlockHashOrNumber,
512    ) -> ProviderResult<Option<Vec<Self::Receipt>>> {
513        self.provider()?.receipts_by_block(block)
514    }
515
516    fn receipts_by_tx_range(
517        &self,
518        range: impl RangeBounds<TxNumber>,
519    ) -> ProviderResult<Vec<Self::Receipt>> {
520        self.static_file_provider.get_range_with_static_file_or_database(
521            StaticFileSegment::Receipts,
522            to_range(range),
523            |static_file, range, _| static_file.receipts_by_tx_range(range),
524            |range, _| self.provider()?.receipts_by_tx_range(range),
525            |_| true,
526        )
527    }
528}
529
530impl<N: ProviderNodeTypes> WithdrawalsProvider for ProviderFactory<N> {
531    fn withdrawals_by_block(
532        &self,
533        id: BlockHashOrNumber,
534        timestamp: u64,
535    ) -> ProviderResult<Option<Withdrawals>> {
536        self.provider()?.withdrawals_by_block(id, timestamp)
537    }
538}
539
540impl<N: ProviderNodeTypes> OmmersProvider for ProviderFactory<N> {
541    fn ommers(&self, id: BlockHashOrNumber) -> ProviderResult<Option<Vec<Self::Header>>> {
542        self.provider()?.ommers(id)
543    }
544}
545
546impl<N: ProviderNodeTypes> BlockBodyIndicesProvider for ProviderFactory<N> {
547    fn block_body_indices(
548        &self,
549        number: BlockNumber,
550    ) -> ProviderResult<Option<StoredBlockBodyIndices>> {
551        self.static_file_provider.get_with_static_file_or_database(
552            StaticFileSegment::BlockMeta,
553            number,
554            |static_file| static_file.block_body_indices(number),
555            || self.provider()?.block_body_indices(number),
556        )
557    }
558
559    fn block_body_indices_range(
560        &self,
561        range: RangeInclusive<BlockNumber>,
562    ) -> ProviderResult<Vec<StoredBlockBodyIndices>> {
563        self.static_file_provider.get_range_with_static_file_or_database(
564            StaticFileSegment::BlockMeta,
565            *range.start()..*range.end() + 1,
566            |static_file, range, _| {
567                static_file.block_body_indices_range(range.start..=range.end.saturating_sub(1))
568            },
569            |range, _| {
570                self.provider()?.block_body_indices_range(range.start..=range.end.saturating_sub(1))
571            },
572            |_| true,
573        )
574    }
575}
576
577impl<N: ProviderNodeTypes> StageCheckpointReader for ProviderFactory<N> {
578    fn get_stage_checkpoint(&self, id: StageId) -> ProviderResult<Option<StageCheckpoint>> {
579        self.provider()?.get_stage_checkpoint(id)
580    }
581
582    fn get_stage_checkpoint_progress(&self, id: StageId) -> ProviderResult<Option<Vec<u8>>> {
583        self.provider()?.get_stage_checkpoint_progress(id)
584    }
585    fn get_all_checkpoints(&self) -> ProviderResult<Vec<(String, StageCheckpoint)>> {
586        self.provider()?.get_all_checkpoints()
587    }
588}
589
590impl<N: NodeTypesWithDB> ChainSpecProvider for ProviderFactory<N> {
591    type ChainSpec = N::ChainSpec;
592
593    fn chain_spec(&self) -> Arc<N::ChainSpec> {
594        self.chain_spec.clone()
595    }
596}
597
598impl<N: ProviderNodeTypes> PruneCheckpointReader for ProviderFactory<N> {
599    fn get_prune_checkpoint(
600        &self,
601        segment: PruneSegment,
602    ) -> ProviderResult<Option<PruneCheckpoint>> {
603        self.provider()?.get_prune_checkpoint(segment)
604    }
605
606    fn get_prune_checkpoints(&self) -> ProviderResult<Vec<(PruneSegment, PruneCheckpoint)>> {
607        self.provider()?.get_prune_checkpoints()
608    }
609}
610
611impl<N: ProviderNodeTypes> HashedPostStateProvider for ProviderFactory<N> {
612    fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState {
613        HashedPostState::from_bundle_state::<<N::StateCommitment as StateCommitment>::KeyHasher>(
614            bundle_state.state(),
615        )
616    }
617}
618
619impl<N> fmt::Debug for ProviderFactory<N>
620where
621    N: NodeTypesWithDB<DB: fmt::Debug, ChainSpec: fmt::Debug, Storage: fmt::Debug>,
622{
623    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
624        let Self { db, chain_spec, static_file_provider, prune_modes, storage } = self;
625        f.debug_struct("ProviderFactory")
626            .field("db", &db)
627            .field("chain_spec", &chain_spec)
628            .field("static_file_provider", &static_file_provider)
629            .field("prune_modes", &prune_modes)
630            .field("storage", &storage)
631            .finish()
632    }
633}
634
635impl<N: NodeTypesWithDB> Clone for ProviderFactory<N> {
636    fn clone(&self) -> Self {
637        Self {
638            db: self.db.clone(),
639            chain_spec: self.chain_spec.clone(),
640            static_file_provider: self.static_file_provider.clone(),
641            prune_modes: self.prune_modes.clone(),
642            storage: self.storage.clone(),
643        }
644    }
645}
646
647#[cfg(test)]
648mod tests {
649    use super::*;
650    use crate::{
651        providers::{StaticFileProvider, StaticFileWriter},
652        test_utils::{blocks::TEST_BLOCK, create_test_provider_factory, MockNodeTypesWithDB},
653        BlockHashReader, BlockNumReader, BlockWriter, DBProvider, HeaderSyncGapProvider,
654        StorageLocation, TransactionsProvider,
655    };
656    use alloy_primitives::{TxNumber, B256, U256};
657    use assert_matches::assert_matches;
658    use rand::Rng;
659    use reth_chainspec::ChainSpecBuilder;
660    use reth_db::{
661        mdbx::DatabaseArguments,
662        test_utils::{create_test_static_files_dir, ERROR_TEMPDIR},
663    };
664    use reth_db_api::tables;
665    use reth_primitives_traits::SignedTransaction;
666    use reth_prune_types::{PruneMode, PruneModes};
667    use reth_storage_errors::provider::ProviderError;
668    use reth_testing_utils::generators::{self, random_block, random_header, BlockParams};
669    use std::{ops::RangeInclusive, sync::Arc};
670    use tokio::sync::watch;
671
672    #[test]
673    fn common_history_provider() {
674        let factory = create_test_provider_factory();
675        let _ = factory.latest();
676    }
677
678    #[test]
679    fn default_chain_info() {
680        let factory = create_test_provider_factory();
681        let provider = factory.provider().unwrap();
682
683        let chain_info = provider.chain_info().expect("should be ok");
684        assert_eq!(chain_info.best_number, 0);
685        assert_eq!(chain_info.best_hash, B256::ZERO);
686    }
687
688    #[test]
689    fn provider_flow() {
690        let factory = create_test_provider_factory();
691        let provider = factory.provider().unwrap();
692        provider.block_hash(0).unwrap();
693        let provider_rw = factory.provider_rw().unwrap();
694        provider_rw.block_hash(0).unwrap();
695        provider.block_hash(0).unwrap();
696    }
697
698    #[test]
699    fn provider_factory_with_database_path() {
700        let chain_spec = ChainSpecBuilder::mainnet().build();
701        let (_static_dir, static_dir_path) = create_test_static_files_dir();
702        let factory = ProviderFactory::<MockNodeTypesWithDB<DatabaseEnv>>::new_with_database_path(
703            tempfile::TempDir::new().expect(ERROR_TEMPDIR).into_path(),
704            Arc::new(chain_spec),
705            DatabaseArguments::new(Default::default()),
706            StaticFileProvider::read_write(static_dir_path).unwrap(),
707        )
708        .unwrap();
709
710        let provider = factory.provider().unwrap();
711        provider.block_hash(0).unwrap();
712        let provider_rw = factory.provider_rw().unwrap();
713        provider_rw.block_hash(0).unwrap();
714        provider.block_hash(0).unwrap();
715    }
716
717    #[test]
718    fn insert_block_with_prune_modes() {
719        let factory = create_test_provider_factory();
720
721        let block = TEST_BLOCK.clone();
722        {
723            let provider = factory.provider_rw().unwrap();
724            assert_matches!(
725                provider
726                    .insert_block(block.clone().try_recover().unwrap(), StorageLocation::Database),
727                Ok(_)
728            );
729            assert_matches!(
730                provider.transaction_sender(0), Ok(Some(sender))
731                if sender == block.body().transactions[0].recover_signer().unwrap()
732            );
733            assert_matches!(
734                provider.transaction_id(*block.body().transactions[0].tx_hash()),
735                Ok(Some(0))
736            );
737        }
738
739        {
740            let prune_modes = PruneModes {
741                sender_recovery: Some(PruneMode::Full),
742                transaction_lookup: Some(PruneMode::Full),
743                ..PruneModes::none()
744            };
745            let provider = factory.with_prune_modes(prune_modes).provider_rw().unwrap();
746            assert_matches!(
747                provider
748                    .insert_block(block.clone().try_recover().unwrap(), StorageLocation::Database),
749                Ok(_)
750            );
751            assert_matches!(provider.transaction_sender(0), Ok(None));
752            assert_matches!(
753                provider.transaction_id(*block.body().transactions[0].tx_hash()),
754                Ok(None)
755            );
756        }
757    }
758
759    #[test]
760    fn take_block_transaction_range_recover_senders() {
761        let factory = create_test_provider_factory();
762
763        let mut rng = generators::rng();
764        let block =
765            random_block(&mut rng, 0, BlockParams { tx_count: Some(3), ..Default::default() });
766
767        let tx_ranges: Vec<RangeInclusive<TxNumber>> = vec![0..=0, 1..=1, 2..=2, 0..=1, 1..=2];
768        for range in tx_ranges {
769            let provider = factory.provider_rw().unwrap();
770
771            assert_matches!(
772                provider
773                    .insert_block(block.clone().try_recover().unwrap(), StorageLocation::Database),
774                Ok(_)
775            );
776
777            let senders = provider.take::<tables::TransactionSenders>(range.clone());
778            assert_eq!(
779                senders,
780                Ok(range
781                    .clone()
782                    .map(|tx_number| (
783                        tx_number,
784                        block.body().transactions[tx_number as usize].recover_signer().unwrap()
785                    ))
786                    .collect())
787            );
788
789            let db_senders = provider.senders_by_tx_range(range);
790            assert!(matches!(db_senders, Ok(ref v) if v.is_empty()));
791        }
792    }
793
794    #[test]
795    fn header_sync_gap_lookup() {
796        let factory = create_test_provider_factory();
797        let provider = factory.provider_rw().unwrap();
798
799        let mut rng = generators::rng();
800        let consensus_tip = rng.random();
801        let (_tip_tx, tip_rx) = watch::channel(consensus_tip);
802
803        // Genesis
804        let checkpoint = 0;
805        let head = random_header(&mut rng, 0, None);
806
807        // Empty database
808        assert_matches!(
809            provider.sync_gap(tip_rx.clone(), checkpoint),
810            Err(ProviderError::HeaderNotFound(block_number))
811                if block_number.as_number().unwrap() == checkpoint
812        );
813
814        // Checkpoint and no gap
815        let static_file_provider = provider.static_file_provider();
816        let mut static_file_writer =
817            static_file_provider.latest_writer(StaticFileSegment::Headers).unwrap();
818        static_file_writer.append_header(head.header(), U256::ZERO, &head.hash()).unwrap();
819        static_file_writer.commit().unwrap();
820        drop(static_file_writer);
821
822        let gap = provider.sync_gap(tip_rx, checkpoint).unwrap();
823        assert_eq!(gap.local_head, head);
824        assert_eq!(gap.target.tip(), consensus_tip.into());
825    }
826}