1use crate::launcher::Launcher;
4use clap::{value_parser, Args, Parser};
5use reth_chainspec::{EthChainSpec, EthereumHardforks};
6use reth_cli::chainspec::ChainSpecParser;
7use reth_cli_runner::CliContext;
8use reth_db::init_db;
9use reth_node_builder::NodeBuilder;
10use reth_node_core::{
11 args::{
12 DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, EngineArgs, EraArgs, MetricArgs,
13 NetworkArgs, PayloadBuilderArgs, PruningArgs, RpcServerArgs, StaticFilesArgs, TxPoolArgs,
14 },
15 node_config::NodeConfig,
16 version,
17};
18use std::{ffi::OsString, fmt, path::PathBuf, sync::Arc};
19
20#[derive(Debug, Parser)]
22pub struct NodeCommand<C: ChainSpecParser, Ext: clap::Args + fmt::Debug = NoArgs> {
23 #[arg(long, value_name = "FILE", verbatim_doc_comment)]
25 pub config: Option<PathBuf>,
26
27 #[arg(
31 long,
32 value_name = "CHAIN_OR_PATH",
33 long_help = C::help_message(),
34 default_value = C::default_value(),
35 default_value_if("dev", "true", "dev"),
36 value_parser = C::parser(),
37 required = false,
38 )]
39 pub chain: Arc<C::ChainSpec>,
40
41 #[command(flatten)]
43 pub metrics: MetricArgs,
44
45 #[arg(long, value_name = "INSTANCE", global = true, value_parser = value_parser!(u16).range(1..=200))]
60 pub instance: Option<u16>,
61
62 #[arg(long, conflicts_with = "instance", global = true)]
67 pub with_unused_ports: bool,
68
69 #[command(flatten)]
71 pub datadir: DatadirArgs,
72
73 #[command(flatten)]
75 pub network: NetworkArgs,
76
77 #[command(flatten)]
79 pub rpc: RpcServerArgs,
80
81 #[command(flatten)]
83 pub txpool: TxPoolArgs,
84
85 #[command(flatten)]
87 pub builder: PayloadBuilderArgs,
88
89 #[command(flatten)]
91 pub debug: DebugArgs,
92
93 #[command(flatten)]
95 pub db: DatabaseArgs,
96
97 #[command(flatten)]
99 pub dev: DevArgs,
100
101 #[command(flatten)]
103 pub pruning: PruningArgs,
104
105 #[command(flatten, next_help_heading = "Engine")]
107 pub engine: EngineArgs,
108
109 #[command(flatten, next_help_heading = "ERA")]
111 pub era: EraArgs,
112
113 #[command(flatten, next_help_heading = "Static Files")]
115 pub static_files: StaticFilesArgs,
116
117 #[command(flatten, next_help_heading = "Extension")]
119 pub ext: Ext,
120}
121
122impl<C: ChainSpecParser> NodeCommand<C> {
123 pub fn parse_args() -> Self {
125 Self::parse()
126 }
127
128 pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
130 where
131 I: IntoIterator<Item = T>,
132 T: Into<OsString> + Clone,
133 {
134 Self::try_parse_from(itr)
135 }
136}
137
138impl<C, Ext> NodeCommand<C, Ext>
139where
140 C: ChainSpecParser,
141 C::ChainSpec: EthChainSpec + EthereumHardforks,
142 Ext: clap::Args + fmt::Debug,
143{
144 pub async fn execute<L>(self, ctx: CliContext, launcher: L) -> eyre::Result<()>
149 where
150 L: Launcher<C, Ext>,
151 {
152 tracing::info!(target: "reth::cli", version = ?version::version_metadata().short_version, "Starting {}", version::version_metadata().name_client);
153
154 let Self {
155 datadir,
156 config,
157 chain,
158 metrics,
159 instance,
160 with_unused_ports,
161 network,
162 rpc,
163 txpool,
164 builder,
165 debug,
166 db,
167 dev,
168 pruning,
169 engine,
170 era,
171 static_files,
172 ext,
173 } = self;
174
175 let mut node_config = NodeConfig {
177 datadir,
178 config,
179 chain,
180 metrics,
181 instance,
182 network,
183 rpc,
184 txpool,
185 builder,
186 debug,
187 db,
188 dev,
189 pruning,
190 engine,
191 era,
192 static_files,
193 };
194
195 let data_dir = node_config.datadir();
196 let db_path = data_dir.db();
197
198 tracing::info!(target: "reth::cli", path = ?db_path, "Opening database");
199 let database = Arc::new(init_db(db_path.clone(), self.db.database_args())?.with_metrics());
200
201 if with_unused_ports {
202 node_config = node_config.with_unused_ports();
203 }
204
205 let builder = NodeBuilder::new(node_config)
206 .with_database(database)
207 .with_launch_context(ctx.task_executor);
208
209 launcher.entrypoint(builder, ext).await
210 }
211}
212
213impl<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> NodeCommand<C, Ext> {
214 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
216 Some(&self.chain)
217 }
218}
219
220#[derive(Debug, Clone, Copy, Default, Args)]
222#[non_exhaustive]
223pub struct NoArgs;
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use reth_discv4::DEFAULT_DISCOVERY_PORT;
229 use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
230 use std::{
231 net::{IpAddr, Ipv4Addr, SocketAddr},
232 path::Path,
233 };
234
235 #[test]
236 fn parse_help_node_command() {
237 let err = NodeCommand::<EthereumChainSpecParser>::try_parse_args_from(["reth", "--help"])
238 .unwrap_err();
239 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
240 }
241
242 #[test]
243 fn parse_common_node_command_chain_args() {
244 for chain in SUPPORTED_CHAINS {
245 let args: NodeCommand<EthereumChainSpecParser> =
246 NodeCommand::parse_from(["reth", "--chain", chain]);
247 assert_eq!(args.chain.chain, chain.parse::<reth_chainspec::Chain>().unwrap());
248 }
249 }
250
251 #[test]
252 fn parse_discovery_addr() {
253 let cmd: NodeCommand<EthereumChainSpecParser> =
254 NodeCommand::try_parse_args_from(["reth", "--discovery.addr", "127.0.0.1"]).unwrap();
255 assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
256 }
257
258 #[test]
259 fn parse_addr() {
260 let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([
261 "reth",
262 "--discovery.addr",
263 "127.0.0.1",
264 "--addr",
265 "127.0.0.1",
266 ])
267 .unwrap();
268 assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
269 assert_eq!(cmd.network.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
270 }
271
272 #[test]
273 fn parse_discovery_port() {
274 let cmd: NodeCommand<EthereumChainSpecParser> =
275 NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300"]).unwrap();
276 assert_eq!(cmd.network.discovery.port, 300);
277 }
278
279 #[test]
280 fn parse_port() {
281 let cmd: NodeCommand<EthereumChainSpecParser> =
282 NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300", "--port", "99"])
283 .unwrap();
284 assert_eq!(cmd.network.discovery.port, 300);
285 assert_eq!(cmd.network.port, 99);
286 }
287
288 #[test]
289 fn parse_metrics_port() {
290 let cmd: NodeCommand<EthereumChainSpecParser> =
291 NodeCommand::try_parse_args_from(["reth", "--metrics", "9001"]).unwrap();
292 assert_eq!(
293 cmd.metrics.prometheus,
294 Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))
295 );
296
297 let cmd: NodeCommand<EthereumChainSpecParser> =
298 NodeCommand::try_parse_args_from(["reth", "--metrics", ":9001"]).unwrap();
299 assert_eq!(
300 cmd.metrics.prometheus,
301 Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))
302 );
303
304 let cmd: NodeCommand<EthereumChainSpecParser> =
305 NodeCommand::try_parse_args_from(["reth", "--metrics", "localhost:9001"]).unwrap();
306 assert_eq!(
307 cmd.metrics.prometheus,
308 Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))
309 );
310 }
311
312 #[test]
313 fn parse_config_path() {
314 let cmd: NodeCommand<EthereumChainSpecParser> =
315 NodeCommand::try_parse_args_from(["reth", "--config", "my/path/to/reth.toml"]).unwrap();
316 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
318 let config_path = cmd.config.unwrap_or_else(|| data_dir.config());
319 assert_eq!(config_path, Path::new("my/path/to/reth.toml"));
320
321 let cmd: NodeCommand<EthereumChainSpecParser> =
322 NodeCommand::try_parse_args_from(["reth"]).unwrap();
323
324 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
326 let config_path = cmd.config.clone().unwrap_or_else(|| data_dir.config());
327 let end = format!("{}/reth.toml", SUPPORTED_CHAINS[0]);
328 assert!(config_path.ends_with(end), "{:?}", cmd.config);
329 }
330
331 #[test]
332 fn parse_db_path() {
333 let cmd: NodeCommand<EthereumChainSpecParser> =
334 NodeCommand::try_parse_args_from(["reth"]).unwrap();
335 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
336
337 let db_path = data_dir.db();
338 let end = format!("reth/{}/db", SUPPORTED_CHAINS[0]);
339 assert!(db_path.ends_with(end), "{:?}", cmd.config);
340
341 let cmd: NodeCommand<EthereumChainSpecParser> =
342 NodeCommand::try_parse_args_from(["reth", "--datadir", "my/custom/path"]).unwrap();
343 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
344
345 let db_path = data_dir.db();
346 assert_eq!(db_path, Path::new("my/custom/path/db"));
347 }
348
349 #[test]
350 fn parse_instance() {
351 let mut cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from(["reth"]);
352 cmd.rpc.adjust_instance_ports(cmd.instance);
353 cmd.network.port = DEFAULT_DISCOVERY_PORT;
354 assert_eq!(cmd.rpc.auth_port, 8551);
356 assert_eq!(cmd.rpc.http_port, 8545);
357 assert_eq!(cmd.rpc.ws_port, 8546);
358 assert_eq!(cmd.network.port, 30303);
360
361 let mut cmd: NodeCommand<EthereumChainSpecParser> =
362 NodeCommand::parse_from(["reth", "--instance", "2"]);
363 cmd.rpc.adjust_instance_ports(cmd.instance);
364 cmd.network.port = DEFAULT_DISCOVERY_PORT + 2 - 1;
365 assert_eq!(cmd.rpc.auth_port, 8651);
367 assert_eq!(cmd.rpc.http_port, 8544);
368 assert_eq!(cmd.rpc.ws_port, 8548);
369 assert_eq!(cmd.network.port, 30304);
371
372 let mut cmd: NodeCommand<EthereumChainSpecParser> =
373 NodeCommand::parse_from(["reth", "--instance", "3"]);
374 cmd.rpc.adjust_instance_ports(cmd.instance);
375 cmd.network.port = DEFAULT_DISCOVERY_PORT + 3 - 1;
376 assert_eq!(cmd.rpc.auth_port, 8751);
378 assert_eq!(cmd.rpc.http_port, 8543);
379 assert_eq!(cmd.rpc.ws_port, 8550);
380 assert_eq!(cmd.network.port, 30305);
382 }
383
384 #[test]
385 fn parse_with_unused_ports() {
386 let cmd: NodeCommand<EthereumChainSpecParser> =
387 NodeCommand::parse_from(["reth", "--with-unused-ports"]);
388 assert!(cmd.with_unused_ports);
389 }
390
391 #[test]
392 fn with_unused_ports_conflicts_with_instance() {
393 let err = NodeCommand::<EthereumChainSpecParser>::try_parse_args_from([
394 "reth",
395 "--with-unused-ports",
396 "--instance",
397 "2",
398 ])
399 .unwrap_err();
400 assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
401 }
402
403 #[test]
404 fn with_unused_ports_check_zero() {
405 let mut cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from(["reth"]);
406 cmd.rpc = cmd.rpc.with_unused_ports();
407 cmd.network = cmd.network.with_unused_ports();
408
409 assert_eq!(cmd.rpc.auth_port, 0);
411 assert_eq!(cmd.rpc.http_port, 0);
412 assert_eq!(cmd.rpc.ws_port, 0);
413
414 assert_eq!(cmd.network.port, 0);
416 assert_eq!(cmd.network.discovery.port, 0);
417
418 assert_ne!(cmd.rpc.ipcpath, String::from("/tmp/reth.ipc"));
420 }
421}