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, CliNodeTypes, HeaderMut},
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};
18
19#[derive(Debug)]
21pub struct CliApp<Spec: ChainSpecParser, Ext: clap::Args + fmt::Debug, Rpc: RpcModuleValidator> {
22 cli: Cli<Spec, Ext, Rpc>,
23 runner: Option<CliRunner>,
24 layers: Option<Layers>,
25 guard: Option<FileWorkerGuard>,
26}
27
28impl<C, Ext, Rpc> CliApp<C, Ext, Rpc>
29where
30 C: ChainSpecParser,
31 Ext: clap::Args + fmt::Debug,
32 Rpc: RpcModuleValidator,
33{
34 pub(crate) fn new(cli: Cli<C, Ext, Rpc>) -> Self {
35 Self { cli, runner: None, layers: Some(Layers::new()), guard: None }
36 }
37
38 pub fn set_runner(&mut self, runner: CliRunner) {
42 self.runner = Some(runner);
43 }
44
45 pub fn access_tracing_layers(&mut self) -> Result<&mut Layers> {
50 self.layers.as_mut().ok_or_else(|| eyre!("Tracing already initialized"))
51 }
52
53 pub fn run(self, launcher: impl Launcher<C, Ext>) -> Result<()>
58 where
59 C: ChainSpecParser<ChainSpec = ChainSpec>,
60 {
61 let components = |spec: Arc<ChainSpec>| {
62 (EthEvmConfig::ethereum(spec.clone()), Arc::new(EthBeaconConsensus::new(spec)))
63 };
64
65 self.run_with_components::<EthereumNode>(components, |builder, ext| async move {
66 launcher.entrypoint(builder, ext).await
67 })
68 }
69
70 pub fn run_with_components<N>(
76 mut self,
77 components: impl CliComponentsBuilder<N>,
78 launcher: impl AsyncFnOnce(
79 WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>,
80 Ext,
81 ) -> Result<()>,
82 ) -> Result<()>
83 where
84 N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: HeaderMut>, ChainSpec: Hardforks>,
85 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
86 {
87 let runner = match self.runner.take() {
88 Some(runner) => runner,
89 None => CliRunner::try_default_runtime()?,
90 };
91
92 if let Some(chain_spec) = self.cli.command.chain_spec() {
94 self.cli.logs.log_file_directory =
95 self.cli.logs.log_file_directory.join(chain_spec.chain().to_string());
96 }
97
98 self.init_tracing(&runner)?;
99
100 let _ = install_prometheus_recorder();
102
103 run_commands_with::<C, Ext, Rpc, N>(self.cli, runner, components, launcher)
104 }
105
106 pub fn init_tracing(&mut self, runner: &CliRunner) -> Result<()> {
110 if self.guard.is_none() {
111 self.guard = self.cli.init_tracing(runner, self.layers.take().unwrap_or_default())?;
112 }
113
114 Ok(())
115 }
116}
117
118pub(crate) fn run_commands_with<C, Ext, Rpc, N>(
121 cli: Cli<C, Ext, Rpc>,
122 runner: CliRunner,
123 components: impl CliComponentsBuilder<N>,
124 launcher: impl AsyncFnOnce(
125 WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>,
126 Ext,
127 ) -> Result<()>,
128) -> Result<()>
129where
130 C: ChainSpecParser<ChainSpec = N::ChainSpec>,
131 Ext: clap::Args + fmt::Debug,
132 Rpc: RpcModuleValidator,
133 N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: HeaderMut>, ChainSpec: Hardforks>,
134{
135 match cli.command {
136 Commands::Node(command) => {
137 if let Some(http_api) = &command.rpc.http_api {
139 Rpc::validate_selection(http_api, "http.api").map_err(|e| eyre!("{e}"))?;
140 }
141 if let Some(ws_api) = &command.rpc.ws_api {
142 Rpc::validate_selection(ws_api, "ws.api").map_err(|e| eyre!("{e}"))?;
143 }
144
145 runner.run_command_until_exit(|ctx| {
146 command.execute(ctx, FnLauncher::new::<C, Ext>(launcher))
147 })
148 }
149 Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
150 Commands::InitState(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
151 Commands::Import(command) => {
152 runner.run_blocking_until_ctrl_c(command.execute::<N, _>(components))
153 }
154 Commands::ImportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
155 Commands::ExportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
156 Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
157 Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
158 Commands::Download(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
159 Commands::Stage(command) => {
160 runner.run_command_until_exit(|ctx| command.execute::<N, _>(ctx, components))
161 }
162 Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::<N>()),
163 Commands::Config(command) => runner.run_until_ctrl_c(command.execute()),
164 Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::<N>()),
165 #[cfg(feature = "dev")]
166 Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
167 Commands::ReExecute(command) => runner.run_until_ctrl_c(command.execute::<N>(components)),
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::chainspec::EthereumChainSpecParser;
175 use clap::Parser;
176 use reth_cli_commands::node::NoArgs;
177
178 #[test]
179 fn test_cli_app_creation() {
180 let args = vec!["reth", "config"];
181 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
182 let app = cli.configure();
183
184 assert!(app.runner.is_none());
186 assert!(app.layers.is_some());
187 assert!(app.guard.is_none());
188 }
189
190 #[test]
191 fn test_set_runner() {
192 let args = vec!["reth", "config"];
193 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
194 let mut app = cli.configure();
195
196 if let Ok(runner) = CliRunner::try_default_runtime() {
198 app.set_runner(runner);
199 assert!(app.runner.is_some());
200 }
201 }
202
203 #[test]
204 fn test_access_tracing_layers() {
205 let args = vec!["reth", "config"];
206 let cli = Cli::<EthereumChainSpecParser, NoArgs>::try_parse_from(args).unwrap();
207 let mut app = cli.configure();
208
209 assert!(app.access_tracing_layers().is_ok());
211
212 app.layers = None;
214 assert!(app.access_tracing_layers().is_err());
215 }
216}