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;
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_eth_wire::NetPrimitivesFor;
13use reth_evm::{noop::NoopEvmConfig, ConfigureEvm};
14use reth_network::NetworkEventListenerProvider;
15use reth_node_api::FullNodeTypesAdapter;
16use reth_node_builder::{
17 Node, NodeComponents, NodeComponentsBuilder, NodeTypes, NodeTypesWithDBAdapter,
18};
19use reth_node_core::{
20 args::{DatabaseArgs, DatadirArgs},
21 dirs::{ChainPath, DataDirPath},
22};
23use reth_provider::{
24 providers::{BlockchainProvider, NodeTypesForProvider, StaticFileProvider},
25 ProviderFactory, StaticFileProviderFactory,
26};
27use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
28use reth_static_file::StaticFileProducer;
29use std::{path::PathBuf, sync::Arc};
30use tokio::sync::watch;
31use tracing::{debug, info, warn};
32
33#[derive(Debug, Parser)]
35pub struct EnvironmentArgs<C: ChainSpecParser> {
36 #[command(flatten)]
38 pub datadir: DatadirArgs,
39
40 #[arg(long, value_name = "FILE")]
42 pub config: Option<PathBuf>,
43
44 #[arg(
48 long,
49 value_name = "CHAIN_OR_PATH",
50 long_help = C::help_message(),
51 default_value = C::default_value(),
52 value_parser = C::parser(),
53 global = true
54 )]
55 pub chain: Arc<C::ChainSpec>,
56
57 #[command(flatten)]
59 pub db: DatabaseArgs,
60}
61
62impl<C: ChainSpecParser> EnvironmentArgs<C> {
63 pub fn init<N: CliNodeTypes>(&self, access: AccessRights) -> eyre::Result<Environment<N>>
66 where
67 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
68 {
69 let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
70 let db_path = data_dir.db();
71 let sf_path = data_dir.static_files();
72
73 if access.is_read_write() {
74 reth_fs_util::create_dir_all(&db_path)?;
75 reth_fs_util::create_dir_all(&sf_path)?;
76 }
77
78 let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
79
80 let mut config = Config::from_path(config_path)
81 .inspect_err(
82 |err| warn!(target: "reth::cli", %err, "Failed to load config file, using default"),
83 )
84 .unwrap_or_default();
85
86 if config.stages.etl.dir.is_none() {
88 config.stages.etl.dir = Some(EtlConfig::from_datadir(data_dir.data_dir()));
89 }
90 if config.stages.era.folder.is_none() {
91 config.stages.era = config.stages.era.with_datadir(data_dir.data_dir());
92 }
93
94 info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
95 let (db, sfp) = match access {
96 AccessRights::RW => (
97 Arc::new(init_db(db_path, self.db.database_args())?),
98 StaticFileProvider::read_write(sf_path)?,
99 ),
100 AccessRights::RO => (
101 Arc::new(open_db_read_only(&db_path, self.db.database_args())?),
102 StaticFileProvider::read_only(sf_path, false)?,
103 ),
104 };
105
106 let provider_factory = self.create_provider_factory(&config, db, sfp)?;
107 if access.is_read_write() {
108 debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
109 init_genesis(&provider_factory)?;
110 }
111
112 Ok(Environment { config, provider_factory, data_dir })
113 }
114
115 fn create_provider_factory<N: CliNodeTypes>(
121 &self,
122 config: &Config,
123 db: Arc<DatabaseEnv>,
124 static_file_provider: StaticFileProvider<N::Primitives>,
125 ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
126 where
127 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
128 {
129 let has_receipt_pruning = config.prune.has_receipts_pruning();
130 let prune_modes = config.prune.segments.clone();
131 let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::new(
132 db,
133 self.chain.clone(),
134 static_file_provider,
135 )
136 .with_prune_modes(prune_modes.clone());
137
138 if let Some(unwind_target) = factory
140 .static_file_provider()
141 .check_consistency(&factory.provider()?, has_receipt_pruning)?
142 {
143 if factory.db_ref().is_read_only()? {
144 warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
145 return Ok(factory)
146 }
147
148 assert_ne!(
151 unwind_target,
152 PipelineTarget::Unwind(0),
153 "A static file <> database inconsistency was found that would trigger an unwind to block 0"
154 );
155
156 info!(target: "reth::cli", unwind_target = %unwind_target, "Executing an unwind after a failed storage consistency check.");
157
158 let (_tip_tx, tip_rx) = watch::channel(B256::ZERO);
159
160 let mut pipeline = Pipeline::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::builder()
162 .add_stages(DefaultStages::new(
163 factory.clone(),
164 tip_rx,
165 Arc::new(NoopConsensus::default()),
166 NoopHeaderDownloader::default(),
167 NoopBodiesDownloader::default(),
168 NoopEvmConfig::<N::Evm>::default(),
169 config.stages.clone(),
170 prune_modes.clone(),
171 None,
172 ))
173 .build(factory.clone(), StaticFileProducer::new(factory.clone(), prune_modes));
174
175 pipeline.move_to_static_files()?;
177 pipeline.unwind(unwind_target.unwind_target().expect("should exist"), None)?;
178 }
179
180 Ok(factory)
181 }
182}
183
184#[derive(Debug)]
186pub struct Environment<N: NodeTypes> {
187 pub config: Config,
189 pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>,
191 pub data_dir: ChainPath<DataDirPath>,
193}
194
195#[derive(Debug, Copy, Clone)]
197pub enum AccessRights {
198 RW,
200 RO,
202}
203
204impl AccessRights {
205 pub const fn is_read_write(&self) -> bool {
207 matches!(self, Self::RW)
208 }
209}
210
211type FullTypesAdapter<T> = FullNodeTypesAdapter<
213 T,
214 Arc<DatabaseEnv>,
215 BlockchainProvider<NodeTypesWithDBAdapter<T, Arc<DatabaseEnv>>>,
216>;
217
218pub trait CliHeader {
220 fn set_number(&mut self, number: u64);
221}
222
223impl CliHeader for alloy_consensus::Header {
224 fn set_number(&mut self, number: u64) {
225 self.number = number;
226 }
227}
228
229pub trait CliNodeTypes: Node<FullTypesAdapter<Self>> + NodeTypesForProvider {
232 type Evm: ConfigureEvm<Primitives = Self::Primitives>;
233 type NetworkPrimitives: NetPrimitivesFor<Self::Primitives>;
234}
235
236impl<N> CliNodeTypes for N
237where
238 N: Node<FullTypesAdapter<Self>> + NodeTypesForProvider,
239{
240 type Evm = <<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Evm;
241 type NetworkPrimitives = <<<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Network as NetworkEventListenerProvider>::Primitives;
242}
243
244type EvmFor<N> = <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
245 FullTypesAdapter<N>,
246>>::Components as NodeComponents<FullTypesAdapter<N>>>::Evm;
247
248type ConsensusFor<N> =
249 <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
250 FullTypesAdapter<N>,
251 >>::Components as NodeComponents<FullTypesAdapter<N>>>::Consensus;
252
253pub trait CliNodeComponents<N: CliNodeTypes>: Send + Sync + 'static {
255 fn evm_config(&self) -> &EvmFor<N>;
257 fn consensus(&self) -> &ConsensusFor<N>;
259}
260
261impl<N: CliNodeTypes> CliNodeComponents<N> for (EvmFor<N>, ConsensusFor<N>) {
262 fn evm_config(&self) -> &EvmFor<N> {
263 &self.0
264 }
265
266 fn consensus(&self) -> &ConsensusFor<N> {
267 &self.1
268 }
269}
270
271pub trait CliComponentsBuilder<N: CliNodeTypes>:
273 FnOnce(Arc<N::ChainSpec>) -> Self::Components + Send + Sync + 'static
274{
275 type Components: CliNodeComponents<N>;
276}
277
278impl<N: CliNodeTypes, F, Comp> CliComponentsBuilder<N> for F
279where
280 F: FnOnce(Arc<N::ChainSpec>) -> Comp + Send + Sync + 'static,
281 Comp: CliNodeComponents<N>,
282{
283 type Components = Comp;
284}