reth_provider/providers/database/
builder.rs

1//! Helper builder entrypoint to instantiate a [`ProviderFactory`].
2//!
3//! This also includes general purpose staging types that provide builder style functions that lead
4//! up to the intended build target.
5
6use crate::{
7    providers::{NodeTypesForProvider, RocksDBProvider, StaticFileProvider},
8    ProviderFactory,
9};
10use reth_db::{
11    mdbx::{DatabaseArguments, MaxReadTransactionDuration},
12    open_db_read_only, DatabaseEnv,
13};
14use reth_db_api::{database_metrics::DatabaseMetrics, Database};
15use reth_node_types::{NodeTypes, NodeTypesWithDBAdapter};
16use reth_storage_errors::provider::ProviderResult;
17use std::{
18    marker::PhantomData,
19    path::{Path, PathBuf},
20    sync::Arc,
21};
22
23/// Helper type to create a [`ProviderFactory`].
24///
25/// This type is the entry point for a stage based builder.
26///
27/// The intended staging is:
28///  1. Configure the database: [`ProviderFactoryBuilder::db`]
29///  2. Configure the chainspec: `chainspec`
30///  3. Configure the [`StaticFileProvider`]: `static_file`
31#[derive(Debug)]
32pub struct ProviderFactoryBuilder<N> {
33    _types: PhantomData<N>,
34}
35
36impl<N> ProviderFactoryBuilder<N> {
37    /// Maps the [`NodeTypes`] of this builder.
38    pub fn types<T>(self) -> ProviderFactoryBuilder<T> {
39        ProviderFactoryBuilder::default()
40    }
41
42    /// Configures the database.
43    pub fn db<DB>(self, db: DB) -> TypesAnd1<N, DB> {
44        TypesAnd1::new(db)
45    }
46
47    /// Opens the database with the given chainspec and [`ReadOnlyConfig`].
48    ///
49    /// # Open a monitored instance
50    ///
51    /// This is recommended when the new read-only instance is used with an active node.
52    ///
53    /// ```no_run
54    /// use reth_chainspec::MAINNET;
55    /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder};
56    ///
57    /// fn demo<N: NodeTypesForProvider<ChainSpec = reth_chainspec::ChainSpec>>() {
58    ///     let provider_factory = ProviderFactoryBuilder::<N>::default()
59    ///         .open_read_only(MAINNET.clone(), "datadir")
60    ///         .unwrap();
61    /// }
62    /// ```
63    ///
64    /// # Open an unmonitored instance
65    ///
66    /// This is recommended when no changes to the static files are expected (e.g. no active node)
67    ///
68    /// ```no_run
69    /// use reth_chainspec::MAINNET;
70    /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder, ReadOnlyConfig};
71    ///
72    /// fn demo<N: NodeTypesForProvider<ChainSpec = reth_chainspec::ChainSpec>>() {
73    ///     let provider_factory = ProviderFactoryBuilder::<N>::default()
74    ///         .open_read_only(MAINNET.clone(), ReadOnlyConfig::from_datadir("datadir").no_watch())
75    ///         .unwrap();
76    /// }
77    /// ```
78    ///
79    /// # Open an instance with disabled read-transaction timeout
80    ///
81    /// By default, read transactions are automatically terminated after a timeout to prevent
82    /// database free list growth. However, if the database is static (no writes occurring), this
83    /// safety mechanism can be disabled using
84    /// [`ReadOnlyConfig::disable_long_read_transaction_safety`].
85    ///
86    /// ```no_run
87    /// use reth_chainspec::MAINNET;
88    /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder, ReadOnlyConfig};
89    ///
90    /// fn demo<N: NodeTypesForProvider<ChainSpec = reth_chainspec::ChainSpec>>() {
91    ///     let provider_factory = ProviderFactoryBuilder::<N>::default()
92    ///         .open_read_only(
93    ///             MAINNET.clone(),
94    ///             ReadOnlyConfig::from_datadir("datadir").disable_long_read_transaction_safety(),
95    ///         )
96    ///         .unwrap();
97    /// }
98    /// ```
99    pub fn open_read_only(
100        self,
101        chainspec: Arc<N::ChainSpec>,
102        config: impl Into<ReadOnlyConfig>,
103    ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
104    where
105        N: NodeTypesForProvider,
106    {
107        let ReadOnlyConfig { db_dir, db_args, static_files_dir, rocksdb_dir, watch_static_files } =
108            config.into();
109        self.db(Arc::new(open_db_read_only(db_dir, db_args)?))
110            .chainspec(chainspec)
111            .static_file(StaticFileProvider::read_only(static_files_dir, watch_static_files)?)
112            .rocksdb_provider(RocksDBProvider::builder(&rocksdb_dir).with_default_tables().build()?)
113            .build_provider_factory()
114            .map_err(Into::into)
115    }
116}
117
118impl<N> Default for ProviderFactoryBuilder<N> {
119    fn default() -> Self {
120        Self { _types: Default::default() }
121    }
122}
123
124/// Settings for how to open the database, static files, and `RocksDB`.
125///
126/// The default derivation from a path assumes the path is the datadir:
127/// [`ReadOnlyConfig::from_datadir`]
128#[derive(Debug, Clone)]
129pub struct ReadOnlyConfig {
130    /// The path to the database directory.
131    pub db_dir: PathBuf,
132    /// How to open the database
133    pub db_args: DatabaseArguments,
134    /// The path to the static file dir
135    pub static_files_dir: PathBuf,
136    /// The path to the `RocksDB` directory
137    pub rocksdb_dir: PathBuf,
138    /// Whether the static files should be watched for changes.
139    pub watch_static_files: bool,
140}
141
142impl ReadOnlyConfig {
143    /// Derives the [`ReadOnlyConfig`] from the datadir.
144    ///
145    /// By default this assumes the following datadir layout:
146    ///
147    /// ```text
148    ///  -`datadir`
149    ///    |__db
150    ///    |__rocksdb
151    ///    |__static_files
152    /// ```
153    ///
154    /// By default this watches the static file directory for changes, see also
155    /// [`StaticFileProvider::read_only`]
156    pub fn from_datadir(datadir: impl AsRef<Path>) -> Self {
157        let datadir = datadir.as_ref();
158        Self {
159            db_dir: datadir.join("db"),
160            db_args: Default::default(),
161            static_files_dir: datadir.join("static_files"),
162            rocksdb_dir: datadir.join("rocksdb"),
163            watch_static_files: true,
164        }
165    }
166
167    /// Disables long-lived read transaction safety guarantees.
168    ///
169    /// Caution: Keeping database transaction open indefinitely can cause the free list to grow if
170    /// changes to the database are made.
171    pub const fn disable_long_read_transaction_safety(mut self) -> Self {
172        self.db_args.max_read_transaction_duration(Some(MaxReadTransactionDuration::Unbounded));
173        self
174    }
175
176    /// Derives the [`ReadOnlyConfig`] from the database dir.
177    ///
178    /// By default this assumes the following datadir layout:
179    ///
180    /// ```text
181    ///    - db
182    ///    - rocksdb
183    ///    - static_files
184    /// ```
185    ///
186    /// By default this watches the static file directory for changes, see also
187    /// [`StaticFileProvider::read_only`]
188    ///
189    /// # Panics
190    ///
191    /// If the path does not exist
192    pub fn from_db_dir(db_dir: impl AsRef<Path>) -> Self {
193        let db_dir = db_dir.as_ref();
194        let datadir = std::fs::canonicalize(db_dir).unwrap().parent().unwrap().to_path_buf();
195        let static_files_dir = datadir.join("static_files");
196        let rocksdb_dir = datadir.join("rocksdb");
197        Self::from_dirs(db_dir, static_files_dir, rocksdb_dir)
198    }
199
200    /// Creates the config for the given paths.
201    ///
202    ///
203    /// By default this watches the static file directory for changes, see also
204    /// [`StaticFileProvider::read_only`]
205    pub fn from_dirs(
206        db_dir: impl AsRef<Path>,
207        static_files_dir: impl AsRef<Path>,
208        rocksdb_dir: impl AsRef<Path>,
209    ) -> Self {
210        Self {
211            db_dir: db_dir.as_ref().into(),
212            db_args: Default::default(),
213            static_files_dir: static_files_dir.as_ref().into(),
214            rocksdb_dir: rocksdb_dir.as_ref().into(),
215            watch_static_files: true,
216        }
217    }
218
219    /// Configures the db arguments used when opening the database.
220    pub fn with_db_args(mut self, db_args: impl Into<DatabaseArguments>) -> Self {
221        self.db_args = db_args.into();
222        self
223    }
224
225    /// Configures the db directory.
226    pub fn with_db_dir(mut self, db_dir: impl Into<PathBuf>) -> Self {
227        self.db_dir = db_dir.into();
228        self
229    }
230
231    /// Configures the static file directory.
232    pub fn with_static_file_dir(mut self, static_file_dir: impl Into<PathBuf>) -> Self {
233        self.static_files_dir = static_file_dir.into();
234        self
235    }
236
237    /// Whether the static file directory should be watches for changes, see also
238    /// [`StaticFileProvider::read_only`]
239    pub const fn set_watch_static_files(&mut self, watch_static_files: bool) {
240        self.watch_static_files = watch_static_files;
241    }
242
243    /// Don't watch the static files for changes.
244    ///
245    /// This is only recommended if this is used without a running node instance that modifies
246    /// static files.
247    pub const fn no_watch(mut self) -> Self {
248        self.set_watch_static_files(false);
249        self
250    }
251}
252
253impl<T> From<T> for ReadOnlyConfig
254where
255    T: AsRef<Path>,
256{
257    fn from(value: T) -> Self {
258        Self::from_datadir(value.as_ref())
259    }
260}
261
262/// This is staging type that contains the configured types and _one_ value.
263#[derive(Debug)]
264pub struct TypesAnd1<N, Val1> {
265    _types: PhantomData<N>,
266    val_1: Val1,
267}
268
269impl<N, Val1> TypesAnd1<N, Val1> {
270    /// Creates a new instance with the given types and one value.
271    pub fn new(val_1: Val1) -> Self {
272        Self { _types: Default::default(), val_1 }
273    }
274
275    /// Configures the chainspec.
276    pub fn chainspec<C>(self, chainspec: Arc<C>) -> TypesAnd2<N, Val1, Arc<C>> {
277        TypesAnd2::new(self.val_1, chainspec)
278    }
279}
280
281/// This is staging type that contains the configured types and _two_ values.
282#[derive(Debug)]
283pub struct TypesAnd2<N, Val1, Val2> {
284    _types: PhantomData<N>,
285    val_1: Val1,
286    val_2: Val2,
287}
288
289impl<N, Val1, Val2> TypesAnd2<N, Val1, Val2> {
290    /// Creates a new instance with the given types and two values.
291    pub fn new(val_1: Val1, val_2: Val2) -> Self {
292        Self { _types: Default::default(), val_1, val_2 }
293    }
294
295    /// Returns the first value.
296    pub const fn val_1(&self) -> &Val1 {
297        &self.val_1
298    }
299
300    /// Returns the second value.
301    pub const fn val_2(&self) -> &Val2 {
302        &self.val_2
303    }
304
305    /// Configures the [`StaticFileProvider`].
306    pub fn static_file(
307        self,
308        static_file_provider: StaticFileProvider<N::Primitives>,
309    ) -> TypesAnd3<N, Val1, Val2, StaticFileProvider<N::Primitives>>
310    where
311        N: NodeTypes,
312    {
313        TypesAnd3::new(self.val_1, self.val_2, static_file_provider)
314    }
315}
316
317/// This is staging type that contains the configured types and _three_ values.
318#[derive(Debug)]
319pub struct TypesAnd3<N, Val1, Val2, Val3> {
320    _types: PhantomData<N>,
321    val_1: Val1,
322    val_2: Val2,
323    val_3: Val3,
324}
325
326impl<N, Val1, Val2, Val3> TypesAnd3<N, Val1, Val2, Val3> {
327    /// Creates a new instance with the given types and three values.
328    pub fn new(val_1: Val1, val_2: Val2, val_3: Val3) -> Self {
329        Self { _types: Default::default(), val_1, val_2, val_3 }
330    }
331}
332
333impl<N, DB, C> TypesAnd3<N, DB, Arc<C>, StaticFileProvider<N::Primitives>>
334where
335    N: NodeTypes,
336{
337    /// Configures the `RocksDB` provider.
338    pub fn rocksdb_provider(
339        self,
340        rocksdb_provider: RocksDBProvider,
341    ) -> TypesAnd4<N, DB, Arc<C>, StaticFileProvider<N::Primitives>, RocksDBProvider> {
342        TypesAnd4::new(self.val_1, self.val_2, self.val_3, rocksdb_provider)
343    }
344}
345
346/// This is staging type that contains the configured types and _four_ values.
347#[derive(Debug)]
348pub struct TypesAnd4<N, Val1, Val2, Val3, Val4> {
349    _types: PhantomData<N>,
350    val_1: Val1,
351    val_2: Val2,
352    val_3: Val3,
353    val_4: Val4,
354}
355
356impl<N, Val1, Val2, Val3, Val4> TypesAnd4<N, Val1, Val2, Val3, Val4> {
357    /// Creates a new instance with the given types and four values.
358    pub fn new(val_1: Val1, val_2: Val2, val_3: Val3, val_4: Val4) -> Self {
359        Self { _types: Default::default(), val_1, val_2, val_3, val_4 }
360    }
361}
362
363impl<N, DB> TypesAnd4<N, DB, Arc<N::ChainSpec>, StaticFileProvider<N::Primitives>, RocksDBProvider>
364where
365    N: NodeTypesForProvider,
366    DB: Database + DatabaseMetrics + Clone + Unpin + 'static,
367{
368    /// Creates the [`ProviderFactory`].
369    pub fn build_provider_factory(
370        self,
371    ) -> ProviderResult<ProviderFactory<NodeTypesWithDBAdapter<N, DB>>> {
372        let Self { _types, val_1, val_2, val_3, val_4 } = self;
373        ProviderFactory::new(val_1, val_2, val_3, val_4)
374    }
375}