reth_cli_commands/
common.rs

1//! Contains common `reth` arguments
2
3use 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/// Struct to hold config and datadir paths
32#[derive(Debug, Parser)]
33pub struct EnvironmentArgs<C: ChainSpecParser> {
34    /// Parameters for datadir configuration
35    #[command(flatten)]
36    pub datadir: DatadirArgs,
37
38    /// The path to the configuration file to use.
39    #[arg(long, value_name = "FILE")]
40    pub config: Option<PathBuf>,
41
42    /// The chain this node is running.
43    ///
44    /// Possible values are either a built-in chain or the path to a chain specification file.
45    #[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    /// All database related arguments
56    #[command(flatten)]
57    pub db: DatabaseArgs,
58}
59
60impl<C: ChainSpecParser> EnvironmentArgs<C> {
61    /// Initializes environment according to [`AccessRights`] and returns an instance of
62    /// [`Environment`].
63    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        // Make sure ETL doesn't default to /tmp/, but to whatever datadir is set to
85        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    /// Returns a [`ProviderFactory`] after executing consistency checks.
111    ///
112    /// If it's a read-write environment and an issue is found, it will attempt to heal (including a
113    /// pipeline unwind). Otherwise, it will print out a warning, advising the user to restart the
114    /// node to heal.
115    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        // Check for consistency between database and static files.
135        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            // Highly unlikely to happen, and given its destructive nature, it's better to panic
145            // instead.
146            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            // Builds and executes an unwind-only pipeline
157            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            // Move all applicable data from database to static files.
171            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/// Environment built from [`EnvironmentArgs`].
180#[derive(Debug)]
181pub struct Environment<N: NodeTypes> {
182    /// Configuration for reth node
183    pub config: Config,
184    /// Provider factory.
185    pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>,
186    /// Datadir path.
187    pub data_dir: ChainPath<DataDirPath>,
188}
189
190/// Environment access rights.
191#[derive(Debug, Copy, Clone)]
192pub enum AccessRights {
193    /// Read-write access
194    RW,
195    /// Read-only access
196    RO,
197}
198
199impl AccessRights {
200    /// Returns `true` if it requires read-write access to the environment.
201    pub const fn is_read_write(&self) -> bool {
202        matches!(self, Self::RW)
203    }
204}
205
206/// Helper alias to satisfy `FullNodeTypes` bound on [`Node`] trait generic.
207type FullTypesAdapter<T> = FullNodeTypesAdapter<
208    T,
209    Arc<DatabaseEnv>,
210    BlockchainProvider<NodeTypesWithDBAdapter<T, Arc<DatabaseEnv>>>,
211>;
212
213/// Helper trait with a common set of requirements for the
214/// [`NodeTypes`] in CLI.
215pub 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
226/// Helper trait aggregating components required for the CLI.
227pub trait CliNodeComponents<N: CliNodeTypes> {
228    /// Evm to use.
229    type Evm: ConfigureEvm<Primitives = N::Primitives> + 'static;
230    /// Consensus implementation.
231    type Consensus: FullConsensus<N::Primitives, Error = ConsensusError> + Clone + 'static;
232
233    /// Returns the configured EVM.
234    fn evm_config(&self) -> &Self::Evm;
235    /// Returns the consensus implementation.
236    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}