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, 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 = "Extension")]
115 pub ext: Ext,
116}
117
118impl<C: ChainSpecParser> NodeCommand<C> {
119 pub fn parse_args() -> Self {
121 Self::parse()
122 }
123
124 pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
126 where
127 I: IntoIterator<Item = T>,
128 T: Into<OsString> + Clone,
129 {
130 Self::try_parse_from(itr)
131 }
132}
133
134impl<C, Ext> NodeCommand<C, Ext>
135where
136 C: ChainSpecParser,
137 C::ChainSpec: EthChainSpec + EthereumHardforks,
138 Ext: clap::Args + fmt::Debug,
139{
140 pub async fn execute<L>(self, ctx: CliContext, launcher: L) -> eyre::Result<()>
145 where
146 L: Launcher<C, Ext>,
147 {
148 tracing::info!(target: "reth::cli", version = ?version::version_metadata().short_version, "Starting reth");
149
150 let Self {
151 datadir,
152 config,
153 chain,
154 metrics,
155 instance,
156 with_unused_ports,
157 network,
158 rpc,
159 txpool,
160 builder,
161 debug,
162 db,
163 dev,
164 pruning,
165 ext,
166 engine,
167 era,
168 } = self;
169
170 let mut node_config = NodeConfig {
172 datadir,
173 config,
174 chain,
175 metrics,
176 instance,
177 network,
178 rpc,
179 txpool,
180 builder,
181 debug,
182 db,
183 dev,
184 pruning,
185 engine,
186 era,
187 };
188
189 let data_dir = node_config.datadir();
190 let db_path = data_dir.db();
191
192 tracing::info!(target: "reth::cli", path = ?db_path, "Opening database");
193 let database = Arc::new(init_db(db_path.clone(), self.db.database_args())?.with_metrics());
194
195 if with_unused_ports {
196 node_config = node_config.with_unused_ports();
197 }
198
199 let builder = NodeBuilder::new(node_config)
200 .with_database(database)
201 .with_launch_context(ctx.task_executor);
202
203 launcher.entrypoint(builder, ext).await
204 }
205}
206
207impl<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> NodeCommand<C, Ext> {
208 pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
210 Some(&self.chain)
211 }
212}
213
214#[derive(Debug, Clone, Copy, Default, Args)]
216#[non_exhaustive]
217pub struct NoArgs;
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use reth_discv4::DEFAULT_DISCOVERY_PORT;
223 use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
224 use std::{
225 net::{IpAddr, Ipv4Addr, SocketAddr},
226 path::Path,
227 };
228
229 #[test]
230 fn parse_help_node_command() {
231 let err = NodeCommand::<EthereumChainSpecParser>::try_parse_args_from(["reth", "--help"])
232 .unwrap_err();
233 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
234 }
235
236 #[test]
237 fn parse_common_node_command_chain_args() {
238 for chain in SUPPORTED_CHAINS {
239 let args: NodeCommand<EthereumChainSpecParser> =
240 NodeCommand::parse_from(["reth", "--chain", chain]);
241 assert_eq!(args.chain.chain, chain.parse::<reth_chainspec::Chain>().unwrap());
242 }
243 }
244
245 #[test]
246 fn parse_discovery_addr() {
247 let cmd: NodeCommand<EthereumChainSpecParser> =
248 NodeCommand::try_parse_args_from(["reth", "--discovery.addr", "127.0.0.1"]).unwrap();
249 assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
250 }
251
252 #[test]
253 fn parse_addr() {
254 let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([
255 "reth",
256 "--discovery.addr",
257 "127.0.0.1",
258 "--addr",
259 "127.0.0.1",
260 ])
261 .unwrap();
262 assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
263 assert_eq!(cmd.network.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
264 }
265
266 #[test]
267 fn parse_discovery_port() {
268 let cmd: NodeCommand<EthereumChainSpecParser> =
269 NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300"]).unwrap();
270 assert_eq!(cmd.network.discovery.port, 300);
271 }
272
273 #[test]
274 fn parse_port() {
275 let cmd: NodeCommand<EthereumChainSpecParser> =
276 NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300", "--port", "99"])
277 .unwrap();
278 assert_eq!(cmd.network.discovery.port, 300);
279 assert_eq!(cmd.network.port, 99);
280 }
281
282 #[test]
283 fn parse_metrics_port() {
284 let cmd: NodeCommand<EthereumChainSpecParser> =
285 NodeCommand::try_parse_args_from(["reth", "--metrics", "9001"]).unwrap();
286 assert_eq!(
287 cmd.metrics.prometheus,
288 Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))
289 );
290
291 let cmd: NodeCommand<EthereumChainSpecParser> =
292 NodeCommand::try_parse_args_from(["reth", "--metrics", ":9001"]).unwrap();
293 assert_eq!(
294 cmd.metrics.prometheus,
295 Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))
296 );
297
298 let cmd: NodeCommand<EthereumChainSpecParser> =
299 NodeCommand::try_parse_args_from(["reth", "--metrics", "localhost:9001"]).unwrap();
300 assert_eq!(
301 cmd.metrics.prometheus,
302 Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))
303 );
304 }
305
306 #[test]
307 fn parse_config_path() {
308 let cmd: NodeCommand<EthereumChainSpecParser> =
309 NodeCommand::try_parse_args_from(["reth", "--config", "my/path/to/reth.toml"]).unwrap();
310 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
312 let config_path = cmd.config.unwrap_or_else(|| data_dir.config());
313 assert_eq!(config_path, Path::new("my/path/to/reth.toml"));
314
315 let cmd: NodeCommand<EthereumChainSpecParser> =
316 NodeCommand::try_parse_args_from(["reth"]).unwrap();
317
318 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
320 let config_path = cmd.config.clone().unwrap_or_else(|| data_dir.config());
321 let end = format!("{}/reth.toml", SUPPORTED_CHAINS[0]);
322 assert!(config_path.ends_with(end), "{:?}", cmd.config);
323 }
324
325 #[test]
326 fn parse_db_path() {
327 let cmd: NodeCommand<EthereumChainSpecParser> =
328 NodeCommand::try_parse_args_from(["reth"]).unwrap();
329 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
330
331 let db_path = data_dir.db();
332 let end = format!("reth/{}/db", SUPPORTED_CHAINS[0]);
333 assert!(db_path.ends_with(end), "{:?}", cmd.config);
334
335 let cmd: NodeCommand<EthereumChainSpecParser> =
336 NodeCommand::try_parse_args_from(["reth", "--datadir", "my/custom/path"]).unwrap();
337 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
338
339 let db_path = data_dir.db();
340 assert_eq!(db_path, Path::new("my/custom/path/db"));
341 }
342
343 #[test]
344 fn parse_instance() {
345 let mut cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from(["reth"]);
346 cmd.rpc.adjust_instance_ports(cmd.instance);
347 cmd.network.port = DEFAULT_DISCOVERY_PORT;
348 assert_eq!(cmd.rpc.auth_port, 8551);
350 assert_eq!(cmd.rpc.http_port, 8545);
351 assert_eq!(cmd.rpc.ws_port, 8546);
352 assert_eq!(cmd.network.port, 30303);
354
355 let mut cmd: NodeCommand<EthereumChainSpecParser> =
356 NodeCommand::parse_from(["reth", "--instance", "2"]);
357 cmd.rpc.adjust_instance_ports(cmd.instance);
358 cmd.network.port = DEFAULT_DISCOVERY_PORT + 2 - 1;
359 assert_eq!(cmd.rpc.auth_port, 8651);
361 assert_eq!(cmd.rpc.http_port, 8544);
362 assert_eq!(cmd.rpc.ws_port, 8548);
363 assert_eq!(cmd.network.port, 30304);
365
366 let mut cmd: NodeCommand<EthereumChainSpecParser> =
367 NodeCommand::parse_from(["reth", "--instance", "3"]);
368 cmd.rpc.adjust_instance_ports(cmd.instance);
369 cmd.network.port = DEFAULT_DISCOVERY_PORT + 3 - 1;
370 assert_eq!(cmd.rpc.auth_port, 8751);
372 assert_eq!(cmd.rpc.http_port, 8543);
373 assert_eq!(cmd.rpc.ws_port, 8550);
374 assert_eq!(cmd.network.port, 30305);
376 }
377
378 #[test]
379 fn parse_with_unused_ports() {
380 let cmd: NodeCommand<EthereumChainSpecParser> =
381 NodeCommand::parse_from(["reth", "--with-unused-ports"]);
382 assert!(cmd.with_unused_ports);
383 }
384
385 #[test]
386 fn with_unused_ports_conflicts_with_instance() {
387 let err = NodeCommand::<EthereumChainSpecParser>::try_parse_args_from([
388 "reth",
389 "--with-unused-ports",
390 "--instance",
391 "2",
392 ])
393 .unwrap_err();
394 assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
395 }
396
397 #[test]
398 fn with_unused_ports_check_zero() {
399 let mut cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from(["reth"]);
400 cmd.rpc = cmd.rpc.with_unused_ports();
401 cmd.network = cmd.network.with_unused_ports();
402
403 assert_eq!(cmd.rpc.auth_port, 0);
405 assert_eq!(cmd.rpc.http_port, 0);
406 assert_eq!(cmd.rpc.ws_port, 0);
407
408 assert_eq!(cmd.network.port, 0);
410 assert_eq!(cmd.network.discovery.port, 0);
411
412 assert_ne!(cmd.rpc.ipcpath, String::from("/tmp/reth.ipc"));
414 }
415}