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, EnclaveArgs, EngineArgs, 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::SUPPORTED_CHAINS[0],
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(..=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 = "Extension")]
114 pub ext: Ext,
115
116 #[command(flatten)]
118 pub enclave: EnclaveArgs,
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::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 enclave,
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 enclave,
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#[derive(Debug, Clone, Copy, Default, Args)]
218#[non_exhaustive]
219pub struct NoArgs;
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use reth_discv4::DEFAULT_DISCOVERY_PORT;
225 use reth_ethereum_cli::chainspec::{EthereumChainSpecParser, SUPPORTED_CHAINS};
226 use std::{
227 net::{IpAddr, Ipv4Addr},
228 path::Path,
229 };
230
231 #[test]
232 fn parse_help_node_command() {
233 let err = NodeCommand::<EthereumChainSpecParser>::try_parse_args_from(["reth", "--help"])
234 .unwrap_err();
235 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
236 }
237
238 #[test]
239 fn parse_common_node_command_chain_args() {
240 for chain in SUPPORTED_CHAINS {
241 let args: NodeCommand<EthereumChainSpecParser> =
242 NodeCommand::parse_from(["reth", "--chain", chain]);
243 assert_eq!(args.chain.chain, chain.parse::<reth_chainspec::Chain>().unwrap());
244 }
245 }
246
247 #[test]
248 fn parse_discovery_addr() {
249 let cmd: NodeCommand<EthereumChainSpecParser> =
250 NodeCommand::try_parse_args_from(["reth", "--discovery.addr", "127.0.0.1"]).unwrap();
251 assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
252 }
253
254 #[test]
255 fn parse_addr() {
256 let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([
257 "reth",
258 "--discovery.addr",
259 "127.0.0.1",
260 "--addr",
261 "127.0.0.1",
262 ])
263 .unwrap();
264 assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
265 assert_eq!(cmd.network.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
266 }
267
268 #[test]
269 fn parse_discovery_port() {
270 let cmd: NodeCommand<EthereumChainSpecParser> =
271 NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300"]).unwrap();
272 assert_eq!(cmd.network.discovery.port, 300);
273 }
274
275 #[test]
276 fn parse_port() {
277 let cmd: NodeCommand<EthereumChainSpecParser> =
278 NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300", "--port", "99"])
279 .unwrap();
280 assert_eq!(cmd.network.discovery.port, 300);
281 assert_eq!(cmd.network.port, 99);
282 }
283
284 #[test]
285 fn parse_metrics_port() {
286 let cmd: NodeCommand<EthereumChainSpecParser> =
287 NodeCommand::try_parse_args_from(["reth", "--metrics", "9001"]).unwrap();
288 assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001)));
289
290 let cmd: NodeCommand<EthereumChainSpecParser> =
291 NodeCommand::try_parse_args_from(["reth", "--metrics", ":9001"]).unwrap();
292 assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001)));
293
294 let cmd: NodeCommand<EthereumChainSpecParser> =
295 NodeCommand::try_parse_args_from(["reth", "--metrics", "localhost:9001"]).unwrap();
296 assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001)));
297 }
298
299 #[test]
300 fn parse_config_path() {
301 let cmd: NodeCommand<EthereumChainSpecParser> =
302 NodeCommand::try_parse_args_from(["reth", "--config", "my/path/to/reth.toml"]).unwrap();
303 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
305 let config_path = cmd.config.unwrap_or_else(|| data_dir.config());
306 assert_eq!(config_path, Path::new("my/path/to/reth.toml"));
307
308 let cmd: NodeCommand<EthereumChainSpecParser> =
309 NodeCommand::try_parse_args_from(["reth"]).unwrap();
310
311 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
313 let config_path = cmd.config.clone().unwrap_or_else(|| data_dir.config());
314 let end = format!("{}/reth.toml", SUPPORTED_CHAINS[0]);
315 assert!(config_path.ends_with(end), "{:?}", cmd.config);
316 }
317
318 #[test]
319 fn parse_db_path() {
320 let cmd: NodeCommand<EthereumChainSpecParser> =
321 NodeCommand::try_parse_args_from(["reth"]).unwrap();
322 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
323
324 let db_path = data_dir.db();
325 let end = format!("reth/{}/db", SUPPORTED_CHAINS[0]);
326 assert!(db_path.ends_with(end), "{:?}", cmd.config);
327
328 let cmd: NodeCommand<EthereumChainSpecParser> =
329 NodeCommand::try_parse_args_from(["reth", "--datadir", "my/custom/path"]).unwrap();
330 let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);
331
332 let db_path = data_dir.db();
333 assert_eq!(db_path, Path::new("my/custom/path/db"));
334 }
335
336 #[test]
337 fn parse_instance() {
338 let mut cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from(["reth"]);
339 cmd.rpc.adjust_instance_ports(cmd.instance);
340 cmd.network.port = DEFAULT_DISCOVERY_PORT;
341 assert_eq!(cmd.rpc.auth_port, 8551);
343 assert_eq!(cmd.rpc.http_port, 8545);
344 assert_eq!(cmd.rpc.ws_port, 8546);
345 assert_eq!(cmd.network.port, 30303);
347
348 let mut cmd: NodeCommand<EthereumChainSpecParser> =
349 NodeCommand::parse_from(["reth", "--instance", "2"]);
350 cmd.rpc.adjust_instance_ports(cmd.instance);
351 cmd.network.port = DEFAULT_DISCOVERY_PORT + 2 - 1;
352 assert_eq!(cmd.rpc.auth_port, 8651);
354 assert_eq!(cmd.rpc.http_port, 8544);
355 assert_eq!(cmd.rpc.ws_port, 8548);
356 assert_eq!(cmd.network.port, 30304);
358
359 let mut cmd: NodeCommand<EthereumChainSpecParser> =
360 NodeCommand::parse_from(["reth", "--instance", "3"]);
361 cmd.rpc.adjust_instance_ports(cmd.instance);
362 cmd.network.port = DEFAULT_DISCOVERY_PORT + 3 - 1;
363 assert_eq!(cmd.rpc.auth_port, 8751);
365 assert_eq!(cmd.rpc.http_port, 8543);
366 assert_eq!(cmd.rpc.ws_port, 8550);
367 assert_eq!(cmd.network.port, 30305);
369 }
370
371 #[test]
372 fn parse_with_unused_ports() {
373 let cmd: NodeCommand<EthereumChainSpecParser> =
374 NodeCommand::parse_from(["reth", "--with-unused-ports"]);
375 assert!(cmd.with_unused_ports);
376 }
377
378 #[test]
379 fn with_unused_ports_conflicts_with_instance() {
380 let err = NodeCommand::<EthereumChainSpecParser>::try_parse_args_from([
381 "reth",
382 "--with-unused-ports",
383 "--instance",
384 "2",
385 ])
386 .unwrap_err();
387 assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
388 }
389
390 #[test]
391 fn with_unused_ports_check_zero() {
392 let mut cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from(["reth"]);
393 cmd.rpc = cmd.rpc.with_unused_ports();
394 cmd.network = cmd.network.with_unused_ports();
395
396 assert_eq!(cmd.rpc.auth_port, 0);
398 assert_eq!(cmd.rpc.http_port, 0);
399 assert_eq!(cmd.rpc.ws_port, 0);
400
401 assert_eq!(cmd.network.port, 0);
403 assert_eq!(cmd.network.discovery.port, 0);
404
405 assert_ne!(cmd.rpc.ipcpath, String::from("/tmp/reth.ipc"));
407 }
408}