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 ProviderFactory, 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 {
81 StorageSettings::v2()
82 }
83
84 pub fn init<N: CliNodeTypes>(
89 &self,
90 access: AccessRights,
91 runtime: reth_tasks::Runtime,
92 ) -> eyre::Result<Environment<N>>
93 where
94 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
95 {
96 let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
97 let db_path = data_dir.db();
98 let sf_path = data_dir.static_files();
99 let rocksdb_path = data_dir.rocksdb();
100
101 if access.is_read_write() {
102 reth_fs_util::create_dir_all(&db_path)?;
103 reth_fs_util::create_dir_all(&sf_path)?;
104 reth_fs_util::create_dir_all(&rocksdb_path)?;
105 }
106
107 let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
108
109 let mut config = Config::from_path(config_path)
110 .inspect_err(
111 |err| warn!(target: "reth::cli", %err, "Failed to load config file, using default"),
112 )
113 .unwrap_or_default();
114
115 if config.stages.etl.dir.is_none() {
117 config.stages.etl.dir = Some(EtlConfig::from_datadir(data_dir.data_dir()));
118 }
119 if config.stages.era.folder.is_none() {
120 config.stages.era = config.stages.era.with_datadir(data_dir.data_dir());
121 }
122
123 info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
124 let genesis_block_number = self.chain.genesis().number.unwrap_or_default();
125 let (db, sfp) = match access {
126 AccessRights::RW => (
127 init_db(db_path, self.db.database_args())?,
128 StaticFileProviderBuilder::read_write(sf_path)
129 .with_metrics()
130 .with_genesis_block_number(genesis_block_number)
131 .build()?,
132 ),
133 AccessRights::RO | AccessRights::RoInconsistent => {
134 (open_db_read_only(&db_path, self.db.database_args())?, {
135 let provider = StaticFileProviderBuilder::read_only(sf_path)
136 .with_metrics()
137 .with_genesis_block_number(genesis_block_number)
138 .build()?;
139 provider.watch_directory();
140 provider
141 })
142 }
143 };
144 let rocksdb_provider = if !access.is_read_write() && !RocksDBProvider::exists(&rocksdb_path)
145 {
146 debug!(target: "reth::cli", ?rocksdb_path, "RocksDB not found, initializing empty database");
150 reth_fs_util::create_dir_all(&rocksdb_path)?;
151 RocksDBProvider::builder(data_dir.rocksdb())
152 .with_default_tables()
153 .with_database_log_level(self.db.log_level)
154 .build()?
155 } else {
156 RocksDBProvider::builder(data_dir.rocksdb())
157 .with_default_tables()
158 .with_database_log_level(self.db.log_level)
159 .with_read_only(!access.is_read_write())
160 .build()?
161 };
162
163 let provider_factory =
164 self.create_provider_factory(&config, db, sfp, rocksdb_provider, access, runtime)?;
165 if access.is_read_write() {
166 debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
167 init_genesis_with_settings(&provider_factory, self.storage_settings())?;
168 }
169
170 Ok(Environment { config, provider_factory, data_dir })
171 }
172
173 fn create_provider_factory<N: CliNodeTypes>(
179 &self,
180 config: &Config,
181 db: DatabaseEnv,
182 static_file_provider: StaticFileProvider<N::Primitives>,
183 rocksdb_provider: RocksDBProvider,
184 access: AccessRights,
185 runtime: reth_tasks::Runtime,
186 ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>>>
187 where
188 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
189 {
190 let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, DatabaseEnv>>::new(
191 db,
192 self.chain.clone(),
193 static_file_provider,
194 rocksdb_provider,
195 runtime,
196 )?
197 .with_prune_modes(config.prune.segments.clone())
198 .with_minimum_pruning_distance(config.prune.minimum_pruning_distance);
199
200 if !access.is_read_only_inconsistent() &&
202 let Some(unwind_target) =
203 factory.static_file_provider().check_consistency(&factory.provider()?)?
204 {
205 if factory.db_ref().is_read_only()? {
206 warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
207 return Ok(factory)
208 }
209
210 assert_ne!(
213 unwind_target,
214 PipelineTarget::Unwind(0),
215 "A static file <> database inconsistency was found that would trigger an unwind to block 0"
216 );
217
218 info!(target: "reth::cli", unwind_target = %unwind_target, "Executing an unwind after a failed storage consistency check.");
219
220 let (_tip_tx, tip_rx) = watch::channel(B256::ZERO);
221
222 let mut pipeline = Pipeline::<NodeTypesWithDBAdapter<N, DatabaseEnv>>::builder()
224 .add_stages(DefaultStages::new(
225 factory.clone(),
226 tip_rx,
227 Arc::new(NoopConsensus::default()),
228 NoopHeaderDownloader::default(),
229 NoopBodiesDownloader::default(),
230 NoopEvmConfig::<N::Evm>::default(),
231 config.stages.clone(),
232 config.prune.segments.clone(),
233 None,
234 ))
235 .build(
236 factory.clone(),
237 StaticFileProducer::new(factory.clone(), config.prune.segments.clone()),
238 );
239
240 pipeline.move_to_static_files()?;
242 pipeline.unwind(unwind_target.unwind_target().expect("should exist"), None)?;
243 }
244
245 Ok(factory)
246 }
247}
248
249#[derive(Debug)]
251pub struct Environment<N: NodeTypes> {
252 pub config: Config,
254 pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>>,
256 pub data_dir: ChainPath<DataDirPath>,
258}
259
260#[derive(Debug, Copy, Clone)]
262pub enum AccessRights {
263 RW,
265 RO,
267 RoInconsistent,
269}
270
271impl AccessRights {
272 pub const fn is_read_write(&self) -> bool {
274 matches!(self, Self::RW)
275 }
276
277 pub const fn is_read_only_inconsistent(&self) -> bool {
280 matches!(self, Self::RoInconsistent)
281 }
282}
283
284type FullTypesAdapter<T> = FullNodeTypesAdapter<
286 T,
287 DatabaseEnv,
288 BlockchainProvider<NodeTypesWithDBAdapter<T, DatabaseEnv>>,
289>;
290
291pub trait CliNodeTypes: Node<FullTypesAdapter<Self>> + NodeTypesForProvider {
294 type Evm: ConfigureEvm<Primitives = Self::Primitives>;
295 type NetworkPrimitives: NetPrimitivesFor<Self::Primitives>;
296}
297
298impl<N> CliNodeTypes for N
299where
300 N: Node<FullTypesAdapter<Self>> + NodeTypesForProvider,
301{
302 type Evm = <<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Evm;
303 type NetworkPrimitives = <<<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Network as NetworkEventListenerProvider>::Primitives;
304}
305
306type EvmFor<N> = <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
307 FullTypesAdapter<N>,
308>>::Components as NodeComponents<FullTypesAdapter<N>>>::Evm;
309
310type ConsensusFor<N> =
311 <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
312 FullTypesAdapter<N>,
313 >>::Components as NodeComponents<FullTypesAdapter<N>>>::Consensus;
314
315pub trait CliNodeComponents<N: CliNodeTypes>: Send + Sync + 'static {
317 fn evm_config(&self) -> &EvmFor<N>;
319 fn consensus(&self) -> &ConsensusFor<N>;
321}
322
323impl<N: CliNodeTypes> CliNodeComponents<N> for (EvmFor<N>, ConsensusFor<N>) {
324 fn evm_config(&self) -> &EvmFor<N> {
325 &self.0
326 }
327
328 fn consensus(&self) -> &ConsensusFor<N> {
329 &self.1
330 }
331}
332
333pub trait CliComponentsBuilder<N: CliNodeTypes>:
335 FnOnce(Arc<N::ChainSpec>) -> Self::Components + Send + Sync + 'static
336{
337 type Components: CliNodeComponents<N>;
338}
339
340impl<N: CliNodeTypes, F, Comp> CliComponentsBuilder<N> for F
341where
342 F: FnOnce(Arc<N::ChainSpec>) -> Comp + Send + Sync + 'static,
343 Comp: CliNodeComponents<N>,
344{
345 type Components = Comp;
346}