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