use crate::{
args::LogArgs,
commands::debug_cmd,
version::{LONG_VERSION, SHORT_VERSION},
};
use clap::{value_parser, Parser, Subcommand};
use reth_chainspec::ChainSpec;
use reth_cli::chainspec::ChainSpecParser;
use reth_cli_commands::{
config_cmd, db, dump_genesis, import, init_cmd, init_state,
node::{self, NoArgs},
p2p, prune, recover, stage,
};
use reth_cli_runner::CliRunner;
use reth_db::DatabaseEnv;
use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
use reth_network::EthNetworkPrimitives;
use reth_node_builder::{NodeBuilder, WithLaunchContext};
use reth_node_ethereum::{EthExecutorProvider, EthereumNode};
use reth_node_metrics::recorder::install_prometheus_recorder;
use reth_tracing::FileWorkerGuard;
use std::{ffi::OsString, fmt, future::Future, sync::Arc};
use tracing::info;
pub use crate::core::cli::*;
#[derive(Debug, Parser)]
#[command(author, version = SHORT_VERSION, long_version = LONG_VERSION, about = "Reth", long_about = None)]
pub struct Cli<C: ChainSpecParser = EthereumChainSpecParser, Ext: clap::Args + fmt::Debug = NoArgs>
{
#[command(subcommand)]
pub command: Commands<C, Ext>,
#[arg(
long,
value_name = "CHAIN_OR_PATH",
long_help = C::help_message(),
default_value = C::SUPPORTED_CHAINS[0],
value_parser = C::parser(),
global = true,
)]
pub chain: Arc<C::ChainSpec>,
#[arg(long, value_name = "INSTANCE", global = true, default_value_t = 1, value_parser = value_parser!(u16).range(..=200))]
pub instance: u16,
#[command(flatten)]
pub logs: LogArgs,
}
impl Cli {
pub fn parse_args() -> Self {
Self::parse()
}
pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
Self::try_parse_from(itr)
}
}
impl<C: ChainSpecParser<ChainSpec = ChainSpec>, Ext: clap::Args + fmt::Debug> Cli<C, Ext> {
pub fn run<L, Fut>(mut self, launcher: L) -> eyre::Result<()>
where
L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut,
Fut: Future<Output = eyre::Result<()>>,
{
self.logs.log_file_directory =
self.logs.log_file_directory.join(self.chain.chain.to_string());
let _guard = self.init_tracing()?;
info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.logs.log_file_directory);
let _ = install_prometheus_recorder();
let runner = CliRunner::default();
match self.command {
Commands::Node(command) => {
runner.run_command_until_exit(|ctx| command.execute(ctx, launcher))
}
Commands::Init(command) => {
runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
}
Commands::InitState(command) => {
runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
}
Commands::Import(command) => runner.run_blocking_until_ctrl_c(
command.execute::<EthereumNode, _, _>(EthExecutorProvider::ethereum),
),
Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
Commands::Db(command) => {
runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
}
Commands::Stage(command) => runner.run_command_until_exit(|ctx| {
command.execute::<EthereumNode, _, _, EthNetworkPrimitives>(
ctx,
EthExecutorProvider::ethereum,
)
}),
Commands::P2P(command) => {
runner.run_until_ctrl_c(command.execute::<EthNetworkPrimitives>())
}
#[cfg(feature = "dev")]
Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
Commands::Config(command) => runner.run_until_ctrl_c(command.execute()),
Commands::Debug(command) => {
runner.run_command_until_exit(|ctx| command.execute::<EthereumNode>(ctx))
}
Commands::Recover(command) => {
runner.run_command_until_exit(|ctx| command.execute::<EthereumNode>(ctx))
}
Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::<EthereumNode>()),
}
}
pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
let guard = self.logs.init_tracing()?;
Ok(guard)
}
}
#[derive(Debug, Subcommand)]
pub enum Commands<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> {
#[command(name = "node")]
Node(Box<node::NodeCommand<C, Ext>>),
#[command(name = "init")]
Init(init_cmd::InitCommand<C>),
#[command(name = "init-state")]
InitState(init_state::InitStateCommand<C>),
#[command(name = "import")]
Import(import::ImportCommand<C>),
DumpGenesis(dump_genesis::DumpGenesisCommand<C>),
#[command(name = "db")]
Db(db::Command<C>),
#[command(name = "stage")]
Stage(stage::Command<C>),
#[command(name = "p2p")]
P2P(p2p::Command<C>),
#[cfg(feature = "dev")]
#[command(name = "test-vectors")]
TestVectors(reth_cli_commands::test_vectors::Command),
#[command(name = "config")]
Config(config_cmd::Command),
#[command(name = "debug")]
Debug(debug_cmd::Command<C>),
#[command(name = "recover")]
Recover(recover::Command<C>),
#[command(name = "prune")]
Prune(prune::PruneCommand<C>),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::args::ColorMode;
use clap::CommandFactory;
use reth_ethereum_cli::chainspec::SUPPORTED_CHAINS;
#[test]
fn parse_color_mode() {
let reth = Cli::try_parse_args_from(["reth", "node", "--color", "always"]).unwrap();
assert_eq!(reth.logs.color, ColorMode::Always);
}
#[test]
fn test_parse_help_all_subcommands() {
let reth = Cli::<EthereumChainSpecParser, NoArgs>::command();
for sub_command in reth.get_subcommands() {
let err = Cli::try_parse_args_from(["reth", sub_command.get_name(), "--help"])
.err()
.unwrap_or_else(|| {
panic!("Failed to parse help message {}", sub_command.get_name())
});
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
}
}
#[test]
fn parse_logs_path() {
let mut reth = Cli::try_parse_args_from(["reth", "node"]).unwrap();
reth.logs.log_file_directory =
reth.logs.log_file_directory.join(reth.chain.chain.to_string());
let log_dir = reth.logs.log_file_directory;
let end = format!("reth/logs/{}", SUPPORTED_CHAINS[0]);
assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
let mut iter = SUPPORTED_CHAINS.iter();
iter.next();
for chain in iter {
let mut reth = Cli::try_parse_args_from(["reth", "node", "--chain", chain]).unwrap();
reth.logs.log_file_directory =
reth.logs.log_file_directory.join(reth.chain.chain.to_string());
let log_dir = reth.logs.log_file_directory;
let end = format!("reth/logs/{chain}");
assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
}
}
#[test]
fn parse_env_filter_directives() {
let temp_dir = tempfile::tempdir().unwrap();
std::env::set_var("RUST_LOG", "info,evm=debug");
let reth = Cli::try_parse_args_from([
"reth",
"init",
"--datadir",
temp_dir.path().to_str().unwrap(),
"--log.file.filter",
"debug,net=trace",
])
.unwrap();
assert!(reth.run(|_, _| async move { Ok(()) }).is_ok());
}
}