1use alloy_primitives::B256;
4use clap::Parser;
5use reth_chainspec::EthChainSpec;
6use reth_cli::chainspec::ChainSpecParser;
7use reth_config::{config::EtlConfig, Config};
8use reth_consensus::{noop::NoopConsensus, ConsensusError, FullConsensus};
9use reth_db::{init_db, open_db_read_only, DatabaseEnv};
10use reth_db_common::init::init_genesis;
11use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader};
12use reth_evm::{noop::NoopEvmConfig, ConfigureEvm};
13use reth_node_api::FullNodeTypesAdapter;
14use reth_node_builder::{
15 Node, NodeComponents, NodeComponentsBuilder, NodeTypes, NodeTypesWithDBAdapter,
16};
17use reth_node_core::{
18 args::{DatabaseArgs, DatadirArgs},
19 dirs::{ChainPath, DataDirPath},
20};
21use reth_provider::{
22 providers::{BlockchainProvider, NodeTypesForProvider, StaticFileProvider},
23 ProviderFactory, StaticFileProviderFactory,
24};
25use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
26use reth_static_file::StaticFileProducer;
27use std::{path::PathBuf, sync::Arc};
28use tokio::sync::watch;
29use tracing::{debug, info, warn};
30
31#[derive(Debug, Parser)]
33pub struct EnvironmentArgs<C: ChainSpecParser> {
34 #[command(flatten)]
36 pub datadir: DatadirArgs,
37
38 #[arg(long, value_name = "FILE")]
40 pub config: Option<PathBuf>,
41
42 #[arg(
46 long,
47 value_name = "CHAIN_OR_PATH",
48 long_help = C::help_message(),
49 default_value = C::SUPPORTED_CHAINS[0],
50 value_parser = C::parser(),
51 global = true
52 )]
53 pub chain: Arc<C::ChainSpec>,
54
55 #[command(flatten)]
57 pub db: DatabaseArgs,
58}
59
60impl<C: ChainSpecParser> EnvironmentArgs<C> {
61 pub fn init<N: CliNodeTypes>(&self, access: AccessRights) -> eyre::Result<Environment<N>>
64 where
65 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
66 {
67 let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
68 let db_path = data_dir.db();
69 let sf_path = data_dir.static_files();
70
71 if access.is_read_write() {
72 reth_fs_util::create_dir_all(&db_path)?;
73 reth_fs_util::create_dir_all(&sf_path)?;
74 }
75
76 let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
77
78 let mut config = Config::from_path(config_path)
79 .inspect_err(
80 |err| warn!(target: "reth::cli", %err, "Failed to load config file, using default"),
81 )
82 .unwrap_or_default();
83
84 if config.stages.etl.dir.is_none() {
86 config.stages.etl.dir = Some(EtlConfig::from_datadir(data_dir.data_dir()));
87 }
88
89 info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
90 let (db, sfp) = match access {
91 AccessRights::RW => (
92 Arc::new(init_db(db_path, self.db.database_args())?),
93 StaticFileProvider::read_write(sf_path)?,
94 ),
95 AccessRights::RO => (
96 Arc::new(open_db_read_only(&db_path, self.db.database_args())?),
97 StaticFileProvider::read_only(sf_path, false)?,
98 ),
99 };
100
101 let provider_factory = self.create_provider_factory(&config, db, sfp)?;
102 if access.is_read_write() {
103 debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
104 init_genesis(&provider_factory)?;
105 }
106
107 Ok(Environment { config, provider_factory, data_dir })
108 }
109
110 fn create_provider_factory<N: CliNodeTypes>(
116 &self,
117 config: &Config,
118 db: Arc<DatabaseEnv>,
119 static_file_provider: StaticFileProvider<N::Primitives>,
120 ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
121 where
122 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
123 {
124 let has_receipt_pruning = config.prune.as_ref().is_some_and(|a| a.has_receipts_pruning());
125 let prune_modes =
126 config.prune.as_ref().map(|prune| prune.segments.clone()).unwrap_or_default();
127 let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::new(
128 db,
129 self.chain.clone(),
130 static_file_provider,
131 )
132 .with_prune_modes(prune_modes.clone());
133
134 if let Some(unwind_target) = factory
136 .static_file_provider()
137 .check_consistency(&factory.provider()?, has_receipt_pruning)?
138 {
139 if factory.db_ref().is_read_only()? {
140 warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
141 return Ok(factory)
142 }
143
144 assert_ne!(
147 unwind_target,
148 PipelineTarget::Unwind(0),
149 "A static file <> database inconsistency was found that would trigger an unwind to block 0"
150 );
151
152 info!(target: "reth::cli", unwind_target = %unwind_target, "Executing an unwind after a failed storage consistency check.");
153
154 let (_tip_tx, tip_rx) = watch::channel(B256::ZERO);
155
156 let mut pipeline = Pipeline::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::builder()
158 .add_stages(DefaultStages::new(
159 factory.clone(),
160 tip_rx,
161 Arc::new(NoopConsensus::default()),
162 NoopHeaderDownloader::default(),
163 NoopBodiesDownloader::default(),
164 NoopEvmConfig::<N::Evm>::default(),
165 config.stages.clone(),
166 prune_modes.clone(),
167 ))
168 .build(factory.clone(), StaticFileProducer::new(factory.clone(), prune_modes));
169
170 pipeline.move_to_static_files()?;
172 pipeline.unwind(unwind_target.unwind_target().expect("should exist"), None)?;
173 }
174
175 Ok(factory)
176 }
177}
178
179#[derive(Debug)]
181pub struct Environment<N: NodeTypes> {
182 pub config: Config,
184 pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>,
186 pub data_dir: ChainPath<DataDirPath>,
188}
189
190#[derive(Debug, Copy, Clone)]
192pub enum AccessRights {
193 RW,
195 RO,
197}
198
199impl AccessRights {
200 pub const fn is_read_write(&self) -> bool {
202 matches!(self, Self::RW)
203 }
204}
205
206type FullTypesAdapter<T> = FullNodeTypesAdapter<
208 T,
209 Arc<DatabaseEnv>,
210 BlockchainProvider<NodeTypesWithDBAdapter<T, Arc<DatabaseEnv>>>,
211>;
212
213pub trait CliNodeTypes: NodeTypesForProvider {
216 type Evm: ConfigureEvm<Primitives = Self::Primitives>;
217}
218
219impl<N> CliNodeTypes for N
220where
221 N: Node<FullTypesAdapter<Self>> + NodeTypesForProvider,
222{
223 type Evm = <<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Evm;
224}
225
226pub trait CliNodeComponents<N: CliNodeTypes> {
228 type Evm: ConfigureEvm<Primitives = N::Primitives> + 'static;
230 type Consensus: FullConsensus<N::Primitives, Error = ConsensusError> + Clone + 'static;
232
233 fn evm_config(&self) -> &Self::Evm;
235 fn consensus(&self) -> &Self::Consensus;
237}
238
239impl<N: CliNodeTypes, E, C> CliNodeComponents<N> for (E, C)
240where
241 E: ConfigureEvm<Primitives = N::Primitives> + 'static,
242 C: FullConsensus<N::Primitives, Error = ConsensusError> + Clone + 'static,
243{
244 type Evm = E;
245 type Consensus = C;
246
247 fn evm_config(&self) -> &Self::Evm {
248 &self.0
249 }
250
251 fn consensus(&self) -> &Self::Consensus {
252 &self.1
253 }
254}