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