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::{execute::BlockExecutorProvider, noop::NoopBlockExecutorProvider};
13use reth_node_builder::{NodeTypesWithDBAdapter, NodeTypesWithEngine};
14use reth_node_core::{
15    args::{DatabaseArgs, DatadirArgs},
16    dirs::{ChainPath, DataDirPath},
17};
18use reth_provider::{
19    providers::{NodeTypesForProvider, StaticFileProvider},
20    ProviderFactory, StaticFileProviderFactory,
21};
22use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
23use reth_static_file::StaticFileProducer;
24use std::{path::PathBuf, sync::Arc};
25use tokio::sync::watch;
26use tracing::{debug, info, warn};
27
28/// Struct to hold config and datadir paths
29#[derive(Debug, Parser)]
30pub struct EnvironmentArgs<C: ChainSpecParser> {
31    /// Parameters for datadir configuration
32    #[command(flatten)]
33    pub datadir: DatadirArgs,
34
35    /// The path to the configuration file to use.
36    #[arg(long, value_name = "FILE")]
37    pub config: Option<PathBuf>,
38
39    /// The chain this node is running.
40    ///
41    /// Possible values are either a built-in chain or the path to a chain specification file.
42    #[arg(
43        long,
44        value_name = "CHAIN_OR_PATH",
45        long_help = C::help_message(),
46        default_value = C::SUPPORTED_CHAINS[0],
47        value_parser = C::parser()
48    )]
49    pub chain: Arc<C::ChainSpec>,
50
51    /// All database related arguments
52    #[command(flatten)]
53    pub db: DatabaseArgs,
54}
55
56impl<C: ChainSpecParser> EnvironmentArgs<C> {
57    /// Initializes environment according to [`AccessRights`] and returns an instance of
58    /// [`Environment`].
59    pub fn init<N: CliNodeTypes>(&self, access: AccessRights) -> eyre::Result<Environment<N>>
60    where
61        C: ChainSpecParser<ChainSpec = N::ChainSpec>,
62    {
63        let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
64        let db_path = data_dir.db();
65        let sf_path = data_dir.static_files();
66
67        if access.is_read_write() {
68            reth_fs_util::create_dir_all(&db_path)?;
69            reth_fs_util::create_dir_all(&sf_path)?;
70        }
71
72        let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
73
74        let mut config = Config::from_path(config_path)
75            .inspect_err(
76                |err| warn!(target: "reth::cli", %err, "Failed to load config file, using default"),
77            )
78            .unwrap_or_default();
79
80        // Make sure ETL doesn't default to /tmp/, but to whatever datadir is set to
81        if config.stages.etl.dir.is_none() {
82            config.stages.etl.dir = Some(EtlConfig::from_datadir(data_dir.data_dir()));
83        }
84
85        info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
86        let (db, sfp) = match access {
87            AccessRights::RW => (
88                Arc::new(init_db(db_path, self.db.database_args())?),
89                StaticFileProvider::read_write(sf_path)?,
90            ),
91            AccessRights::RO => (
92                Arc::new(open_db_read_only(&db_path, self.db.database_args())?),
93                StaticFileProvider::read_only(sf_path, false)?,
94            ),
95        };
96
97        let provider_factory = self.create_provider_factory(&config, db, sfp)?;
98        if access.is_read_write() {
99            debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
100            init_genesis(&provider_factory)?;
101        }
102
103        Ok(Environment { config, provider_factory, data_dir })
104    }
105
106    /// Returns a [`ProviderFactory`] after executing consistency checks.
107    ///
108    /// If it's a read-write environment and an issue is found, it will attempt to heal (including a
109    /// pipeline unwind). Otherwise, it will print out a warning, advising the user to restart the
110    /// node to heal.
111    fn create_provider_factory<N: CliNodeTypes>(
112        &self,
113        config: &Config,
114        db: Arc<DatabaseEnv>,
115        static_file_provider: StaticFileProvider<N::Primitives>,
116    ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
117    where
118        C: ChainSpecParser<ChainSpec = N::ChainSpec>,
119    {
120        let has_receipt_pruning = config.prune.as_ref().is_some_and(|a| a.has_receipts_pruning());
121        let prune_modes =
122            config.prune.as_ref().map(|prune| prune.segments.clone()).unwrap_or_default();
123        let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::new(
124            db,
125            self.chain.clone(),
126            static_file_provider,
127        )
128        .with_prune_modes(prune_modes.clone());
129
130        // Check for consistency between database and static files.
131        if let Some(unwind_target) = factory
132            .static_file_provider()
133            .check_consistency(&factory.provider()?, has_receipt_pruning)?
134        {
135            if factory.db_ref().is_read_only()? {
136                warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
137                return Ok(factory)
138            }
139
140            // Highly unlikely to happen, and given its destructive nature, it's better to panic
141            // instead.
142            assert_ne!(unwind_target, PipelineTarget::Unwind(0), "A static file <> database inconsistency was found that would trigger an unwind to block 0");
143
144            info!(target: "reth::cli", unwind_target = %unwind_target, "Executing an unwind after a failed storage consistency check.");
145
146            let (_tip_tx, tip_rx) = watch::channel(B256::ZERO);
147
148            // Builds and executes an unwind-only pipeline
149            let mut pipeline = Pipeline::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::builder()
150                .add_stages(DefaultStages::new(
151                    factory.clone(),
152                    tip_rx,
153                    Arc::new(NoopConsensus::default()),
154                    NoopHeaderDownloader::default(),
155                    NoopBodiesDownloader::default(),
156                    NoopBlockExecutorProvider::<N::Primitives>::default(),
157                    config.stages.clone(),
158                    prune_modes.clone(),
159                ))
160                .build(factory.clone(), StaticFileProducer::new(factory.clone(), prune_modes));
161
162            // Move all applicable data from database to static files.
163            pipeline.move_to_static_files()?;
164            pipeline.unwind(unwind_target.unwind_target().expect("should exist"), None)?;
165        }
166
167        Ok(factory)
168    }
169}
170
171/// Environment built from [`EnvironmentArgs`].
172#[derive(Debug)]
173pub struct Environment<N: NodeTypesWithEngine> {
174    /// Configuration for reth node
175    pub config: Config,
176    /// Provider factory.
177    pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>,
178    /// Datadir path.
179    pub data_dir: ChainPath<DataDirPath>,
180}
181
182/// Environment access rights.
183#[derive(Debug, Copy, Clone)]
184pub enum AccessRights {
185    /// Read-write access
186    RW,
187    /// Read-only access
188    RO,
189}
190
191impl AccessRights {
192    /// Returns `true` if it requires read-write access to the environment.
193    pub const fn is_read_write(&self) -> bool {
194        matches!(self, Self::RW)
195    }
196}
197
198/// Helper trait with a common set of requirements for the
199/// [`NodeTypes`](reth_node_builder::NodeTypes) in CLI.
200pub trait CliNodeTypes: NodeTypesWithEngine + NodeTypesForProvider {}
201impl<N> CliNodeTypes for N where N: NodeTypesWithEngine + NodeTypesForProvider {}
202
203/// Helper trait aggregating components required for the CLI.
204pub trait CliNodeComponents<N: CliNodeTypes> {
205    /// Block executor.
206    type Executor: BlockExecutorProvider<Primitives = N::Primitives>;
207    /// Consensus implementation.
208    type Consensus: FullConsensus<N::Primitives, Error = ConsensusError> + Clone + 'static;
209
210    /// Returns the block executor.
211    fn executor(&self) -> &Self::Executor;
212    /// Returns the consensus implementation.
213    fn consensus(&self) -> &Self::Consensus;
214}
215
216impl<N: CliNodeTypes, E, C> CliNodeComponents<N> for (E, C)
217where
218    E: BlockExecutorProvider<Primitives = N::Primitives>,
219    C: FullConsensus<N::Primitives, Error = ConsensusError> + Clone + 'static,
220{
221    type Executor = E;
222    type Consensus = C;
223
224    fn executor(&self) -> &Self::Executor {
225        &self.0
226    }
227
228    fn consensus(&self) -> &Self::Consensus {
229        &self.1
230    }
231}