Skip to main content

reth_cli_commands/p2p/
enode.rs

1//! Enode identifier command
2
3use clap::Parser;
4use eyre::WrapErr;
5use reth_network_peers::NodeRecord;
6use secp256k1::SecretKey;
7use std::{
8    net::{IpAddr, Ipv4Addr, SocketAddr},
9    path::PathBuf,
10};
11
12/// Print the enode identifier for a given secret key.
13#[derive(Parser, Debug)]
14pub struct Command {
15    /// Path to the secret key file for discovery.
16    pub discovery_secret: PathBuf,
17
18    /// Optional IP address to include in the enode URL.
19    ///
20    /// If not provided, defaults to 0.0.0.0.
21    #[arg(long)]
22    pub ip: Option<IpAddr>,
23
24    /// Port to include in the enode URL.
25    #[arg(long, default_value_t = 30303)]
26    pub port: u16,
27}
28
29impl Command {
30    /// Execute the enode command.
31    pub fn execute(self) -> eyre::Result<()> {
32        let contents = reth_fs_util::read_to_string(&self.discovery_secret)?;
33        // Parse exactly like the node's key loader so only keys it accepts produce an enode.
34        let sk = contents.parse::<SecretKey>().wrap_err_with(|| {
35            format!("Invalid secret key in {}", self.discovery_secret.display())
36        })?;
37        let ip = self.ip.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
38        let addr = SocketAddr::new(ip, self.port);
39        let enr = NodeRecord::from_secret_key(addr, &sk);
40        println!("{enr}");
41        Ok(())
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn missing_secret_key_is_not_generated() {
51        let dir = tempfile::tempdir().unwrap();
52        let path = dir.path().join("key");
53        let cmd = Command::parse_from(["enode", path.to_str().unwrap()]);
54        assert!(cmd.execute().is_err());
55        assert!(!path.exists());
56    }
57}