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_tasks::RayonConfig;
21use reth_tracing::{FileWorkerGuard, Layers};
22use std::{fmt, sync::Arc};
23
24#[derive(Debug)]
26pub struct CliApp<
27 Spec: ChainSpecParser,
28 Ext: clap::Args + fmt::Debug,
29 Rpc: RpcModuleValidator,
30 SubCmd: Subcommand + fmt::Debug = NoSubCmd,
31> {
32 cli: Cli<Spec, Ext, Rpc, SubCmd>,
33 runner: Option<CliRunner>,
34 layers: Option<Layers>,
35 guard: Option<FileWorkerGuard>,
36}
37
38impl<C, Ext, Rpc, SubCmd> CliApp<C, Ext, Rpc, SubCmd>
39where
40 C: ChainSpecParser,
41 Ext: clap::Args + fmt::Debug,
42 Rpc: RpcModuleValidator,
43 SubCmd: ExtendedCommand + Subcommand + fmt::Debug,
44{
45 pub(crate) fn new(cli: Cli<C, Ext, Rpc, SubCmd>) -> Self {
46 Self { cli, runner: None, layers: Some(Layers::new()), guard: None }
47 }
48
49 pub fn set_runner(&mut self, runner: CliRunner) {
53 self.runner = Some(runner);
54 }
55
56 pub fn access_tracing_layers(&mut self) -> Result<&mut Layers> {
61 self.layers.as_mut().ok_or_else(|| eyre!("Tracing already initialized"))
62 }
63
64 pub fn run(self, launcher: impl Launcher<C, Ext>) -> Result<()>
69 where
70 C: ChainSpecParser<ChainSpec = ChainSpec>,
71 {
72 let components = |spec: Arc<ChainSpec>| {
73 (EthEvmConfig::ethereum(spec.clone()), Arc::new(EthBeaconConsensus::new(spec)))
74 };
75
76 self.run_with_components::<EthereumNode>(components, |builder, ext| async move {
77 launcher.entrypoint(builder, ext).await
78 })
79 }
80
81 pub fn run_with_components<N>(
87 mut self,
88 components: impl CliComponentsBuilder<N>,
89 launcher: impl AsyncFnOnce(
90 WithLaunchContext<NodeBuilder<DatabaseEnv, C::ChainSpec>>,
91 Ext,
92 ) -> Result<()>,
93 ) -> Result<()>
94 where
95 N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: HeaderMut>, ChainSpec: Hardforks>,
96 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
97 {
98 let runner = match self.runner.take() {
99 Some(runner) => runner,
100 None => CliRunner::try_default_runtime()?,
101 };
102
103 if let Some(chain_spec) = self.cli.command.chain_spec() {
105 self.cli.logs.log_file_directory =
106 self.cli.logs.log_file_directory.join(chain_spec.chain().to_string());
107 }
108
109 self.init_tracing(&runner)?;
110
111 install_prometheus_recorder();
113
114 run_commands_with::<C, Ext, Rpc, N, SubCmd>(self.cli, runner, components, launcher)
115 }
116
117 pub fn init_tracing(&mut self, runner: &CliRunner) -> Result<()> {
121 if self.guard.is_none() {
122 self.guard = self.cli.init_tracing(runner, self.layers.take().unwrap_or_default())?;
123 }
124
125 Ok(())
126 }
127}
128
129pub(crate) fn run_commands_with<C, Ext, Rpc, N, SubCmd>(
132 cli: Cli<C, Ext, Rpc, SubCmd>,
133 runner: CliRunner,
134 components: impl CliComponentsBuilder<N>,
135 launcher: impl AsyncFnOnce(
136 WithLaunchContext<NodeBuilder<DatabaseEnv, C::ChainSpec>>,
137 Ext,
138 ) -> Result<()>,
139) -> Result<()>
140where
141 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
142 Ext: clap::Args + fmt::Debug,
143 Rpc: RpcModuleValidator,
144 N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: HeaderMut>, ChainSpec: Hardforks>,
145 SubCmd: ExtendedCommand + Subcommand + fmt::Debug,
146{
147 match cli.command {
148 Commands::Node(command) => {
149 if let Some(http_api) = &command.rpc.http_api {
151 Rpc::validate_selection(http_api, "http.api").map_err(|e| eyre!("{e}"))?;
152 }
153 if let Some(ws_api) = &command.rpc.ws_api {
154 Rpc::validate_selection(ws_api, "ws.api").map_err(|e| eyre!("{e}"))?;
155 }
156
157 let rayon_config = RayonConfig {
158 reserved_cpu_cores: command.engine.reserved_cpu_cores,
159 proof_storage_worker_threads: command.engine.storage_worker_count,
160 proof_account_worker_threads: command.engine.account_worker_count,
161 ..Default::default()
162 };
163 let runner = CliRunner::try_with_runtime_config(
164 reth_tasks::RuntimeConfig::default().with_rayon(rayon_config),
165 )?;
166
167 runner.run_command_until_exit(|ctx| {
168 command.execute(ctx, FnLauncher::new::<C, Ext>(launcher))
169 })
170 }
171 Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
172 Commands::InitState(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
173 Commands::Import(command) => {
174 runner.run_blocking_until_ctrl_c(command.execute::<N, _>(components))
175 }
176 Commands::ImportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
177 Commands::ExportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
178 Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
179 Commands::Db(command) => {
180 runner.run_blocking_command_until_exit(|ctx| command.execute::<N>(ctx))
181 }
182 Commands::Download(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
183 Commands::Stage(command) => {
184 runner.run_command_until_exit(|ctx| command.execute::<N, _>(ctx, components))
185 }
186 Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::<N>()),
187 Commands::Config(command) => runner.run_until_ctrl_c(command.execute()),
188 Commands::Prune(command) => runner.run_command_until_exit(|ctx| command.execute::<N>(ctx)),
189 #[cfg(feature = "dev")]
190 Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
191 Commands::ReExecute(command) => runner.run_until_ctrl_c(command.execute::<N>(components)),
192 Commands::Ext(command) => command.execute(runner),
193 }
194}
195
196pub trait ExtendedCommand {
201 fn execute(self, runner: CliRunner) -> Result<()>;
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::chainspec::EthereumChainSpecParser;
209 use clap::Parser;
210 use reth_cli_commands::node::NoArgs;
211
212 #[test]
213 fn test_cli_app_creation() {
214 let args = vec!["reth", "config"];
215 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
216 let app = cli.configure();
217
218 assert!(app.runner.is_none());
220 assert!(app.layers.is_some());
221 assert!(app.guard.is_none());
222 }
223
224 #[test]
225 fn test_set_runner() {
226 let args = vec!["reth", "config"];
227 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
228 let mut app = cli.configure();
229
230 if let Ok(runner) = CliRunner::try_default_runtime() {
232 app.set_runner(runner);
233 assert!(app.runner.is_some());
234 }
235 }
236
237 #[test]
238 fn test_access_tracing_layers() {
239 let args = vec!["reth", "config"];
240 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
241 let mut app = cli.configure();
242
243 assert!(app.access_tracing_layers().is_ok());
245
246 app.layers = None;
248 assert!(app.access_tracing_layers().is_err());
249 }
250}