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