reth_seismic_cli/
lib.rs

1//! Seismic-Reth CLI implementation.
2
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6    issue_tracker_base_url = "https://github.com/SeismicSystems/seismic-reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
10
11/// Seismic chain specification parser.
12pub mod chainspec;
13
14use reth_chainspec::ChainSpec;
15use reth_cli_commands::{launcher::FnLauncher, node};
16use reth_seismic_node::args::EnclaveArgs;
17
18use std::{ffi::OsString, fmt, sync::Arc};
19
20use chainspec::SeismicChainSpecParser;
21use clap::{command, value_parser, Parser, Subcommand};
22use futures_util::Future;
23use reth_chainspec::EthChainSpec;
24use reth_cli::chainspec::ChainSpecParser;
25use reth_cli_runner::CliRunner;
26use reth_db::DatabaseEnv;
27use reth_node_builder::{NodeBuilder, WithLaunchContext};
28use reth_node_core::{
29    args::LogArgs,
30    version::{LONG_VERSION, SHORT_VERSION},
31};
32use reth_tracing::FileWorkerGuard;
33use tracing::info;
34
35// This allows us to manually enable node metrics features, required for proper jemalloc metric
36// reporting
37use reth_node_metrics as _;
38use reth_node_metrics::recorder::install_prometheus_recorder;
39
40/// The main seismic-reth cli interface.
41///
42/// This is the entrypoint to the executable.
43#[derive(Debug, Parser)]
44#[command(author, version = SHORT_VERSION, long_version = LONG_VERSION, about = "Reth", long_about = None)]
45pub struct Cli<
46    Spec: ChainSpecParser = SeismicChainSpecParser,
47    Ext: clap::Args + fmt::Debug = EnclaveArgs,
48> {
49    /// The command to run
50    #[command(subcommand)]
51    pub command: Commands<Spec, Ext>,
52
53    /// The chain this node is running.
54    ///
55    /// Possible values are either a built-in chain or the path to a chain specification file.
56    #[arg(
57        long,
58        value_name = "CHAIN_OR_PATH",
59        long_help = Spec::help_message(),
60        default_value = Spec::SUPPORTED_CHAINS[0],
61        value_parser = Spec::parser(),
62        global = true,
63    )]
64    pub chain: Arc<Spec::ChainSpec>,
65
66    /// Add a new instance of a node.
67    ///
68    /// Configures the ports of the node to avoid conflicts with the defaults.
69    /// This is useful for running multiple nodes on the same machine.
70    ///
71    /// Max number of instances is 200. It is chosen in a way so that it's not possible to have
72    /// port numbers that conflict with each other.
73    ///
74    /// Changes to the following port numbers:
75    /// - `DISCOVERY_PORT`: default + `instance` - 1
76    /// - `AUTH_PORT`: default + `instance` * 100 - 100
77    /// - `HTTP_RPC_PORT`: default - `instance` + 1
78    /// - `WS_RPC_PORT`: default + `instance` * 2 - 2
79    #[arg(long, value_name = "INSTANCE", global = true, default_value_t = 1, value_parser = value_parser!(u16).range(..=200))]
80    pub instance: u16,
81
82    /// The logging configuration for the CLI.
83    #[command(flatten)]
84    pub logs: LogArgs,
85}
86
87impl Cli {
88    /// Parsers only the default CLI arguments
89    pub fn parse_args() -> Self {
90        Self::parse()
91    }
92
93    /// Parsers only the default CLI arguments from the given iterator
94    pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
95    where
96        I: IntoIterator<Item = T>,
97        T: Into<OsString> + Clone,
98    {
99        Self::try_parse_from(itr)
100    }
101}
102
103impl<C, Ext> Cli<C, Ext>
104where
105    C: ChainSpecParser<ChainSpec = ChainSpec>,
106    Ext: clap::Args + fmt::Debug,
107{
108    /// Execute the configured cli command.
109    ///
110    /// This accepts a closure that is used to launch the node via the
111    /// [`NodeCommand`](reth_cli_commands::node::NodeCommand).
112    pub fn run<L, Fut>(self, launcher: L) -> eyre::Result<()>
113    where
114        L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut,
115        Fut: Future<Output = eyre::Result<()>>,
116    {
117        self.with_runner(CliRunner::try_default_runtime()?, launcher)
118    }
119
120    /// Execute the configured cli command with the provided [`CliRunner`].
121    pub fn with_runner<L, Fut>(mut self, runner: CliRunner, launcher: L) -> eyre::Result<()>
122    where
123        L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut,
124        Fut: Future<Output = eyre::Result<()>>,
125    {
126        // add network name to logs dir
127        self.logs.log_file_directory =
128            self.logs.log_file_directory.join(self.chain.chain().to_string());
129
130        let _guard = self.init_tracing()?;
131        info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.logs.log_file_directory);
132
133        // Install the prometheus recorder to be sure to record all metrics
134        let _ = install_prometheus_recorder();
135
136        match self.command {
137            Commands::Node(command) => runner.run_command_until_exit(|ctx| {
138                command.execute(ctx, FnLauncher::new::<C, Ext>(launcher))
139            }),
140        }
141    }
142
143    /// Initializes tracing with the configured options.
144    ///
145    /// If file logging is enabled, this function returns a guard that must be kept alive to ensure
146    /// that all logs are flushed to disk.
147    pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
148        let guard = self.logs.init_tracing()?;
149        Ok(guard)
150    }
151}
152
153/// Commands to be executed
154#[derive(Debug, Subcommand)]
155#[expect(clippy::large_enum_variant)]
156pub enum Commands<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> {
157    /// Start the node
158    #[command(name = "node")]
159    Node(Box<node::NodeCommand<C, Ext>>),
160}
161
162#[cfg(test)]
163mod test {
164    use crate::chainspec::SeismicChainSpecParser;
165    use clap::Parser;
166    use reth_cli_commands::{node::NoArgs, NodeCommand};
167    use reth_seismic_chainspec::{SEISMIC_DEV, SEISMIC_DEV_OLD};
168
169    #[test]
170    fn parse_dev() {
171        let cmd =
172            NodeCommand::<SeismicChainSpecParser, NoArgs>::parse_from(["seismic-reth", "--dev"]);
173        let chain = SEISMIC_DEV.clone();
174        assert_eq!(cmd.chain.chain, chain.chain);
175        assert_eq!(cmd.chain.genesis_hash(), chain.genesis_hash());
176        assert_eq!(
177            cmd.chain.paris_block_and_final_difficulty,
178            chain.paris_block_and_final_difficulty
179        );
180        assert_eq!(cmd.chain.hardforks, chain.hardforks);
181
182        assert!(cmd.rpc.http);
183        assert!(cmd.network.discovery.disable_discovery);
184
185        assert!(cmd.dev.dev);
186    }
187
188    #[test]
189    fn parse_dev_old() {
190        // TODO: remove this once we launch devnet with consensus
191        let cmd = NodeCommand::<SeismicChainSpecParser, NoArgs>::parse_from([
192            "seismic-reth",
193            "--chain",
194            "dev-old",
195            "--http",
196            "-d",
197        ]);
198        let chain = SEISMIC_DEV_OLD.clone();
199        assert_eq!(cmd.chain.chain, chain.chain);
200        assert_eq!(cmd.chain.genesis_hash(), chain.genesis_hash());
201        assert_eq!(
202            cmd.chain.paris_block_and_final_difficulty,
203            chain.paris_block_and_final_difficulty
204        );
205        assert_eq!(cmd.chain.hardforks, chain.hardforks);
206
207        assert!(cmd.rpc.http);
208        assert!(cmd.network.discovery.disable_discovery);
209    }
210}