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