reth_provider/providers/database/builder.rs
1//! Helper builder entrypoint to instantiate a [`ProviderFactory`].
2
3use crate::{
4 providers::{NodeTypesForProvider, RocksDBProvider, StaticFileProvider},
5 ProviderFactory,
6};
7use reth_db::{
8 mdbx::{DatabaseArguments, MaxReadTransactionDuration},
9 open_db_read_only, DatabaseEnv,
10};
11use reth_node_types::NodeTypesWithDBAdapter;
12use std::{
13 marker::PhantomData,
14 path::{Path, PathBuf},
15 sync::Arc,
16};
17
18/// Helper type to create a [`ProviderFactory`].
19///
20/// See [`ProviderFactoryBuilder::open_read_only`] for usage examples.
21#[derive(Debug)]
22pub struct ProviderFactoryBuilder<N> {
23 _types: PhantomData<N>,
24}
25
26impl<N> ProviderFactoryBuilder<N> {
27 /// Maps the [`reth_node_types::NodeTypes`] of this builder.
28 pub fn types<T>(self) -> ProviderFactoryBuilder<T> {
29 ProviderFactoryBuilder::default()
30 }
31
32 /// Opens the database with the given chainspec and [`ReadOnlyConfig`].
33 ///
34 /// # Open a monitored instance
35 ///
36 /// This is recommended when the new read-only instance is used with an active node.
37 ///
38 /// ```no_run
39 /// use reth_chainspec::MAINNET;
40 /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder};
41 ///
42 /// fn demo<N: NodeTypesForProvider<ChainSpec = reth_chainspec::ChainSpec>>(
43 /// runtime: reth_tasks::Runtime,
44 /// ) {
45 /// let provider_factory = ProviderFactoryBuilder::<N>::default()
46 /// .open_read_only(MAINNET.clone(), "datadir", runtime)
47 /// .unwrap();
48 /// }
49 /// ```
50 ///
51 /// # Open an unmonitored instance
52 ///
53 /// This is recommended when no changes to the static files are expected (e.g. no active node)
54 ///
55 /// ```no_run
56 /// use reth_chainspec::MAINNET;
57 /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder, ReadOnlyConfig};
58 ///
59 /// fn demo<N: NodeTypesForProvider<ChainSpec = reth_chainspec::ChainSpec>>(
60 /// runtime: reth_tasks::Runtime,
61 /// ) {
62 /// let provider_factory = ProviderFactoryBuilder::<N>::default()
63 /// .open_read_only(
64 /// MAINNET.clone(),
65 /// ReadOnlyConfig::from_datadir("datadir").no_watch(),
66 /// runtime,
67 /// )
68 /// .unwrap();
69 /// }
70 /// ```
71 ///
72 /// # Open an instance with disabled read-transaction timeout
73 ///
74 /// By default, read transactions are automatically terminated after a timeout to prevent
75 /// database free list growth. However, if the database is static (no writes occurring), this
76 /// safety mechanism can be disabled using
77 /// [`ReadOnlyConfig::disable_long_read_transaction_safety`].
78 ///
79 /// ```no_run
80 /// use reth_chainspec::MAINNET;
81 /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder, ReadOnlyConfig};
82 ///
83 /// fn demo<N: NodeTypesForProvider<ChainSpec = reth_chainspec::ChainSpec>>(
84 /// runtime: reth_tasks::Runtime,
85 /// ) {
86 /// let provider_factory = ProviderFactoryBuilder::<N>::default()
87 /// .open_read_only(
88 /// MAINNET.clone(),
89 /// ReadOnlyConfig::from_datadir("datadir").disable_long_read_transaction_safety(),
90 /// runtime,
91 /// )
92 /// .unwrap();
93 /// }
94 /// ```
95 pub fn open_read_only(
96 self,
97 chainspec: Arc<N::ChainSpec>,
98 config: impl Into<ReadOnlyConfig>,
99 runtime: reth_tasks::Runtime,
100 ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>>>
101 where
102 N: NodeTypesForProvider,
103 {
104 let ReadOnlyConfig { db_dir, db_args, static_files_dir, rocksdb_dir, watch_static_files } =
105 config.into();
106 let db = open_db_read_only(db_dir, db_args)?;
107 let static_file_provider =
108 StaticFileProvider::read_only(static_files_dir, watch_static_files)?;
109 let rocksdb_provider = RocksDBProvider::builder(&rocksdb_dir)
110 .with_default_tables()
111 .with_read_only(true)
112 .build()?;
113 ProviderFactory::new(db, chainspec, static_file_provider, rocksdb_provider, runtime)
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}