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, EthereumNode};
18use reth_node_metrics::recorder::install_prometheus_recorder;
19use reth_rpc_server_types::RpcModuleValidator;
20use reth_tasks::RayonConfig;
21use reth_tracing::{Layers, TracingGuards};
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<TracingGuards>,
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 jit_args = match &self.cli.command {
73 Commands::ReExecute(cmd) => cmd.jit.clone(),
74 _ => Default::default(),
75 };
76
77 let components = move |spec: Arc<ChainSpec>| {
78 let (evm_config, _) =
79 reth_node_ethereum::node::build_evm_config(spec.clone(), &jit_args, None)
80 .expect("failed to start revmc JIT backend");
81 (evm_config, Arc::new(EthBeaconConsensus::new(spec)))
82 };
83
84 self.run_with_components::<EthereumNode>(components, async move |builder, ext| {
85 launcher.entrypoint(builder, ext).await
86 })
87 }
88
89 pub fn run_with_components<N>(
95 mut self,
96 components: impl CliComponentsBuilder<N>,
97 launcher: impl AsyncFnOnce(
98 WithLaunchContext<NodeBuilder<DatabaseEnv, C::ChainSpec>>,
99 Ext,
100 ) -> Result<()>,
101 ) -> Result<()>
102 where
103 N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: HeaderMut>, ChainSpec: Hardforks>,
104 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
105 {
106 let runner = match self.runner.take() {
107 Some(runner) => runner,
108 None => {
109 let runtime_config = match &self.cli.command {
110 Commands::Node(command) => {
111 let config = reth_tasks::RuntimeConfig::default().with_rayon(RayonConfig {
112 reserved_cpu_cores: command.engine.reserved_cpu_cores,
113 proof_storage_worker_threads: command.engine.storage_worker_count,
114 proof_account_worker_threads: command.engine.account_worker_count,
115 prewarming_threads: command.engine.prewarming_threads,
116 ..Default::default()
117 });
118 if let Some(cpu_cores) = command.engine.cpu_cores {
119 config.with_cpu_cores(cpu_cores)
120 } else {
121 config
122 }
123 }
124 _ => reth_tasks::RuntimeConfig::default(),
125 };
126 CliRunner::try_with_runtime_config(runtime_config)?
127 }
128 };
129
130 if let Some(chain_spec) = self.cli.command.chain_spec() {
132 self.cli.logs.log_file_directory =
133 self.cli.logs.log_file_directory.join(chain_spec.chain().to_string());
134 }
135
136 if matches!(self.cli.command, Commands::Node(_)) {
138 self.cli.logs.apply_node_defaults();
139 }
140
141 self.init_tracing(&runner)?;
142
143 reth_tasks::utils::deprioritize_background_threads();
145
146 install_prometheus_recorder();
148
149 run_commands_with::<C, Ext, Rpc, N, SubCmd>(self.cli, runner, components, launcher)
150 }
151
152 pub fn init_tracing(&mut self, runner: &CliRunner) -> Result<()> {
156 if let Some(layers) = self.layers.take() {
157 self.guard = Some(self.cli.init_tracing(runner, layers)?);
158 }
159
160 Ok(())
161 }
162}
163
164pub(crate) fn run_commands_with<C, Ext, Rpc, N, SubCmd>(
167 cli: Cli<C, Ext, Rpc, SubCmd>,
168 runner: CliRunner,
169 components: impl CliComponentsBuilder<N>,
170 launcher: impl AsyncFnOnce(
171 WithLaunchContext<NodeBuilder<DatabaseEnv, C::ChainSpec>>,
172 Ext,
173 ) -> Result<()>,
174) -> Result<()>
175where
176 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
177 Ext: clap::Args + fmt::Debug,
178 Rpc: RpcModuleValidator,
179 N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: HeaderMut>, ChainSpec: Hardforks>,
180 SubCmd: ExtendedCommand + Subcommand + fmt::Debug,
181{
182 let rt = runner.runtime();
183
184 match cli.command {
185 Commands::Node(command) => {
186 if let Some(http_api) = &command.rpc.http_api {
188 Rpc::validate_selection(http_api, "http.api").map_err(|e| eyre!("{e}"))?;
189 }
190 if let Some(ws_api) = &command.rpc.ws_api {
191 Rpc::validate_selection(ws_api, "ws.api").map_err(|e| eyre!("{e}"))?;
192 }
193
194 runner.run_command_until_exit(|ctx| {
195 command.execute(ctx, FnLauncher::new::<C, Ext>(launcher))
196 })
197 }
198 Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>(rt)),
199 Commands::InitState(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>(rt)),
200 Commands::Import(command) => {
201 runner.run_blocking_until_ctrl_c(command.execute::<N, _>(components, rt))
202 }
203 Commands::ImportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>(rt)),
204 Commands::ExportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>(rt)),
205 Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
206 Commands::Db(command) => {
207 runner.run_blocking_command_until_exit(|ctx| command.execute::<N>(ctx))
208 }
209 Commands::Download(command) => runner
210 .run_blocking_until_ctrl_c(async move { command.execute::<N>().await.map(|_| ()) }),
211 Commands::SnapshotManifest(command) => command.execute(),
212 Commands::Stage(command) => {
213 runner.run_command_until_exit(|ctx| command.execute::<N, _>(ctx, components))
214 }
215 Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::<N>()),
216 Commands::Config(command) => runner.run_until_ctrl_c(command.execute()),
217 Commands::Prune(command) => runner.run_command_until_exit(|ctx| command.execute::<N>(ctx)),
218 #[cfg(feature = "dev")]
219 Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
220 Commands::ReExecute(command) => {
221 runner.run_until_ctrl_c(command.execute::<N>(components, rt))
222 }
223 Commands::Ext(command) => command.execute(runner),
224 }
225}
226
227pub trait ExtendedCommand {
232 fn execute(self, runner: CliRunner) -> Result<()>;
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use crate::chainspec::EthereumChainSpecParser;
240 use clap::Parser;
241 use reth_cli_commands::node::NoArgs;
242
243 #[test]
244 fn test_cli_app_creation() {
245 let args = vec!["reth", "config"];
246 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
247 let app = cli.configure();
248
249 assert!(app.runner.is_none());
251 assert!(app.layers.is_some());
252 assert!(app.guard.is_none());
253 }
254
255 #[test]
256 fn test_set_runner() {
257 let args = vec!["reth", "config"];
258 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
259 let mut app = cli.configure();
260
261 if let Ok(runner) = CliRunner::try_default_runtime() {
263 app.set_runner(runner);
264 assert!(app.runner.is_some());
265 }
266 }
267
268 #[test]
269 fn test_access_tracing_layers() {
270 let args = vec!["reth", "config"];
271 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
272 let mut app = cli.configure();
273
274 assert!(app.access_tracing_layers().is_ok());
276
277 app.layers = None;
279 assert!(app.access_tracing_layers().is_err());
280 }
281}