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