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, 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, 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 .build_provider_factory()
113 .map_err(Into::into)
114 }
115}
116
117impl<N> Default for ProviderFactoryBuilder<N> {
118 fn default() -> Self {
119 Self { _types: Default::default() }
120 }
121}
122
123/// Settings for how to open the database and static files.
124///
125/// The default derivation from a path assumes the path is the datadir:
126/// [`ReadOnlyConfig::from_datadir`]
127#[derive(Debug, Clone)]
128pub struct ReadOnlyConfig {
129 /// The path to the database directory.
130 pub db_dir: PathBuf,
131 /// How to open the database
132 pub db_args: DatabaseArguments,
133 /// The path to the static file dir
134 pub static_files_dir: PathBuf,
135 /// Whether the static files should be watched for changes.
136 pub watch_static_files: bool,
137}
138
139impl ReadOnlyConfig {
140 /// Derives the [`ReadOnlyConfig`] from the datadir.
141 ///
142 /// By default this assumes the following datadir layout:
143 ///
144 /// ```text
145 /// -`datadir`
146 /// |__db
147 /// |__static_files
148 /// ```
149 ///
150 /// By default this watches the static file directory for changes, see also
151 /// [`StaticFileProvider::read_only`]
152 pub fn from_datadir(datadir: impl AsRef<Path>) -> Self {
153 let datadir = datadir.as_ref();
154 Self::from_dirs(datadir.join("db"), datadir.join("static_files"))
155 }
156
157 /// Disables long-lived read transaction safety guarantees.
158 ///
159 /// Caution: Keeping database transaction open indefinitely can cause the free list to grow if
160 /// changes to the database are made.
161 pub const fn disable_long_read_transaction_safety(mut self) -> Self {
162 self.db_args.max_read_transaction_duration(Some(MaxReadTransactionDuration::Unbounded));
163 self
164 }
165
166 /// Derives the [`ReadOnlyConfig`] from the database dir.
167 ///
168 /// By default this assumes the following datadir layout:
169 ///
170 /// ```text
171 /// - db
172 /// -static_files
173 /// ```
174 ///
175 /// By default this watches the static file directory for changes, see also
176 /// [`StaticFileProvider::read_only`]
177 ///
178 /// # Panics
179 ///
180 /// If the path does not exist
181 pub fn from_db_dir(db_dir: impl AsRef<Path>) -> Self {
182 let db_dir = db_dir.as_ref();
183 let static_files_dir = std::fs::canonicalize(db_dir)
184 .unwrap()
185 .parent()
186 .unwrap()
187 .to_path_buf()
188 .join("static_files");
189 Self::from_dirs(db_dir, static_files_dir)
190 }
191
192 /// Creates the config for the given paths.
193 ///
194 ///
195 /// By default this watches the static file directory for changes, see also
196 /// [`StaticFileProvider::read_only`]
197 pub fn from_dirs(db_dir: impl AsRef<Path>, static_files_dir: impl AsRef<Path>) -> Self {
198 Self {
199 static_files_dir: static_files_dir.as_ref().into(),
200 db_dir: db_dir.as_ref().into(),
201 db_args: Default::default(),
202 watch_static_files: true,
203 }
204 }
205
206 /// Configures the db arguments used when opening the database.
207 pub fn with_db_args(mut self, db_args: impl Into<DatabaseArguments>) -> Self {
208 self.db_args = db_args.into();
209 self
210 }
211
212 /// Configures the db directory.
213 pub fn with_db_dir(mut self, db_dir: impl Into<PathBuf>) -> Self {
214 self.db_dir = db_dir.into();
215 self
216 }
217
218 /// Configures the static file directory.
219 pub fn with_static_file_dir(mut self, static_file_dir: impl Into<PathBuf>) -> Self {
220 self.static_files_dir = static_file_dir.into();
221 self
222 }
223
224 /// Whether the static file directory should be watches for changes, see also
225 /// [`StaticFileProvider::read_only`]
226 pub const fn set_watch_static_files(&mut self, watch_static_files: bool) {
227 self.watch_static_files = watch_static_files;
228 }
229
230 /// Don't watch the static files for changes.
231 ///
232 /// This is only recommended if this is used without a running node instance that modifies
233 /// static files.
234 pub const fn no_watch(mut self) -> Self {
235 self.set_watch_static_files(false);
236 self
237 }
238}
239
240impl<T> From<T> for ReadOnlyConfig
241where
242 T: AsRef<Path>,
243{
244 fn from(value: T) -> Self {
245 Self::from_datadir(value.as_ref())
246 }
247}
248
249/// This is staging type that contains the configured types and _one_ value.
250#[derive(Debug)]
251pub struct TypesAnd1<N, Val1> {
252 _types: PhantomData<N>,
253 val_1: Val1,
254}
255
256impl<N, Val1> TypesAnd1<N, Val1> {
257 /// Creates a new instance with the given types and one value.
258 pub fn new(val_1: Val1) -> Self {
259 Self { _types: Default::default(), val_1 }
260 }
261
262 /// Configures the chainspec.
263 pub fn chainspec<C>(self, chainspec: Arc<C>) -> TypesAnd2<N, Val1, Arc<C>> {
264 TypesAnd2::new(self.val_1, chainspec)
265 }
266}
267
268/// This is staging type that contains the configured types and _two_ values.
269#[derive(Debug)]
270pub struct TypesAnd2<N, Val1, Val2> {
271 _types: PhantomData<N>,
272 val_1: Val1,
273 val_2: Val2,
274}
275
276impl<N, Val1, Val2> TypesAnd2<N, Val1, Val2> {
277 /// Creates a new instance with the given types and two values.
278 pub fn new(val_1: Val1, val_2: Val2) -> Self {
279 Self { _types: Default::default(), val_1, val_2 }
280 }
281
282 /// Returns the first value.
283 pub const fn val_1(&self) -> &Val1 {
284 &self.val_1
285 }
286
287 /// Returns the second value.
288 pub const fn val_2(&self) -> &Val2 {
289 &self.val_2
290 }
291
292 /// Configures the [`StaticFileProvider`].
293 pub fn static_file(
294 self,
295 static_file_provider: StaticFileProvider<N::Primitives>,
296 ) -> TypesAnd3<N, Val1, Val2, StaticFileProvider<N::Primitives>>
297 where
298 N: NodeTypes,
299 {
300 TypesAnd3::new(self.val_1, self.val_2, static_file_provider)
301 }
302}
303
304/// This is staging type that contains the configured types and _three_ values.
305#[derive(Debug)]
306pub struct TypesAnd3<N, Val1, Val2, Val3> {
307 _types: PhantomData<N>,
308 val_1: Val1,
309 val_2: Val2,
310 val_3: Val3,
311}
312
313impl<N, Val1, Val2, Val3> TypesAnd3<N, Val1, Val2, Val3> {
314 /// Creates a new instance with the given types and three values.
315 pub fn new(val_1: Val1, val_2: Val2, val_3: Val3) -> Self {
316 Self { _types: Default::default(), val_1, val_2, val_3 }
317 }
318}
319
320impl<N, DB> TypesAnd3<N, DB, Arc<N::ChainSpec>, StaticFileProvider<N::Primitives>>
321where
322 N: NodeTypesForProvider,
323 DB: Database + DatabaseMetrics + Clone + Unpin + 'static,
324{
325 /// Creates the [`ProviderFactory`].
326 pub fn build_provider_factory(
327 self,
328 ) -> ProviderResult<ProviderFactory<NodeTypesWithDBAdapter<N, DB>>> {
329 let Self { _types, val_1, val_2, val_3 } = self;
330 ProviderFactory::new(val_1, val_2, val_3)
331 }
332}