diff --git a/Cargo.lock b/Cargo.lock index deb87de4f..950cc155f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4447,6 +4447,7 @@ dependencies = [ "eyre", "fdlimit", "futures", + "hex", "human_bytes", "hyper", "jsonrpsee", @@ -4478,10 +4479,12 @@ dependencies = [ "reth-tasks", "reth-tracing", "reth-transaction-pool", + "secp256k1 0.26.0", "serde", "serde_json", "shellexpand", "tempfile", + "thiserror", "tokio", "tracing", "tui", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 1b12b670c..49f5cbc5e 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -34,6 +34,13 @@ reth-net-nat = { path = "../../crates/net/nat" } reth-miner = { path = "../../crates/miner" } reth-discv4 = { path = "../../crates/net/discv4" } +# crypto +secp256k1 = { version = "0.26.0", features = [ + "global-context", + "rand-std", + "recovery", +] } + # tracing tracing = "0.1" @@ -72,3 +79,5 @@ eyre = "0.6.8" clap = { version = "4", features = ["derive", "cargo"] } tempfile = { version = "3.3.0" } backon = "0.4" +hex = "0.4" +thiserror = "1.0" \ No newline at end of file diff --git a/bin/reth/src/args/mod.rs b/bin/reth/src/args/mod.rs index a9d6efa74..98167ad59 100644 --- a/bin/reth/src/args/mod.rs +++ b/bin/reth/src/args/mod.rs @@ -11,3 +11,6 @@ pub use rpc_server_args::RpcServerArgs; /// DebugArgs struct for debugging purposes mod debug_args; pub use debug_args::DebugArgs; + +mod secret_key; +pub use secret_key::{get_secret_key, SecretKeyError}; diff --git a/bin/reth/src/args/network_args.rs b/bin/reth/src/args/network_args.rs index 39bf49ee9..c68c2044b 100644 --- a/bin/reth/src/args/network_args.rs +++ b/bin/reth/src/args/network_args.rs @@ -6,6 +6,7 @@ use reth_net_nat::NatResolver; use reth_network::NetworkConfigBuilder; use reth_primitives::{mainnet_nodes, ChainSpec, NodeRecord}; use reth_staged_sync::Config; +use secp256k1::SecretKey; use std::{path::PathBuf, sync::Arc}; /// Parameters for configuring the network more granularity via CLI @@ -57,11 +58,12 @@ impl NetworkArgs { &self, config: &Config, chain_spec: Arc, + secret_key: SecretKey, ) -> NetworkConfigBuilder { let chain_bootnodes = chain_spec.chain.bootnodes().unwrap_or_else(mainnet_nodes); let network_config_builder = config - .network_config(self.nat, self.persistent_peers_file()) + .network_config(self.nat, self.persistent_peers_file(), secret_key) .boot_nodes(self.bootnodes.clone().unwrap_or(chain_bootnodes)) .chain_spec(chain_spec); diff --git a/bin/reth/src/args/secret_key.rs b/bin/reth/src/args/secret_key.rs new file mode 100644 index 000000000..6904213f8 --- /dev/null +++ b/bin/reth/src/args/secret_key.rs @@ -0,0 +1,49 @@ +use crate::dirs::{PlatformPath, SecretKeyPath}; +use hex::encode as hex_encode; +use reth_network::config::rng_secret_key; +use secp256k1::{Error as SecretKeyBaseError, SecretKey}; +use std::fs::read_to_string; +use thiserror::Error; + +/// Errors returned by loading a [`SecretKey`][secp256k1::SecretKey], including IO errors. +#[derive(Error, Debug)] +#[allow(missing_docs)] +pub enum SecretKeyError { + #[error(transparent)] + SecretKeyDecodeError(#[from] SecretKeyBaseError), + #[error("An I/O error occurred: {0}")] + IOError(#[from] std::io::Error), +} + +/// Attempts to load a [`SecretKey`] from a specified path. If no file exists +/// there, then it generates a secret key and stores it in the default path. I/O +/// errors might occur during write operations in the form of a +/// [`SecretKeyError`] +pub fn get_secret_key( + secret_key_path: &PlatformPath, +) -> Result { + let fpath = secret_key_path.as_ref(); + let exists = fpath.try_exists(); + + match exists { + Ok(true) => { + let contents = read_to_string(fpath)?; + (contents.as_str().parse::()).map_err(SecretKeyError::SecretKeyDecodeError) + } + Ok(false) => { + let default_path = PlatformPath::::default(); + let fpath = default_path.as_ref(); + + if let Some(dir) = fpath.parent() { + // Create parent directory + std::fs::create_dir_all(dir)? + } + + let secret = rng_secret_key(); + let hex = hex_encode(secret.as_ref()); + std::fs::write(fpath, hex)?; + Ok(secret) + } + Err(e) => Err(SecretKeyError::IOError(e)), + } +} diff --git a/bin/reth/src/dirs.rs b/bin/reth/src/dirs.rs index 72e83e587..c71213bcf 100644 --- a/bin/reth/src/dirs.rs +++ b/bin/reth/src/dirs.rs @@ -56,6 +56,13 @@ pub fn net_dir() -> Option { data_dir().map(|root| root.join("net")) } +/// Returns the path to the reth secret key directory. +/// +/// Refer to [dirs_next::data_dir] for cross-platform behavior. +pub fn p2p_secret_key_dir() -> Option { + data_dir().map(|root| root.join("p2p")) +} + /// Returns the path to the reth database. /// /// Refer to [dirs_next::data_dir] for cross-platform behavior. @@ -121,6 +128,19 @@ impl XdgPath for LogsDir { } } +/// Returns the path to the default reth secret key directory. +/// +/// Refer to [dirs_next::data_dir] for cross-platform behavior. +#[derive(Default, Debug, Clone)] +#[non_exhaustive] +pub struct SecretKeyPath; + +impl XdgPath for SecretKeyPath { + fn resolve() -> Option { + p2p_secret_key_dir().map(|p| p.join("secret")) + } +} + /// A small helper trait for unit structs that represent a standard path following the XDG /// path specification. pub trait XdgPath { diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 4cd201a51..abdea40fc 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -2,8 +2,8 @@ //! //! Starts the client use crate::{ - args::{DebugArgs, NetworkArgs, RpcServerArgs}, - dirs::{ConfigPath, DbPath, PlatformPath}, + args::{get_secret_key, DebugArgs, NetworkArgs, RpcServerArgs}, + dirs::{ConfigPath, DbPath, PlatformPath, SecretKeyPath}, prometheus_exporter, runner::CliContext, utils::get_single_header, @@ -59,6 +59,7 @@ use reth_stages::{ }; use reth_tasks::TaskExecutor; use reth_transaction_pool::{EthTransactionValidator, TransactionPool}; +use secp256k1::SecretKey; use std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4}, path::PathBuf, @@ -109,6 +110,12 @@ pub struct Command { )] chain: Arc, + /// Secret key to use for this node. + /// + /// This also will deterministically set the peer ID. + #[arg(long, value_name = "PATH", global = true, required = false, default_value_t)] + p2p_secret_key: PlatformPath, + /// Enable Prometheus metrics. /// /// The metrics will be served at the given interface and port. @@ -168,8 +175,13 @@ impl Command { info!(target: "reth::cli", "Test transaction pool initialized"); info!(target: "reth::cli", "Connecting to P2P network"); - let network_config = - self.load_network_config(&config, Arc::clone(&db), ctx.task_executor.clone()); + let secret_key = get_secret_key(&self.p2p_secret_key)?; + let network_config = self.load_network_config( + &config, + Arc::clone(&db), + ctx.task_executor.clone(), + secret_key, + ); let network = self .start_network(network_config, &ctx.task_executor, transaction_pool.clone()) .await?; @@ -548,11 +560,12 @@ impl Command { config: &Config, db: Arc>, executor: TaskExecutor, + secret_key: SecretKey, ) -> NetworkConfig>>> { let head = self.lookup_head(Arc::clone(&db)).expect("the head block is missing"); self.network - .network_config(config, self.chain.clone()) + .network_config(config, self.chain.clone(), secret_key) .with_task_executor(Box::new(executor)) .set_head(head) .listener_addr(SocketAddr::V4(SocketAddrV4::new( diff --git a/bin/reth/src/p2p/mod.rs b/bin/reth/src/p2p/mod.rs index 44960d76c..f3dcbaffd 100644 --- a/bin/reth/src/p2p/mod.rs +++ b/bin/reth/src/p2p/mod.rs @@ -1,7 +1,7 @@ //! P2P Debugging tool use crate::{ - args::DiscoveryArgs, - dirs::{ConfigPath, PlatformPath}, + args::{get_secret_key, DiscoveryArgs}, + dirs::{ConfigPath, PlatformPath, SecretKeyPath}, utils::get_single_header, }; use backon::{ConstantBuilder, Retryable}; @@ -41,6 +41,12 @@ pub struct Command { )] chain: Arc, + /// Secret key to use for this node. + /// + /// This also will deterministically set the peer ID. + #[arg(long, value_name = "PATH", global = true, required = false, default_value_t)] + p2p_secret_key: PlatformPath, + /// Disable the discovery service. #[command(flatten)] pub discovery: DiscoveryArgs, @@ -98,8 +104,10 @@ impl Command { config.peers.connect_trusted_nodes_only = self.trusted_only; + let p2p_secret_key = get_secret_key(&self.p2p_secret_key)?; + let mut network_config_builder = - config.network_config(self.nat, None).chain_spec(self.chain.clone()); + config.network_config(self.nat, None, p2p_secret_key).chain_spec(self.chain.clone()); network_config_builder = self.discovery.apply_to_builder(network_config_builder); diff --git a/bin/reth/src/stage/mod.rs b/bin/reth/src/stage/mod.rs index 7c16e2206..0b06437f5 100644 --- a/bin/reth/src/stage/mod.rs +++ b/bin/reth/src/stage/mod.rs @@ -2,8 +2,8 @@ //! //! Stage debugging tool use crate::{ - args::NetworkArgs, - dirs::{ConfigPath, DbPath, PlatformPath}, + args::{get_secret_key, NetworkArgs}, + dirs::{ConfigPath, DbPath, PlatformPath, SecretKeyPath}, prometheus_exporter, StageEnum, }; use clap::Parser; @@ -56,6 +56,12 @@ pub struct Command { )] chain: Arc, + /// Secret key to use for this node. + /// + /// This also will deterministically set the peer ID. + #[arg(long, value_name = "PATH", global = true, required = false, default_value_t)] + p2p_secret_key: PlatformPath, + /// Enable Prometheus metrics. /// /// The metrics will be served at the given interface and port. @@ -125,9 +131,11 @@ impl Command { }); } + let p2p_secret_key = get_secret_key(&self.p2p_secret_key)?; + let network = self .network - .network_config(&config, self.chain.clone()) + .network_config(&config, self.chain.clone(), p2p_secret_key) .build(Arc::new(ShareableDatabase::new(db.clone(), self.chain.clone()))) .start_network() .await?; diff --git a/crates/staged-sync/Cargo.toml b/crates/staged-sync/Cargo.toml index 7be910935..9c040d96a 100644 --- a/crates/staged-sync/Cargo.toml +++ b/crates/staged-sync/Cargo.toml @@ -30,6 +30,11 @@ tracing = "0.1.37" # crypto rand = { version = "0.8", optional = true } +secp256k1 = { version = "0.26.0", features = [ + "global-context", + "rand-std", + "recovery", +] } # errors thiserror = "1" diff --git a/crates/staged-sync/src/config.rs b/crates/staged-sync/src/config.rs index eb72b2d9f..7eabe81f1 100644 --- a/crates/staged-sync/src/config.rs +++ b/crates/staged-sync/src/config.rs @@ -4,7 +4,8 @@ use reth_downloaders::{ bodies::bodies::BodiesDownloaderBuilder, headers::reverse_headers::ReverseHeadersDownloaderBuilder, }; -use reth_network::{config::rng_secret_key, NetworkConfigBuilder, PeersConfig}; +use reth_network::{NetworkConfigBuilder, PeersConfig}; +use secp256k1::SecretKey; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -25,15 +26,17 @@ impl Config { &self, nat_resolution_method: reth_net_nat::NatResolver, peers_file: Option, + secret_key: SecretKey, ) -> NetworkConfigBuilder { let peer_config = self .peers .clone() .with_basic_nodes_from_file(peers_file) .unwrap_or_else(|_| self.peers.clone()); + let discv4 = Discv4Config::builder().external_ip_resolver(Some(nat_resolution_method)).clone(); - NetworkConfigBuilder::new(rng_secret_key()).peer_config(peer_config).discovery(discv4) + NetworkConfigBuilder::new(secret_key).peer_config(peer_config).discovery(discv4) } }