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