1use crate::{
4 args::LogArgs,
5 commands::debug_cmd,
6 version::{LONG_VERSION, SHORT_VERSION},
7};
8use clap::{value_parser, Parser, Subcommand};
9use reth_chainspec::ChainSpec;
10use reth_cli::chainspec::ChainSpecParser;
11use reth_cli_commands::{
12 config_cmd, db, dump_genesis, import, init_cmd, init_state,
13 node::{self, NoArgs},
14 p2p, prune, recover, stage,
15};
16use reth_cli_runner::CliRunner;
17use reth_db::DatabaseEnv;
18use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
19use reth_node_builder::{NodeBuilder, WithLaunchContext};
20use reth_node_ethereum::{EthExecutorProvider, EthereumNode};
21use reth_node_metrics::recorder::install_prometheus_recorder;
22use reth_tracing::FileWorkerGuard;
23use std::{ffi::OsString, fmt, future::Future, sync::Arc};
24use tracing::info;
25
26pub use crate::core::cli::*;
32
33#[derive(Debug, Parser)]
37#[command(author, version = SHORT_VERSION, long_version = LONG_VERSION, about = "Reth", long_about = None)]
38pub struct Cli<C: ChainSpecParser = EthereumChainSpecParser, Ext: clap::Args + fmt::Debug = NoArgs>
39{
40 #[command(subcommand)]
42 pub command: Commands<C, Ext>,
43
44 #[arg(
48 long,
49 value_name = "CHAIN_OR_PATH",
50 long_help = C::help_message(),
51 default_value = C::SUPPORTED_CHAINS[0],
52 value_parser = C::parser(),
53 global = true,
54 )]
55 pub chain: Arc<C::ChainSpec>,
56
57 #[arg(long, value_name = "INSTANCE", global = true, default_value_t = 1, value_parser = value_parser!(u16).range(..=200))]
71 pub instance: u16,
72
73 #[command(flatten)]
75 pub logs: LogArgs,
76}
77
78impl Cli {
79 pub fn parse_args() -> Self {
81 Self::parse()
82 }
83
84 pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
86 where
87 I: IntoIterator<Item = T>,
88 T: Into<OsString> + Clone,
89 {
90 Self::try_parse_from(itr)
91 }
92}
93
94impl<C: ChainSpecParser<ChainSpec = ChainSpec>, Ext: clap::Args + fmt::Debug> Cli<C, Ext> {
95 pub fn run<L, Fut>(mut self, launcher: L) -> eyre::Result<()>
139 where
140 L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut,
141 Fut: Future<Output = eyre::Result<()>>,
142 {
143 self.logs.log_file_directory =
145 self.logs.log_file_directory.join(self.chain.chain.to_string());
146
147 let _guard = self.init_tracing()?;
148 info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.logs.log_file_directory);
149
150 let _ = install_prometheus_recorder();
152
153 let runner = CliRunner::default();
154 match self.command {
155 Commands::Node(command) => {
156 runner.run_command_until_exit(|ctx| command.execute(ctx, launcher))
157 }
158 Commands::Init(command) => {
159 runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
160 }
161 Commands::InitState(command) => {
162 runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
163 }
164 Commands::Import(command) => runner.run_blocking_until_ctrl_c(
165 command.execute::<EthereumNode, _, _>(EthExecutorProvider::ethereum),
166 ),
167 Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
168 Commands::Db(command) => {
169 runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
170 }
171 Commands::Stage(command) => runner.run_command_until_exit(|ctx| {
172 command.execute::<EthereumNode, _, _>(ctx, EthExecutorProvider::ethereum)
173 }),
174 Commands::P2P(command) => runner.run_until_ctrl_c(command.execute()),
175 #[cfg(feature = "dev")]
176 Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
177 Commands::Config(command) => runner.run_until_ctrl_c(command.execute()),
178 Commands::Debug(command) => {
179 runner.run_command_until_exit(|ctx| command.execute::<EthereumNode>(ctx))
180 }
181 Commands::Recover(command) => {
182 runner.run_command_until_exit(|ctx| command.execute::<EthereumNode>(ctx))
183 }
184 Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::<EthereumNode>()),
185 }
186 }
187
188 pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
193 let guard = self.logs.init_tracing()?;
194 Ok(guard)
195 }
196}
197
198#[derive(Debug, Subcommand)]
200pub enum Commands<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> {
201 #[command(name = "node")]
203 Node(Box<node::NodeCommand<C, Ext>>),
204 #[command(name = "init")]
206 Init(init_cmd::InitCommand<C>),
207 #[command(name = "init-state")]
209 InitState(init_state::InitStateCommand<C>),
210 #[command(name = "import")]
212 Import(import::ImportCommand<C>),
213 DumpGenesis(dump_genesis::DumpGenesisCommand<C>),
215 #[command(name = "db")]
217 Db(db::Command<C>),
218 #[command(name = "stage")]
220 Stage(stage::Command<C>),
221 #[command(name = "p2p")]
223 P2P(p2p::Command<C>),
224 #[cfg(feature = "dev")]
226 #[command(name = "test-vectors")]
227 TestVectors(reth_cli_commands::test_vectors::Command),
228 #[command(name = "config")]
230 Config(config_cmd::Command),
231 #[command(name = "debug")]
233 Debug(debug_cmd::Command<C>),
234 #[command(name = "recover")]
236 Recover(recover::Command<C>),
237 #[command(name = "prune")]
239 Prune(prune::PruneCommand<C>),
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::args::ColorMode;
246 use clap::CommandFactory;
247 use reth_ethereum_cli::chainspec::SUPPORTED_CHAINS;
248
249 #[test]
250 fn parse_color_mode() {
251 let reth = Cli::try_parse_args_from(["reth", "node", "--color", "always"]).unwrap();
252 assert_eq!(reth.logs.color, ColorMode::Always);
253 }
254
255 #[test]
259 fn test_parse_help_all_subcommands() {
260 let reth = Cli::<EthereumChainSpecParser, NoArgs>::command();
261 for sub_command in reth.get_subcommands() {
262 let err = Cli::try_parse_args_from(["reth", sub_command.get_name(), "--help"])
263 .err()
264 .unwrap_or_else(|| {
265 panic!("Failed to parse help message {}", sub_command.get_name())
266 });
267
268 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
271 }
272 }
273
274 #[test]
277 fn parse_logs_path() {
278 let mut reth = Cli::try_parse_args_from(["reth", "node"]).unwrap();
279 reth.logs.log_file_directory =
280 reth.logs.log_file_directory.join(reth.chain.chain.to_string());
281 let log_dir = reth.logs.log_file_directory;
282 let end = format!("reth/logs/{}", SUPPORTED_CHAINS[0]);
283 assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
284
285 let mut iter = SUPPORTED_CHAINS.iter();
286 iter.next();
287 for chain in iter {
288 let mut reth = Cli::try_parse_args_from(["reth", "node", "--chain", chain]).unwrap();
289 reth.logs.log_file_directory =
290 reth.logs.log_file_directory.join(reth.chain.chain.to_string());
291 let log_dir = reth.logs.log_file_directory;
292 let end = format!("reth/logs/{chain}");
293 assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
294 }
295 }
296
297 #[test]
298 fn parse_env_filter_directives() {
299 let temp_dir = tempfile::tempdir().unwrap();
300
301 std::env::set_var("RUST_LOG", "info,evm=debug");
302 let reth = Cli::try_parse_args_from([
303 "reth",
304 "init",
305 "--datadir",
306 temp_dir.path().to_str().unwrap(),
307 "--log.file.filter",
308 "debug,net=trace",
309 ])
310 .unwrap();
311 assert!(reth.run(|_, _| async move { Ok(()) }).is_ok());
312 }
313}