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_network::EthNetworkPrimitives;
20use reth_node_builder::{NodeBuilder, WithLaunchContext};
21use reth_node_ethereum::{consensus::EthBeaconConsensus, EthExecutorProvider, EthereumNode};
22use reth_node_metrics::recorder::install_prometheus_recorder;
23use reth_tracing::FileWorkerGuard;
24use std::{ffi::OsString, fmt, future::Future, sync::Arc};
25use tracing::info;
26
27pub use crate::core::cli::*;
33
34#[derive(Debug, Parser)]
38#[command(author, version = SHORT_VERSION, long_version = LONG_VERSION, about = "Reth", long_about = None)]
39pub struct Cli<C: ChainSpecParser = EthereumChainSpecParser, Ext: clap::Args + fmt::Debug = NoArgs>
40{
41 #[command(subcommand)]
43 pub command: Commands<C, Ext>,
44
45 #[arg(
49 long,
50 value_name = "CHAIN_OR_PATH",
51 long_help = C::help_message(),
52 default_value = C::SUPPORTED_CHAINS[0],
53 value_parser = C::parser(),
54 global = true,
55 )]
56 pub chain: Arc<C::ChainSpec>,
57
58 #[arg(long, value_name = "INSTANCE", global = true, default_value_t = 1, value_parser = value_parser!(u16).range(..=200))]
72 pub instance: u16,
73
74 #[command(flatten)]
76 pub logs: LogArgs,
77}
78
79impl Cli {
80 pub fn parse_args() -> Self {
82 Self::parse()
83 }
84
85 pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
87 where
88 I: IntoIterator<Item = T>,
89 T: Into<OsString> + Clone,
90 {
91 Self::try_parse_from(itr)
92 }
93}
94
95impl<C: ChainSpecParser<ChainSpec = ChainSpec>, Ext: clap::Args + fmt::Debug> Cli<C, Ext> {
96 pub fn run<L, Fut>(self, launcher: L) -> eyre::Result<()>
140 where
141 L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut,
142 Fut: Future<Output = eyre::Result<()>>,
143 {
144 self.with_runner(CliRunner::try_default_runtime()?, launcher)
145 }
146
147 pub fn with_runner<L, Fut>(mut self, runner: CliRunner, launcher: L) -> eyre::Result<()>
173 where
174 L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut,
175 Fut: Future<Output = eyre::Result<()>>,
176 {
177 self.logs.log_file_directory =
179 self.logs.log_file_directory.join(self.chain.chain.to_string());
180
181 let _guard = self.init_tracing()?;
182 info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.logs.log_file_directory);
183
184 let _ = install_prometheus_recorder();
186
187 let components = |spec: Arc<C::ChainSpec>| {
188 (EthExecutorProvider::ethereum(spec.clone()), EthBeaconConsensus::new(spec))
189 };
190 match self.command {
191 Commands::Node(command) => {
192 runner.run_command_until_exit(|ctx| command.execute(ctx, launcher))
193 }
194 Commands::Init(command) => {
195 runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
196 }
197 Commands::InitState(command) => {
198 runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
199 }
200 Commands::Import(command) => {
201 runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode, _, _>(components))
202 }
203 Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
204 Commands::Db(command) => {
205 runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
206 }
207 Commands::Stage(command) => runner.run_command_until_exit(|ctx| {
208 command.execute::<EthereumNode, _, _, EthNetworkPrimitives>(ctx, components)
209 }),
210 Commands::P2P(command) => {
211 runner.run_until_ctrl_c(command.execute::<EthNetworkPrimitives>())
212 }
213 #[cfg(feature = "dev")]
214 Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
215 Commands::Config(command) => runner.run_until_ctrl_c(command.execute()),
216 Commands::Debug(command) => {
217 runner.run_command_until_exit(|ctx| command.execute::<EthereumNode>(ctx))
218 }
219 Commands::Recover(command) => {
220 runner.run_command_until_exit(|ctx| command.execute::<EthereumNode>(ctx))
221 }
222 Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::<EthereumNode>()),
223 }
224 }
225
226 pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
231 let guard = self.logs.init_tracing()?;
232 Ok(guard)
233 }
234}
235
236#[derive(Debug, Subcommand)]
238#[allow(clippy::large_enum_variant)]
239pub enum Commands<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> {
240 #[command(name = "node")]
242 Node(Box<node::NodeCommand<C, Ext>>),
243 #[command(name = "init")]
245 Init(init_cmd::InitCommand<C>),
246 #[command(name = "init-state")]
248 InitState(init_state::InitStateCommand<C>),
249 #[command(name = "import")]
251 Import(import::ImportCommand<C>),
252 DumpGenesis(dump_genesis::DumpGenesisCommand<C>),
254 #[command(name = "db")]
256 Db(db::Command<C>),
257 #[command(name = "stage")]
259 Stage(stage::Command<C>),
260 #[command(name = "p2p")]
262 P2P(p2p::Command<C>),
263 #[cfg(feature = "dev")]
265 #[command(name = "test-vectors")]
266 TestVectors(reth_cli_commands::test_vectors::Command),
267 #[command(name = "config")]
269 Config(config_cmd::Command),
270 #[command(name = "debug")]
272 Debug(Box<debug_cmd::Command<C>>),
273 #[command(name = "recover")]
275 Recover(recover::Command<C>),
276 #[command(name = "prune")]
278 Prune(prune::PruneCommand<C>),
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use crate::args::ColorMode;
285 use clap::CommandFactory;
286 use reth_ethereum_cli::chainspec::SUPPORTED_CHAINS;
287
288 #[test]
289 fn parse_color_mode() {
290 let reth = Cli::try_parse_args_from(["reth", "node", "--color", "always"]).unwrap();
291 assert_eq!(reth.logs.color, ColorMode::Always);
292 }
293
294 #[test]
298 fn test_parse_help_all_subcommands() {
299 let reth = Cli::<EthereumChainSpecParser, NoArgs>::command();
300 for sub_command in reth.get_subcommands() {
301 let err = Cli::try_parse_args_from(["reth", sub_command.get_name(), "--help"])
302 .err()
303 .unwrap_or_else(|| {
304 panic!("Failed to parse help message {}", sub_command.get_name())
305 });
306
307 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
310 }
311 }
312
313 #[test]
316 fn parse_logs_path() {
317 let mut reth = Cli::try_parse_args_from(["reth", "node"]).unwrap();
318 reth.logs.log_file_directory =
319 reth.logs.log_file_directory.join(reth.chain.chain.to_string());
320 let log_dir = reth.logs.log_file_directory;
321 let end = format!("reth/logs/{}", SUPPORTED_CHAINS[0]);
322 assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
323
324 let mut iter = SUPPORTED_CHAINS.iter();
325 iter.next();
326 for chain in iter {
327 let mut reth = Cli::try_parse_args_from(["reth", "node", "--chain", chain]).unwrap();
328 reth.logs.log_file_directory =
329 reth.logs.log_file_directory.join(reth.chain.chain.to_string());
330 let log_dir = reth.logs.log_file_directory;
331 let end = format!("reth/logs/{chain}");
332 assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
333 }
334 }
335
336 #[test]
337 fn parse_env_filter_directives() {
338 let temp_dir = tempfile::tempdir().unwrap();
339
340 unsafe { std::env::set_var("RUST_LOG", "info,evm=debug") };
341 let reth = Cli::try_parse_args_from([
342 "reth",
343 "init",
344 "--datadir",
345 temp_dir.path().to_str().unwrap(),
346 "--log.file.filter",
347 "debug,net=trace",
348 ])
349 .unwrap();
350 assert!(reth.run(async move |_, _| Ok(())).is_ok());
351 }
352}