1use crate::{interface::Commands, Cli};
2use eyre::{eyre, Result};
3use reth_chainspec::{ChainSpec, EthChainSpec, Hardforks};
4use reth_cli::chainspec::ChainSpecParser;
5use reth_cli_commands::{
6 common::{CliComponentsBuilder, CliHeader, CliNodeTypes},
7 launcher::{FnLauncher, Launcher},
8};
9use reth_cli_runner::CliRunner;
10use reth_db::DatabaseEnv;
11use reth_node_api::NodePrimitives;
12use reth_node_builder::{NodeBuilder, WithLaunchContext};
13use reth_node_ethereum::{consensus::EthBeaconConsensus, EthEvmConfig, EthereumNode};
14use reth_node_metrics::recorder::install_prometheus_recorder;
15use reth_rpc_server_types::RpcModuleValidator;
16use reth_tracing::{FileWorkerGuard, Layers};
17use std::{fmt, sync::Arc};
18use tracing::info;
19
20#[derive(Debug)]
22pub struct CliApp<Spec: ChainSpecParser, Ext: clap::Args + fmt::Debug, Rpc: RpcModuleValidator> {
23 cli: Cli<Spec, Ext, Rpc>,
24 runner: Option<CliRunner>,
25 layers: Option<Layers>,
26 guard: Option<FileWorkerGuard>,
27}
28
29impl<C, Ext, Rpc> CliApp<C, Ext, Rpc>
30where
31 C: ChainSpecParser,
32 Ext: clap::Args + fmt::Debug,
33 Rpc: RpcModuleValidator,
34{
35 pub(crate) fn new(cli: Cli<C, Ext, Rpc>) -> Self {
36 Self { cli, runner: None, layers: Some(Layers::new()), guard: None }
37 }
38
39 pub fn set_runner(&mut self, runner: CliRunner) {
43 self.runner = Some(runner);
44 }
45
46 pub fn access_tracing_layers(&mut self) -> Result<&mut Layers> {
51 self.layers.as_mut().ok_or_else(|| eyre!("Tracing already initialized"))
52 }
53
54 pub fn run(self, launcher: impl Launcher<C, Ext>) -> Result<()>
59 where
60 C: ChainSpecParser<ChainSpec = ChainSpec>,
61 {
62 let components = |spec: Arc<ChainSpec>| {
63 (EthEvmConfig::ethereum(spec.clone()), Arc::new(EthBeaconConsensus::new(spec)))
64 };
65
66 self.run_with_components::<EthereumNode>(components, |builder, ext| async move {
67 launcher.entrypoint(builder, ext).await
68 })
69 }
70
71 pub fn run_with_components<N>(
77 mut self,
78 components: impl CliComponentsBuilder<N>,
79 launcher: impl AsyncFnOnce(
80 WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>,
81 Ext,
82 ) -> Result<()>,
83 ) -> Result<()>
84 where
85 N: CliNodeTypes<
86 Primitives: NodePrimitives<BlockHeader: CliHeader>,
87 ChainSpec: Hardforks + EthChainSpec,
88 >,
89 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
90 {
91 let runner = match self.runner.take() {
92 Some(runner) => runner,
93 None => CliRunner::try_default_runtime()?,
94 };
95
96 if let Some(chain_spec) = self.cli.command.chain_spec() {
98 self.cli.logs.log_file_directory =
99 self.cli.logs.log_file_directory.join(chain_spec.chain().to_string());
100 }
101
102 self.init_tracing()?;
103 let _ = install_prometheus_recorder();
105
106 run_commands_with::<C, Ext, Rpc, N>(self.cli, runner, components, launcher)
107 }
108
109 pub fn init_tracing(&mut self) -> Result<()> {
113 if self.guard.is_none() {
114 let layers = self.layers.take().unwrap_or_default();
115 self.guard = self.cli.logs.init_tracing_with_layers(layers)?;
116 info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.cli.logs.log_file_directory);
117 }
118 Ok(())
119 }
120}
121
122pub(crate) fn run_commands_with<C, Ext, Rpc, N>(
125 cli: Cli<C, Ext, Rpc>,
126 runner: CliRunner,
127 components: impl CliComponentsBuilder<N>,
128 launcher: impl AsyncFnOnce(
129 WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>,
130 Ext,
131 ) -> Result<()>,
132) -> Result<()>
133where
134 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
135 Ext: clap::Args + fmt::Debug,
136 Rpc: RpcModuleValidator,
137 N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: CliHeader>, ChainSpec: Hardforks>,
138{
139 match cli.command {
140 Commands::Node(command) => {
141 if let Some(http_api) = &command.rpc.http_api {
143 Rpc::validate_selection(http_api, "http.api").map_err(|e| eyre!("{e}"))?;
144 }
145 if let Some(ws_api) = &command.rpc.ws_api {
146 Rpc::validate_selection(ws_api, "ws.api").map_err(|e| eyre!("{e}"))?;
147 }
148
149 runner.run_command_until_exit(|ctx| {
150 command.execute(ctx, FnLauncher::new::<C, Ext>(launcher))
151 })
152 }
153 Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
154 Commands::InitState(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
155 Commands::Import(command) => {
156 runner.run_blocking_until_ctrl_c(command.execute::<N, _>(components))
157 }
158 Commands::ImportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
159 Commands::ExportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
160 Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
161 Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
162 Commands::Download(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
163 Commands::Stage(command) => {
164 runner.run_command_until_exit(|ctx| command.execute::<N, _>(ctx, components))
165 }
166 Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::<N>()),
167 Commands::Config(command) => runner.run_until_ctrl_c(command.execute()),
168 Commands::Recover(command) => {
169 runner.run_command_until_exit(|ctx| command.execute::<N>(ctx))
170 }
171 Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::<N>()),
172 #[cfg(feature = "dev")]
173 Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
174 Commands::ReExecute(command) => runner.run_until_ctrl_c(command.execute::<N>(components)),
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::chainspec::EthereumChainSpecParser;
182 use clap::Parser;
183 use reth_cli_commands::node::NoArgs;
184
185 #[test]
186 fn test_cli_app_creation() {
187 let args = vec!["reth", "config"];
188 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
189 let app = cli.configure();
190
191 assert!(app.runner.is_none());
193 assert!(app.layers.is_some());
194 assert!(app.guard.is_none());
195 }
196
197 #[test]
198 fn test_set_runner() {
199 let args = vec!["reth", "config"];
200 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
201 let mut app = cli.configure();
202
203 if let Ok(runner) = CliRunner::try_default_runtime() {
205 app.set_runner(runner);
206 assert!(app.runner.is_some());
207 }
208 }
209
210 #[test]
211 fn test_access_tracing_layers() {
212 let args = vec!["reth", "config"];
213 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
214 let mut app = cli.configure();
215
216 assert!(app.access_tracing_layers().is_ok());
218
219 app.layers = None;
221 assert!(app.access_tracing_layers().is_err());
222 }
223}