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