reth_cli_commands/
common.rs

1//! Contains common `reth` arguments
2
3pub 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},
23    dirs::{ChainPath, DataDirPath},
24};
25use reth_provider::{
26    providers::{BlockchainProvider, NodeTypesForProvider, StaticFileProvider},
27    ProviderFactory, StaticFileProviderFactory,
28};
29use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
30use reth_static_file::StaticFileProducer;
31use std::{path::PathBuf, sync::Arc};
32use tokio::sync::watch;
33use tracing::{debug, info, warn};
34
35/// Struct to hold config and datadir paths
36#[derive(Debug, Parser)]
37pub struct EnvironmentArgs<C: ChainSpecParser> {
38    /// Parameters for datadir configuration
39    #[command(flatten)]
40    pub datadir: DatadirArgs,
41
42    /// The path to the configuration file to use.
43    #[arg(long, value_name = "FILE")]
44    pub config: Option<PathBuf>,
45
46    /// The chain this node is running.
47    ///
48    /// Possible values are either a built-in chain or the path to a chain specification file.
49    #[arg(
50        long,
51        value_name = "CHAIN_OR_PATH",
52        long_help = C::help_message(),
53        default_value = C::default_value(),
54        value_parser = C::parser(),
55        global = true
56    )]
57    pub chain: Arc<C::ChainSpec>,
58
59    /// All database related arguments
60    #[command(flatten)]
61    pub db: DatabaseArgs,
62
63    /// All static files related arguments
64    #[command(flatten)]
65    pub static_files: StaticFilesArgs,
66}
67
68impl<C: ChainSpecParser> EnvironmentArgs<C> {
69    /// Initializes environment according to [`AccessRights`] and returns an instance of
70    /// [`Environment`].
71    pub fn init<N: CliNodeTypes>(&self, access: AccessRights) -> eyre::Result<Environment<N>>
72    where
73        C: ChainSpecParser<ChainSpec = N::ChainSpec>,
74    {
75        let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
76        let db_path = data_dir.db();
77        let sf_path = data_dir.static_files();
78
79        if access.is_read_write() {
80            reth_fs_util::create_dir_all(&db_path)?;
81            reth_fs_util::create_dir_all(&sf_path)?;
82        }
83
84        let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
85
86        let mut config = Config::from_path(config_path)
87            .inspect_err(
88                |err| warn!(target: "reth::cli", %err, "Failed to load config file, using default"),
89            )
90            .unwrap_or_default();
91
92        // Make sure ETL doesn't default to /tmp/, but to whatever datadir is set to
93        if config.stages.etl.dir.is_none() {
94            config.stages.etl.dir = Some(EtlConfig::from_datadir(data_dir.data_dir()));
95        }
96        if config.stages.era.folder.is_none() {
97            config.stages.era = config.stages.era.with_datadir(data_dir.data_dir());
98        }
99
100        info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
101        let (db, sfp) = match access {
102            AccessRights::RW => (
103                Arc::new(init_db(db_path, self.db.database_args())?),
104                StaticFileProvider::read_write(sf_path)?,
105            ),
106            AccessRights::RO | AccessRights::RoInconsistent => (
107                Arc::new(open_db_read_only(&db_path, self.db.database_args())?),
108                StaticFileProvider::read_only(sf_path, false)?,
109            ),
110        };
111
112        let provider_factory = self.create_provider_factory(&config, db, sfp, access)?;
113        if access.is_read_write() {
114            debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
115            init_genesis_with_settings(&provider_factory, self.static_files.to_settings())?;
116        }
117
118        Ok(Environment { config, provider_factory, data_dir })
119    }
120
121    /// Returns a [`ProviderFactory`] after executing consistency checks.
122    ///
123    /// If it's a read-write environment and an issue is found, it will attempt to heal (including a
124    /// pipeline unwind). Otherwise, it will print out a warning, advising the user to restart the
125    /// node to heal.
126    fn create_provider_factory<N: CliNodeTypes>(
127        &self,
128        config: &Config,
129        db: Arc<DatabaseEnv>,
130        static_file_provider: StaticFileProvider<N::Primitives>,
131        access: AccessRights,
132    ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
133    where
134        C: ChainSpecParser<ChainSpec = N::ChainSpec>,
135    {
136        let prune_modes = config.prune.segments.clone();
137        let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::new(
138            db,
139            self.chain.clone(),
140            static_file_provider,
141        )?
142        .with_prune_modes(prune_modes.clone());
143
144        // Check for consistency between database and static files.
145        if !access.is_read_only_inconsistent() &&
146            let Some(unwind_target) =
147                factory.static_file_provider().check_consistency(&factory.provider()?)?
148        {
149            if factory.db_ref().is_read_only()? {
150                warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
151                return Ok(factory)
152            }
153
154            // Highly unlikely to happen, and given its destructive nature, it's better to panic
155            // instead.
156            assert_ne!(
157                unwind_target,
158                PipelineTarget::Unwind(0),
159                "A static file <> database inconsistency was found that would trigger an unwind to block 0"
160            );
161
162            info!(target: "reth::cli", unwind_target = %unwind_target, "Executing an unwind after a failed storage consistency check.");
163
164            let (_tip_tx, tip_rx) = watch::channel(B256::ZERO);
165
166            // Builds and executes an unwind-only pipeline
167            let mut pipeline = Pipeline::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::builder()
168                .add_stages(DefaultStages::new(
169                    factory.clone(),
170                    tip_rx,
171                    Arc::new(NoopConsensus::default()),
172                    NoopHeaderDownloader::default(),
173                    NoopBodiesDownloader::default(),
174                    NoopEvmConfig::<N::Evm>::default(),
175                    config.stages.clone(),
176                    prune_modes.clone(),
177                    None,
178                ))
179                .build(factory.clone(), StaticFileProducer::new(factory.clone(), prune_modes));
180
181            // Move all applicable data from database to static files.
182            pipeline.move_to_static_files()?;
183            pipeline.unwind(unwind_target.unwind_target().expect("should exist"), None)?;
184        }
185
186        Ok(factory)
187    }
188}
189
190/// Environment built from [`EnvironmentArgs`].
191#[derive(Debug)]
192pub struct Environment<N: NodeTypes> {
193    /// Configuration for reth node
194    pub config: Config,
195    /// Provider factory.
196    pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>,
197    /// Datadir path.
198    pub data_dir: ChainPath<DataDirPath>,
199}
200
201/// Environment access rights.
202#[derive(Debug, Copy, Clone)]
203pub enum AccessRights {
204    /// Read-write access
205    RW,
206    /// Read-only access
207    RO,
208    /// Read-only access with possibly inconsistent data
209    RoInconsistent,
210}
211
212impl AccessRights {
213    /// Returns `true` if it requires read-write access to the environment.
214    pub const fn is_read_write(&self) -> bool {
215        matches!(self, Self::RW)
216    }
217
218    /// Returns `true` if it requires read-only access to the environment with possibly inconsistent
219    /// data.
220    pub const fn is_read_only_inconsistent(&self) -> bool {
221        matches!(self, Self::RoInconsistent)
222    }
223}
224
225/// Helper alias to satisfy `FullNodeTypes` bound on [`Node`] trait generic.
226type FullTypesAdapter<T> = FullNodeTypesAdapter<
227    T,
228    Arc<DatabaseEnv>,
229    BlockchainProvider<NodeTypesWithDBAdapter<T, Arc<DatabaseEnv>>>,
230>;
231
232/// Helper trait with a common set of requirements for the
233/// [`NodeTypes`] in CLI.
234pub trait CliNodeTypes: Node<FullTypesAdapter<Self>> + NodeTypesForProvider {
235    type Evm: ConfigureEvm<Primitives = Self::Primitives>;
236    type NetworkPrimitives: NetPrimitivesFor<Self::Primitives>;
237}
238
239impl<N> CliNodeTypes for N
240where
241    N: Node<FullTypesAdapter<Self>> + NodeTypesForProvider,
242{
243    type Evm = <<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Evm;
244    type NetworkPrimitives = <<<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Network as NetworkEventListenerProvider>::Primitives;
245}
246
247type EvmFor<N> = <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
248    FullTypesAdapter<N>,
249>>::Components as NodeComponents<FullTypesAdapter<N>>>::Evm;
250
251type ConsensusFor<N> =
252    <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
253        FullTypesAdapter<N>,
254    >>::Components as NodeComponents<FullTypesAdapter<N>>>::Consensus;
255
256/// Helper trait aggregating components required for the CLI.
257pub trait CliNodeComponents<N: CliNodeTypes>: Send + Sync + 'static {
258    /// Returns the configured EVM.
259    fn evm_config(&self) -> &EvmFor<N>;
260    /// Returns the consensus implementation.
261    fn consensus(&self) -> &ConsensusFor<N>;
262}
263
264impl<N: CliNodeTypes> CliNodeComponents<N> for (EvmFor<N>, ConsensusFor<N>) {
265    fn evm_config(&self) -> &EvmFor<N> {
266        &self.0
267    }
268
269    fn consensus(&self) -> &ConsensusFor<N> {
270        &self.1
271    }
272}
273
274/// Helper trait alias for an [`FnOnce`] producing [`CliNodeComponents`].
275pub trait CliComponentsBuilder<N: CliNodeTypes>:
276    FnOnce(Arc<N::ChainSpec>) -> Self::Components + Send + Sync + 'static
277{
278    type Components: CliNodeComponents<N>;
279}
280
281impl<N: CliNodeTypes, F, Comp> CliComponentsBuilder<N> for F
282where
283    F: FnOnce(Arc<N::ChainSpec>) -> Comp + Send + Sync + 'static,
284    Comp: CliNodeComponents<N>,
285{
286    type Components = Comp;
287}