1pub use reth_primitives_traits::header::HeaderMut;
4
5use alloy_primitives::B256;
6use clap::Parser;
7use reth_chainspec::EthChainSpec;
8use reth_cli::chainspec::ChainSpecParser;
9use reth_config::{config::EtlConfig, Config};
10use reth_consensus::noop::NoopConsensus;
11use reth_db::{init_db, open_db_read_only, DatabaseEnv};
12use reth_db_common::init::init_genesis_with_settings;
13use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader};
14use reth_eth_wire::NetPrimitivesFor;
15use reth_evm::{noop::NoopEvmConfig, ConfigureEvm};
16use reth_network::NetworkEventListenerProvider;
17use reth_node_api::FullNodeTypesAdapter;
18use reth_node_builder::{
19 Node, NodeComponents, NodeComponentsBuilder, NodeTypes, NodeTypesWithDBAdapter,
20};
21use reth_node_core::{
22 args::{DatabaseArgs, DatadirArgs, StaticFilesArgs, StorageArgs},
23 dirs::{ChainPath, DataDirPath},
24};
25use reth_provider::{
26 providers::{
27 BlockchainProvider, NodeTypesForProvider, RocksDBProvider, StaticFileProvider,
28 StaticFileProviderBuilder,
29 },
30 BalStoreHandle, ProviderFactory, RocksDBBalStore, StaticFileProviderFactory, StorageSettings,
31};
32use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
33use reth_static_file::StaticFileProducer;
34use std::{path::PathBuf, sync::Arc};
35use tokio::sync::watch;
36use tracing::{debug, info, warn};
37
38#[derive(Debug, Parser)]
40pub struct EnvironmentArgs<C: ChainSpecParser> {
41 #[command(flatten)]
43 pub datadir: DatadirArgs,
44
45 #[arg(long, value_name = "FILE")]
47 pub config: Option<PathBuf>,
48
49 #[arg(
53 long,
54 value_name = "CHAIN_OR_PATH",
55 long_help = C::help_message(),
56 default_value = C::default_value(),
57 value_parser = C::parser(),
58 global = true
59 )]
60 pub chain: Arc<C::ChainSpec>,
61
62 #[command(flatten)]
64 pub db: DatabaseArgs,
65
66 #[command(flatten)]
68 pub static_files: StaticFilesArgs,
69
70 #[command(flatten)]
72 pub storage: StorageArgs,
73}
74
75impl<C: ChainSpecParser> EnvironmentArgs<C> {
76 pub fn storage_settings(&self) -> StorageSettings {
82 if self.storage.v2 {
83 StorageSettings::v2()
84 } else {
85 StorageSettings::v1()
86 }
87 }
88
89 pub fn init<N: CliNodeTypes>(
94 &self,
95 access: AccessRights,
96 runtime: reth_tasks::Runtime,
97 ) -> eyre::Result<Environment<N>>
98 where
99 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
100 {
101 let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
102 let db_path = data_dir.db();
103 let sf_path = data_dir.static_files();
104 let rocksdb_path = data_dir.rocksdb();
105
106 if access.is_read_write() {
107 reth_fs_util::create_dir_all(&db_path)?;
108 reth_fs_util::create_dir_all(&sf_path)?;
109 reth_fs_util::create_dir_all(&rocksdb_path)?;
110 }
111
112 let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
113
114 let mut config = Config::from_path(config_path)
115 .inspect_err(
116 |err| warn!(target: "reth::cli", %err, "Failed to load config file, using default"),
117 )
118 .unwrap_or_default();
119
120 if config.stages.etl.dir.is_none() {
122 config.stages.etl.dir = Some(EtlConfig::from_datadir(data_dir.data_dir()));
123 }
124 if config.stages.era.folder.is_none() {
125 config.stages.era = config.stages.era.with_datadir(data_dir.data_dir());
126 }
127
128 info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
129 let genesis_block_number = self.chain.genesis().number.unwrap_or_default();
130 let (db, sfp) = match access {
131 AccessRights::RW | AccessRights::RwInconsistent => (
132 init_db(db_path, self.db.database_args())?,
133 StaticFileProviderBuilder::read_write(sf_path)
134 .with_metrics()
135 .with_genesis_block_number(genesis_block_number)
136 .build()?,
137 ),
138 AccessRights::RO | AccessRights::RoInconsistent => (
139 open_db_read_only(&db_path, self.db.database_args())?,
140 StaticFileProviderBuilder::read_only(sf_path)
141 .with_metrics()
142 .with_genesis_block_number(genesis_block_number)
143 .build()?,
144 ),
145 };
146 let rocksdb_provider = if !access.is_read_write() && !RocksDBProvider::exists(&rocksdb_path)
147 {
148 debug!(target: "reth::cli", ?rocksdb_path, "RocksDB not found, initializing empty database");
152 reth_fs_util::create_dir_all(&rocksdb_path)?;
153 let mut builder = RocksDBProvider::builder(data_dir.rocksdb())
154 .with_default_tables()
155 .with_database_log_level(self.db.log_level);
156 if let Some(cache_size) = self.db.rocksdb_block_cache_size {
157 builder = builder.with_block_cache_size(cache_size);
158 }
159 builder.build()?
160 } else {
161 let mut builder = RocksDBProvider::builder(data_dir.rocksdb())
162 .with_default_tables()
163 .with_database_log_level(self.db.log_level)
164 .with_read_only(!access.is_read_write());
165 if let Some(cache_size) = self.db.rocksdb_block_cache_size {
166 builder = builder.with_block_cache_size(cache_size);
167 }
168 builder.build()?
169 };
170
171 let provider_factory =
172 self.create_provider_factory(&config, db, sfp, rocksdb_provider, access, runtime)?;
173 if access.is_read_write() {
174 debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
175 init_genesis_with_settings(&provider_factory, self.storage_settings())?;
176 }
177
178 Ok(Environment { config, provider_factory, data_dir })
179 }
180
181 fn create_provider_factory<N: CliNodeTypes>(
187 &self,
188 config: &Config,
189 db: DatabaseEnv,
190 static_file_provider: StaticFileProvider<N::Primitives>,
191 rocksdb_provider: RocksDBProvider,
192 access: AccessRights,
193 runtime: reth_tasks::Runtime,
194 ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>>>
195 where
196 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
197 {
198 let bal_store = self
199 .db
200 .balstore_cache_size
201 .map(|distance| {
202 RocksDBBalStore::with_buffer_retention_distance(rocksdb_provider.clone(), distance)
203 })
204 .unwrap_or_else(|| RocksDBBalStore::new(rocksdb_provider.clone()));
205 let bal_store = BalStoreHandle::new(bal_store);
206 let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, DatabaseEnv>>::new(
207 db,
208 self.chain.clone(),
209 static_file_provider,
210 rocksdb_provider,
211 runtime,
212 )?
213 .with_prune_modes(config.prune.segments.clone())
214 .with_minimum_pruning_distance(config.prune.minimum_pruning_distance)
215 .with_bal_store(bal_store);
216
217 if !access.skips_consistency_check() &&
219 let Some(unwind_target) =
220 factory.static_file_provider().check_consistency(&factory.provider()?)?
221 {
222 if factory.db_ref().is_read_only()? {
223 warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
224 return Ok(factory)
225 }
226
227 assert_ne!(
230 unwind_target,
231 PipelineTarget::Unwind(0),
232 "A static file <> database inconsistency was found that would trigger an unwind to block 0"
233 );
234
235 info!(target: "reth::cli", unwind_target = %unwind_target, "Executing an unwind after a failed storage consistency check.");
236
237 let (_tip_tx, tip_rx) = watch::channel(B256::ZERO);
238
239 let mut pipeline = Pipeline::<NodeTypesWithDBAdapter<N, DatabaseEnv>>::builder()
241 .add_stages(DefaultStages::new(
242 factory.clone(),
243 tip_rx,
244 Arc::new(NoopConsensus::default()),
245 NoopHeaderDownloader::default(),
246 NoopBodiesDownloader::default(),
247 NoopEvmConfig::<N::Evm>::default(),
248 config.stages.clone(),
249 config.prune.segments.clone(),
250 None,
251 ))
252 .build(
253 factory.clone(),
254 StaticFileProducer::new(factory.clone(), config.prune.segments.clone()),
255 );
256
257 pipeline.move_to_static_files()?;
259 pipeline.unwind(unwind_target.unwind_target().expect("should exist"), None)?;
260 }
261
262 Ok(factory)
263 }
264}
265
266#[derive(Debug)]
268pub struct Environment<N: NodeTypes> {
269 pub config: Config,
271 pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>>,
273 pub data_dir: ChainPath<DataDirPath>,
275}
276
277#[derive(Debug, Copy, Clone)]
279pub enum AccessRights {
280 RW,
282 RwInconsistent,
284 RO,
286 RoInconsistent,
288}
289
290impl AccessRights {
291 pub const fn is_read_write(&self) -> bool {
293 matches!(self, Self::RW | Self::RwInconsistent)
294 }
295
296 pub const fn is_read_only_inconsistent(&self) -> bool {
299 matches!(self, Self::RoInconsistent)
300 }
301
302 pub const fn skips_consistency_check(&self) -> bool {
304 matches!(self, Self::RwInconsistent | Self::RoInconsistent)
305 }
306}
307
308type FullTypesAdapter<T> = FullNodeTypesAdapter<
310 T,
311 DatabaseEnv,
312 BlockchainProvider<NodeTypesWithDBAdapter<T, DatabaseEnv>>,
313>;
314
315pub trait CliNodeTypes: Node<FullTypesAdapter<Self>> + NodeTypesForProvider {
318 type Evm: ConfigureEvm<Primitives = Self::Primitives>;
319 type NetworkPrimitives: NetPrimitivesFor<Self::Primitives>;
320}
321
322impl<N> CliNodeTypes for N
323where
324 N: Node<FullTypesAdapter<Self>> + NodeTypesForProvider,
325{
326 type Evm = <<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Evm;
327 type NetworkPrimitives = <<<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Network as NetworkEventListenerProvider>::Primitives;
328}
329
330type EvmFor<N> = <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
331 FullTypesAdapter<N>,
332>>::Components as NodeComponents<FullTypesAdapter<N>>>::Evm;
333
334type ConsensusFor<N> =
335 <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
336 FullTypesAdapter<N>,
337 >>::Components as NodeComponents<FullTypesAdapter<N>>>::Consensus;
338
339pub trait CliNodeComponents<N: CliNodeTypes>: Send + Sync + 'static {
341 fn evm_config(&self) -> &EvmFor<N>;
343 fn consensus(&self) -> &ConsensusFor<N>;
345}
346
347impl<N: CliNodeTypes> CliNodeComponents<N> for (EvmFor<N>, ConsensusFor<N>) {
348 fn evm_config(&self) -> &EvmFor<N> {
349 &self.0
350 }
351
352 fn consensus(&self) -> &ConsensusFor<N> {
353 &self.1
354 }
355}
356
357pub trait CliComponentsBuilder<N: CliNodeTypes>:
359 FnOnce(Arc<N::ChainSpec>) -> Self::Components + Send + Sync + 'static
360{
361 type Components: CliNodeComponents<N>;
362}
363
364impl<N: CliNodeTypes, F, Comp> CliComponentsBuilder<N> for F
365where
366 F: FnOnce(Arc<N::ChainSpec>) -> Comp + Send + Sync + 'static,
367 Comp: CliNodeComponents<N>,
368{
369 type Components = Comp;
370}
371
372#[cfg(test)]
373mod tests {
374 use super::AccessRights;
375
376 #[test]
377 fn inconsistent_access_rights_skip_consistency_checks() {
378 assert!(AccessRights::RwInconsistent.is_read_write());
379 assert!(AccessRights::RwInconsistent.skips_consistency_check());
380 assert!(!AccessRights::RW.skips_consistency_check());
381
382 assert!(!AccessRights::RoInconsistent.is_read_write());
383 assert!(AccessRights::RoInconsistent.is_read_only_inconsistent());
384 assert!(AccessRights::RoInconsistent.skips_consistency_check());
385 assert!(!AccessRights::RO.skips_consistency_check());
386 }
387}