Skip to main content

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, StorageArgs},
23    dirs::{ChainPath, DataDirPath},
24};
25use reth_provider::{
26    providers::{
27        BlockchainProvider, NodeTypesForProvider, RocksDBProvider, StaticFileProvider,
28        StaticFileProviderBuilder,
29    },
30    BalConfig, BalStoreHandle, InMemoryBalStore, ProviderFactory, StaticFileProviderFactory,
31    StorageSettings,
32};
33use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
34use reth_static_file::StaticFileProducer;
35use std::{path::PathBuf, sync::Arc};
36use tokio::sync::watch;
37use tracing::{debug, info, warn};
38
39/// Struct to hold config and datadir paths
40#[derive(Debug, Parser)]
41pub struct EnvironmentArgs<C: ChainSpecParser> {
42    /// Parameters for datadir configuration
43    #[command(flatten)]
44    pub datadir: DatadirArgs,
45
46    /// The path to the configuration file to use.
47    #[arg(long, value_name = "FILE")]
48    pub config: Option<PathBuf>,
49
50    /// The chain this node is running.
51    ///
52    /// Possible values are either a built-in chain or the path to a chain specification file.
53    #[arg(
54        long,
55        value_name = "CHAIN_OR_PATH",
56        long_help = C::help_message(),
57        default_value = C::default_value(),
58        value_parser = C::parser(),
59        global = true
60    )]
61    pub chain: Arc<C::ChainSpec>,
62
63    /// All database related arguments
64    #[command(flatten)]
65    pub db: DatabaseArgs,
66
67    /// All static files related arguments
68    #[command(flatten)]
69    pub static_files: StaticFilesArgs,
70
71    /// Storage mode configuration (v2 vs v1/legacy)
72    #[command(flatten)]
73    pub storage: StorageArgs,
74}
75
76impl<C: ChainSpecParser> EnvironmentArgs<C> {
77    /// Returns the storage settings for new database initialization.
78    ///
79    /// Determined by the `--storage.v2` flag (defaults to `true`).
80    /// Existing databases retain whatever settings are persisted in their
81    /// metadata (checked during genesis init).
82    pub fn storage_settings(&self) -> StorageSettings {
83        if self.storage.v2 {
84            StorageSettings::v2()
85        } else {
86            StorageSettings::v1()
87        }
88    }
89
90    /// Initializes environment according to [`AccessRights`] and returns an instance of
91    /// [`Environment`].
92    ///
93    /// The provided `runtime` is used for parallel storage I/O.
94    pub fn init<N: CliNodeTypes>(
95        &self,
96        access: AccessRights,
97        runtime: reth_tasks::Runtime,
98    ) -> eyre::Result<Environment<N>>
99    where
100        C: ChainSpecParser<ChainSpec = N::ChainSpec>,
101    {
102        let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
103        let db_path = data_dir.db();
104        let sf_path = data_dir.static_files();
105        let rocksdb_path = data_dir.rocksdb();
106
107        if access.is_read_write() {
108            reth_fs_util::create_dir_all(&db_path)?;
109            reth_fs_util::create_dir_all(&sf_path)?;
110            reth_fs_util::create_dir_all(&rocksdb_path)?;
111        }
112
113        let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
114
115        let mut config = Config::from_path(config_path)
116            .inspect_err(
117                |err| warn!(target: "reth::cli", %err, "Failed to load config file, using default"),
118            )
119            .unwrap_or_default();
120
121        // Make sure ETL doesn't default to /tmp/, but to whatever datadir is set to
122        if config.stages.etl.dir.is_none() {
123            config.stages.etl.dir = Some(EtlConfig::from_datadir(data_dir.data_dir()));
124        }
125        if config.stages.era.folder.is_none() {
126            config.stages.era = config.stages.era.with_datadir(data_dir.data_dir());
127        }
128
129        info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
130        let genesis_block_number = self.chain.genesis().number.unwrap_or_default();
131        let (db, sfp) = match access {
132            AccessRights::RW => (
133                init_db(db_path, self.db.database_args())?,
134                StaticFileProviderBuilder::read_write(sf_path)
135                    .with_metrics()
136                    .with_genesis_block_number(genesis_block_number)
137                    .build()?,
138            ),
139            AccessRights::RO | AccessRights::RoInconsistent => (
140                open_db_read_only(&db_path, self.db.database_args())?,
141                StaticFileProviderBuilder::read_only(sf_path)
142                    .with_metrics()
143                    .with_genesis_block_number(genesis_block_number)
144                    .build()?,
145            ),
146        };
147        let rocksdb_provider = if !access.is_read_write() && !RocksDBProvider::exists(&rocksdb_path)
148        {
149            // RocksDB database doesn't exist yet (e.g. datadir restored from a snapshot
150            // or created before RocksDB storage). Create an empty one so read-only
151            // commands can proceed.
152            debug!(target: "reth::cli", ?rocksdb_path, "RocksDB not found, initializing empty database");
153            reth_fs_util::create_dir_all(&rocksdb_path)?;
154            let mut builder = RocksDBProvider::builder(data_dir.rocksdb())
155                .with_default_tables()
156                .with_database_log_level(self.db.log_level);
157            if let Some(cache_size) = self.db.rocksdb_block_cache_size {
158                builder = builder.with_block_cache_size(cache_size);
159            }
160            builder.build()?
161        } else {
162            let mut builder = RocksDBProvider::builder(data_dir.rocksdb())
163                .with_default_tables()
164                .with_database_log_level(self.db.log_level)
165                .with_read_only(!access.is_read_write());
166            if let Some(cache_size) = self.db.rocksdb_block_cache_size {
167                builder = builder.with_block_cache_size(cache_size);
168            }
169            builder.build()?
170        };
171
172        let provider_factory =
173            self.create_provider_factory(&config, db, sfp, rocksdb_provider, access, runtime)?;
174        if access.is_read_write() {
175            debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
176            init_genesis_with_settings(&provider_factory, self.storage_settings())?;
177        }
178
179        Ok(Environment { config, provider_factory, data_dir })
180    }
181
182    /// Returns a [`ProviderFactory`] after executing consistency checks.
183    ///
184    /// If it's a read-write environment and an issue is found, it will attempt to heal (including a
185    /// pipeline unwind). Otherwise, it will print out a warning, advising the user to restart the
186    /// node to heal.
187    fn create_provider_factory<N: CliNodeTypes>(
188        &self,
189        config: &Config,
190        db: DatabaseEnv,
191        static_file_provider: StaticFileProvider<N::Primitives>,
192        rocksdb_provider: RocksDBProvider,
193        access: AccessRights,
194        runtime: reth_tasks::Runtime,
195    ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>>>
196    where
197        C: ChainSpecParser<ChainSpec = N::ChainSpec>,
198    {
199        let balstore_cache_size =
200            self.db.balstore_cache_size.unwrap_or(BalConfig::DEFAULT_IN_MEMORY_RETENTION_DISTANCE);
201        let bal_store = BalStoreHandle::new(InMemoryBalStore::new(
202            BalConfig::with_in_memory_retention_distance(balstore_cache_size),
203        ));
204        let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, DatabaseEnv>>::new(
205            db,
206            self.chain.clone(),
207            static_file_provider,
208            rocksdb_provider,
209            runtime,
210        )?
211        .with_prune_modes(config.prune.segments.clone())
212        .with_minimum_pruning_distance(config.prune.minimum_pruning_distance)
213        .with_bal_store(bal_store);
214
215        // Check for consistency between database and static files.
216        if !access.is_read_only_inconsistent() &&
217            let Some(unwind_target) =
218                factory.static_file_provider().check_consistency(&factory.provider()?)?
219        {
220            if factory.db_ref().is_read_only()? {
221                warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
222                return Ok(factory)
223            }
224
225            // Highly unlikely to happen, and given its destructive nature, it's better to panic
226            // instead.
227            assert_ne!(
228                unwind_target,
229                PipelineTarget::Unwind(0),
230                "A static file <> database inconsistency was found that would trigger an unwind to block 0"
231            );
232
233            info!(target: "reth::cli", unwind_target = %unwind_target, "Executing an unwind after a failed storage consistency check.");
234
235            let (_tip_tx, tip_rx) = watch::channel(B256::ZERO);
236
237            // Builds and executes an unwind-only pipeline
238            let mut pipeline = Pipeline::<NodeTypesWithDBAdapter<N, DatabaseEnv>>::builder()
239                .add_stages(DefaultStages::new(
240                    factory.clone(),
241                    tip_rx,
242                    Arc::new(NoopConsensus::default()),
243                    NoopHeaderDownloader::default(),
244                    NoopBodiesDownloader::default(),
245                    NoopEvmConfig::<N::Evm>::default(),
246                    config.stages.clone(),
247                    config.prune.segments.clone(),
248                    None,
249                ))
250                .build(
251                    factory.clone(),
252                    StaticFileProducer::new(factory.clone(), config.prune.segments.clone()),
253                );
254
255            // Move all applicable data from database to static files.
256            pipeline.move_to_static_files()?;
257            pipeline.unwind(unwind_target.unwind_target().expect("should exist"), None)?;
258        }
259
260        Ok(factory)
261    }
262}
263
264/// Environment built from [`EnvironmentArgs`].
265#[derive(Debug)]
266pub struct Environment<N: NodeTypes> {
267    /// Configuration for reth node
268    pub config: Config,
269    /// Provider factory.
270    pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>>,
271    /// Datadir path.
272    pub data_dir: ChainPath<DataDirPath>,
273}
274
275/// Environment access rights.
276#[derive(Debug, Copy, Clone)]
277pub enum AccessRights {
278    /// Read-write access
279    RW,
280    /// Read-only access
281    RO,
282    /// Read-only access with possibly inconsistent data
283    RoInconsistent,
284}
285
286impl AccessRights {
287    /// Returns `true` if it requires read-write access to the environment.
288    pub const fn is_read_write(&self) -> bool {
289        matches!(self, Self::RW)
290    }
291
292    /// Returns `true` if it requires read-only access to the environment with possibly inconsistent
293    /// data.
294    pub const fn is_read_only_inconsistent(&self) -> bool {
295        matches!(self, Self::RoInconsistent)
296    }
297}
298
299/// Helper alias to satisfy `FullNodeTypes` bound on [`Node`] trait generic.
300type FullTypesAdapter<T> = FullNodeTypesAdapter<
301    T,
302    DatabaseEnv,
303    BlockchainProvider<NodeTypesWithDBAdapter<T, DatabaseEnv>>,
304>;
305
306/// Helper trait with a common set of requirements for the
307/// [`NodeTypes`] in CLI.
308pub trait CliNodeTypes: Node<FullTypesAdapter<Self>> + NodeTypesForProvider {
309    type Evm: ConfigureEvm<Primitives = Self::Primitives>;
310    type NetworkPrimitives: NetPrimitivesFor<Self::Primitives>;
311}
312
313impl<N> CliNodeTypes for N
314where
315    N: Node<FullTypesAdapter<Self>> + NodeTypesForProvider,
316{
317    type Evm = <<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Evm;
318    type NetworkPrimitives = <<<N::ComponentsBuilder as NodeComponentsBuilder<FullTypesAdapter<Self>>>::Components as NodeComponents<FullTypesAdapter<Self>>>::Network as NetworkEventListenerProvider>::Primitives;
319}
320
321type EvmFor<N> = <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
322    FullTypesAdapter<N>,
323>>::Components as NodeComponents<FullTypesAdapter<N>>>::Evm;
324
325type ConsensusFor<N> =
326    <<<N as Node<FullTypesAdapter<N>>>::ComponentsBuilder as NodeComponentsBuilder<
327        FullTypesAdapter<N>,
328    >>::Components as NodeComponents<FullTypesAdapter<N>>>::Consensus;
329
330/// Helper trait aggregating components required for the CLI.
331pub trait CliNodeComponents<N: CliNodeTypes>: Send + Sync + 'static {
332    /// Returns the configured EVM.
333    fn evm_config(&self) -> &EvmFor<N>;
334    /// Returns the consensus implementation.
335    fn consensus(&self) -> &ConsensusFor<N>;
336}
337
338impl<N: CliNodeTypes> CliNodeComponents<N> for (EvmFor<N>, ConsensusFor<N>) {
339    fn evm_config(&self) -> &EvmFor<N> {
340        &self.0
341    }
342
343    fn consensus(&self) -> &ConsensusFor<N> {
344        &self.1
345    }
346}
347
348/// Helper trait alias for an [`FnOnce`] producing [`CliNodeComponents`].
349pub trait CliComponentsBuilder<N: CliNodeTypes>:
350    FnOnce(Arc<N::ChainSpec>) -> Self::Components + Send + Sync + 'static
351{
352    type Components: CliNodeComponents<N>;
353}
354
355impl<N: CliNodeTypes, F, Comp> CliComponentsBuilder<N> for F
356where
357    F: FnOnce(Arc<N::ChainSpec>) -> Comp + Send + Sync + 'static,
358    Comp: CliNodeComponents<N>,
359{
360    type Components = Comp;
361}