1use crate::chainspec::EthereumChainSpecParser;
4use clap::{Parser, Subcommand};
5use reth_chainspec::{ChainSpec, EthChainSpec, Hardforks};
6use reth_cli::chainspec::ChainSpecParser;
7use reth_cli_commands::{
8 common::{CliComponentsBuilder, CliHeader, CliNodeTypes},
9 config_cmd, db, download, dump_genesis, export_era, import, import_era, init_cmd, init_state,
10 launcher::FnLauncher,
11 node::{self, NoArgs},
12 p2p, prune, re_execute, recover, stage,
13};
14use reth_cli_runner::CliRunner;
15use reth_db::DatabaseEnv;
16use reth_node_api::NodePrimitives;
17use reth_node_builder::{NodeBuilder, WithLaunchContext};
18use reth_node_core::{args::LogArgs, version::version_metadata};
19use reth_node_ethereum::{consensus::EthBeaconConsensus, EthEvmConfig, EthereumNode};
20use reth_node_metrics::recorder::install_prometheus_recorder;
21use reth_tracing::FileWorkerGuard;
22use std::{ffi::OsString, fmt, future::Future, sync::Arc};
23use tracing::info;
24
25#[derive(Debug, Parser)]
29#[command(author, version =version_metadata().short_version.as_ref(), long_version = version_metadata().long_version.as_ref(), about = "Reth", long_about = None)]
30pub struct Cli<C: ChainSpecParser = EthereumChainSpecParser, Ext: clap::Args + fmt::Debug = NoArgs>
31{
32 #[command(subcommand)]
34 pub command: Commands<C, Ext>,
35
36 #[command(flatten)]
38 pub logs: LogArgs,
39}
40
41impl Cli {
42 pub fn parse_args() -> Self {
44 Self::parse()
45 }
46
47 pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
49 where
50 I: IntoIterator<Item = T>,
51 T: Into<OsString> + Clone,
52 {
53 Self::try_parse_from(itr)
54 }
55}
56
57impl<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> Cli<C, Ext> {
58 pub fn run<L, Fut>(self, launcher: L) -> eyre::Result<()>
101 where
102 L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut,
103 Fut: Future<Output = eyre::Result<()>>,
104 C: ChainSpecParser<ChainSpec = ChainSpec>,
105 {
106 self.with_runner(CliRunner::try_default_runtime()?, launcher)
107 }
108
109 pub fn run_with_components<N>(
116 self,
117 components: impl CliComponentsBuilder<N>,
118 launcher: impl AsyncFnOnce(
119 WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>,
120 Ext,
121 ) -> eyre::Result<()>,
122 ) -> eyre::Result<()>
123 where
124 N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: CliHeader>, ChainSpec: Hardforks>,
125 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
126 {
127 self.with_runner_and_components(CliRunner::try_default_runtime()?, components, launcher)
128 }
129
130 pub fn with_runner<L, Fut>(self, runner: CliRunner, launcher: L) -> eyre::Result<()>
150 where
151 L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut,
152 Fut: Future<Output = eyre::Result<()>>,
153 C: ChainSpecParser<ChainSpec = ChainSpec>,
154 {
155 let components = |spec: Arc<C::ChainSpec>| {
156 (EthEvmConfig::ethereum(spec.clone()), EthBeaconConsensus::new(spec))
157 };
158
159 self.with_runner_and_components::<EthereumNode>(
160 runner,
161 components,
162 async move |builder, ext| launcher(builder, ext).await,
163 )
164 }
165
166 pub fn with_runner_and_components<N>(
169 mut self,
170 runner: CliRunner,
171 components: impl CliComponentsBuilder<N>,
172 launcher: impl AsyncFnOnce(
173 WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>,
174 Ext,
175 ) -> eyre::Result<()>,
176 ) -> eyre::Result<()>
177 where
178 N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: CliHeader>, ChainSpec: Hardforks>,
179 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
180 {
181 if let Some(chain_spec) = self.command.chain_spec() {
183 self.logs.log_file_directory =
184 self.logs.log_file_directory.join(chain_spec.chain().to_string());
185 }
186 let _guard = self.init_tracing()?;
187 info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.logs.log_file_directory);
188
189 let _ = install_prometheus_recorder();
191
192 match self.command {
193 Commands::Node(command) => runner.run_command_until_exit(|ctx| {
194 command.execute(ctx, FnLauncher::new::<C, Ext>(launcher))
195 }),
196 Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
197 Commands::InitState(command) => {
198 runner.run_blocking_until_ctrl_c(command.execute::<N>())
199 }
200 Commands::Import(command) => {
201 runner.run_blocking_until_ctrl_c(command.execute::<N, _>(components))
202 }
203 Commands::ImportEra(command) => {
204 runner.run_blocking_until_ctrl_c(command.execute::<N>())
205 }
206 Commands::ExportEra(command) => {
207 runner.run_blocking_until_ctrl_c(command.execute::<N>())
208 }
209 Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
210 Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
211 Commands::Download(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
212 Commands::Stage(command) => {
213 runner.run_command_until_exit(|ctx| command.execute::<N, _>(ctx, components))
214 }
215 Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::<N>()),
216 #[cfg(feature = "dev")]
217 Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
218 Commands::Config(command) => runner.run_until_ctrl_c(command.execute()),
219 Commands::Recover(command) => {
220 runner.run_command_until_exit(|ctx| command.execute::<N>(ctx))
221 }
222 Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::<N>()),
223 Commands::ReExecute(command) => {
224 runner.run_until_ctrl_c(command.execute::<N>(components))
225 }
226 }
227 }
228
229 pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
234 let guard = self.logs.init_tracing()?;
235 Ok(guard)
236 }
237}
238
239#[derive(Debug, Subcommand)]
241pub enum Commands<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> {
242 #[command(name = "node")]
244 Node(Box<node::NodeCommand<C, Ext>>),
245 #[command(name = "init")]
247 Init(init_cmd::InitCommand<C>),
248 #[command(name = "init-state")]
250 InitState(init_state::InitStateCommand<C>),
251 #[command(name = "import")]
253 Import(import::ImportCommand<C>),
254 #[command(name = "import-era")]
256 ImportEra(import_era::ImportEraCommand<C>),
257 #[command(name = "export-era")]
259 ExportEra(export_era::ExportEraCommand<C>),
260 DumpGenesis(dump_genesis::DumpGenesisCommand<C>),
262 #[command(name = "db")]
264 Db(Box<db::Command<C>>),
265 #[command(name = "download")]
267 Download(download::DownloadCommand<C>),
268 #[command(name = "stage")]
270 Stage(stage::Command<C>),
271 #[command(name = "p2p")]
273 P2P(Box<p2p::Command<C>>),
274 #[cfg(feature = "dev")]
276 #[command(name = "test-vectors")]
277 TestVectors(reth_cli_commands::test_vectors::Command),
278 #[command(name = "config")]
280 Config(config_cmd::Command),
281 #[command(name = "recover")]
283 Recover(recover::Command<C>),
284 #[command(name = "prune")]
286 Prune(prune::PruneCommand<C>),
287 #[command(name = "re-execute")]
289 ReExecute(re_execute::Command<C>),
290}
291
292impl<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> Commands<C, Ext> {
293 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
295 match self {
296 Self::Node(cmd) => cmd.chain_spec(),
297 Self::Init(cmd) => cmd.chain_spec(),
298 Self::InitState(cmd) => cmd.chain_spec(),
299 Self::Import(cmd) => cmd.chain_spec(),
300 Self::ExportEra(cmd) => cmd.chain_spec(),
301 Self::ImportEra(cmd) => cmd.chain_spec(),
302 Self::DumpGenesis(cmd) => cmd.chain_spec(),
303 Self::Db(cmd) => cmd.chain_spec(),
304 Self::Download(cmd) => cmd.chain_spec(),
305 Self::Stage(cmd) => cmd.chain_spec(),
306 Self::P2P(cmd) => cmd.chain_spec(),
307 #[cfg(feature = "dev")]
308 Self::TestVectors(_) => None,
309 Self::Config(_) => None,
310 Self::Recover(cmd) => cmd.chain_spec(),
311 Self::Prune(cmd) => cmd.chain_spec(),
312 Self::ReExecute(cmd) => cmd.chain_spec(),
313 }
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::chainspec::SUPPORTED_CHAINS;
321 use clap::CommandFactory;
322 use reth_node_core::args::ColorMode;
323
324 #[test]
325 fn parse_color_mode() {
326 let reth = Cli::try_parse_args_from(["reth", "node", "--color", "always"]).unwrap();
327 assert_eq!(reth.logs.color, ColorMode::Always);
328 }
329
330 #[test]
334 fn test_parse_help_all_subcommands() {
335 let reth = Cli::<EthereumChainSpecParser, NoArgs>::command();
336 for sub_command in reth.get_subcommands() {
337 let err = Cli::try_parse_args_from(["reth", sub_command.get_name(), "--help"])
338 .err()
339 .unwrap_or_else(|| {
340 panic!("Failed to parse help message {}", sub_command.get_name())
341 });
342
343 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
346 }
347 }
348
349 #[test]
352 fn parse_logs_path_node() {
353 let mut reth = Cli::try_parse_args_from(["reth", "node"]).unwrap();
354 if let Some(chain_spec) = reth.command.chain_spec() {
355 reth.logs.log_file_directory =
356 reth.logs.log_file_directory.join(chain_spec.chain.to_string());
357 }
358 let log_dir = reth.logs.log_file_directory;
359 let end = format!("reth/logs/{}", SUPPORTED_CHAINS[0]);
360 assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
361
362 let mut iter = SUPPORTED_CHAINS.iter();
363 iter.next();
364 for chain in iter {
365 let mut reth = Cli::try_parse_args_from(["reth", "node", "--chain", chain]).unwrap();
366 let chain =
367 reth.command.chain_spec().map(|c| c.chain.to_string()).unwrap_or(String::new());
368 reth.logs.log_file_directory = reth.logs.log_file_directory.join(chain.clone());
369 let log_dir = reth.logs.log_file_directory;
370 let end = format!("reth/logs/{chain}");
371 assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
372 }
373 }
374
375 #[test]
378 fn parse_logs_path_init() {
379 let mut reth = Cli::try_parse_args_from(["reth", "init"]).unwrap();
380 if let Some(chain_spec) = reth.command.chain_spec() {
381 reth.logs.log_file_directory =
382 reth.logs.log_file_directory.join(chain_spec.chain.to_string());
383 }
384 let log_dir = reth.logs.log_file_directory;
385 let end = format!("reth/logs/{}", SUPPORTED_CHAINS[0]);
386 println!("{log_dir:?}");
387 assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
388 }
389
390 #[test]
392 fn parse_empty_logs_path() {
393 let mut reth = Cli::try_parse_args_from(["reth", "config"]).unwrap();
394 if let Some(chain_spec) = reth.command.chain_spec() {
395 reth.logs.log_file_directory =
396 reth.logs.log_file_directory.join(chain_spec.chain.to_string());
397 }
398 let log_dir = reth.logs.log_file_directory;
399 let end = "reth/logs".to_string();
400 println!("{log_dir:?}");
401 assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
402 }
403
404 #[test]
405 fn parse_env_filter_directives() {
406 let temp_dir = tempfile::tempdir().unwrap();
407
408 unsafe { std::env::set_var("RUST_LOG", "info,evm=debug") };
409 let reth = Cli::try_parse_args_from([
410 "reth",
411 "init",
412 "--datadir",
413 temp_dir.path().to_str().unwrap(),
414 "--log.file.filter",
415 "debug,net=trace",
416 ])
417 .unwrap();
418 assert!(reth.run(async move |_, _| Ok(())).is_ok());
419 }
420}