mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
Co-authored-by: Alexey Shekhirin <a.shekhirin@gmail.com> Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
395 lines
14 KiB
Rust
395 lines
14 KiB
Rust
//! Main node command for launching a node
|
|
|
|
use crate::{
|
|
args::{
|
|
utils::{chain_help, genesis_value_parser, parse_socket_address, SUPPORTED_CHAINS},
|
|
DatabaseArgs, DebugArgs, DevArgs, NetworkArgs, PayloadBuilderArgs, PruningArgs,
|
|
RpcServerArgs, TxPoolArgs,
|
|
},
|
|
dirs::{DataDirPath, MaybePlatformPath},
|
|
};
|
|
use clap::{value_parser, Args, Parser};
|
|
use reth_cli_runner::CliContext;
|
|
use reth_db::{init_db, DatabaseEnv};
|
|
use reth_node_builder::{NodeBuilder, WithLaunchContext};
|
|
use reth_node_core::{node_config::NodeConfig, version};
|
|
use reth_primitives::ChainSpec;
|
|
use std::{ffi::OsString, fmt, future::Future, net::SocketAddr, path::PathBuf, sync::Arc};
|
|
|
|
/// Start the node
|
|
#[derive(Debug, Parser)]
|
|
pub struct NodeCommand<Ext: clap::Args + fmt::Debug = NoArgs> {
|
|
/// The path to the data dir for all reth files and subdirectories.
|
|
///
|
|
/// Defaults to the OS-specific data directory:
|
|
///
|
|
/// - Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/`
|
|
/// - Windows: `{FOLDERID_RoamingAppData}/reth/`
|
|
/// - macOS: `$HOME/Library/Application Support/reth/`
|
|
#[arg(long, value_name = "DATA_DIR", verbatim_doc_comment, default_value_t)]
|
|
pub datadir: MaybePlatformPath<DataDirPath>,
|
|
|
|
/// The path to the configuration file to use.
|
|
#[arg(long, value_name = "FILE", verbatim_doc_comment)]
|
|
pub config: Option<PathBuf>,
|
|
|
|
/// The chain this node is running.
|
|
///
|
|
/// Possible values are either a built-in chain or the path to a chain specification file.
|
|
#[arg(
|
|
long,
|
|
value_name = "CHAIN_OR_PATH",
|
|
long_help = chain_help(),
|
|
default_value = SUPPORTED_CHAINS[0],
|
|
default_value_if("dev", "true", "dev"),
|
|
value_parser = genesis_value_parser,
|
|
required = false,
|
|
)]
|
|
pub chain: Arc<ChainSpec>,
|
|
|
|
/// Enable Prometheus metrics.
|
|
///
|
|
/// The metrics will be served at the given interface and port.
|
|
#[arg(long, value_name = "SOCKET", value_parser = parse_socket_address, help_heading = "Metrics")]
|
|
pub metrics: Option<SocketAddr>,
|
|
|
|
/// Add a new instance of a node.
|
|
///
|
|
/// Configures the ports of the node to avoid conflicts with the defaults.
|
|
/// This is useful for running multiple nodes on the same machine.
|
|
///
|
|
/// Max number of instances is 200. It is chosen in a way so that it's not possible to have
|
|
/// port numbers that conflict with each other.
|
|
///
|
|
/// Changes to the following port numbers:
|
|
/// - `DISCOVERY_PORT`: default + `instance` - 1
|
|
/// - `AUTH_PORT`: default + `instance` * 100 - 100
|
|
/// - `HTTP_RPC_PORT`: default - `instance` + 1
|
|
/// - `WS_RPC_PORT`: default + `instance` * 2 - 2
|
|
#[arg(long, value_name = "INSTANCE", global = true, default_value_t = 1, value_parser = value_parser!(u16).range(..=200))]
|
|
pub instance: u16,
|
|
|
|
/// Sets all ports to unused, allowing the OS to choose random unused ports when sockets are
|
|
/// bound.
|
|
///
|
|
/// Mutually exclusive with `--instance`.
|
|
#[arg(long, conflicts_with = "instance", global = true)]
|
|
pub with_unused_ports: bool,
|
|
|
|
/// All networking related arguments
|
|
#[command(flatten)]
|
|
pub network: NetworkArgs,
|
|
|
|
/// All rpc related arguments
|
|
#[command(flatten)]
|
|
pub rpc: RpcServerArgs,
|
|
|
|
/// All txpool related arguments with --txpool prefix
|
|
#[command(flatten)]
|
|
pub txpool: TxPoolArgs,
|
|
|
|
/// All payload builder related arguments
|
|
#[command(flatten)]
|
|
pub builder: PayloadBuilderArgs,
|
|
|
|
/// All debug related arguments with --debug prefix
|
|
#[command(flatten)]
|
|
pub debug: DebugArgs,
|
|
|
|
/// All database related arguments
|
|
#[command(flatten)]
|
|
pub db: DatabaseArgs,
|
|
|
|
/// All dev related arguments with --dev prefix
|
|
#[command(flatten)]
|
|
pub dev: DevArgs,
|
|
|
|
/// All pruning related arguments
|
|
#[command(flatten)]
|
|
pub pruning: PruningArgs,
|
|
|
|
/// Additional cli arguments
|
|
#[command(flatten, next_help_heading = "Extension")]
|
|
pub ext: Ext,
|
|
}
|
|
|
|
impl NodeCommand {
|
|
/// Parsers only the default CLI arguments
|
|
pub fn parse_args() -> Self {
|
|
Self::parse()
|
|
}
|
|
|
|
/// Parsers only the default [`NodeCommand`] arguments from the given iterator
|
|
pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
|
|
where
|
|
I: IntoIterator<Item = T>,
|
|
T: Into<OsString> + Clone,
|
|
{
|
|
Self::try_parse_from(itr)
|
|
}
|
|
}
|
|
|
|
impl<Ext: clap::Args + fmt::Debug> NodeCommand<Ext> {
|
|
/// Launches the node
|
|
///
|
|
/// This transforms the node command into a node config and launches the node using the given
|
|
/// closure.
|
|
pub async fn execute<L, Fut>(self, ctx: CliContext, launcher: L) -> eyre::Result<()>
|
|
where
|
|
L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>>>, Ext) -> Fut,
|
|
Fut: Future<Output = eyre::Result<()>>,
|
|
{
|
|
tracing::info!(target: "reth::cli", version = ?version::SHORT_VERSION, "Starting reth");
|
|
|
|
let Self {
|
|
datadir,
|
|
config,
|
|
chain,
|
|
metrics,
|
|
instance,
|
|
with_unused_ports,
|
|
network,
|
|
rpc,
|
|
txpool,
|
|
builder,
|
|
debug,
|
|
db,
|
|
dev,
|
|
pruning,
|
|
ext,
|
|
} = self;
|
|
|
|
// set up node config
|
|
let mut node_config = NodeConfig {
|
|
config,
|
|
chain,
|
|
metrics,
|
|
instance,
|
|
network,
|
|
rpc,
|
|
txpool,
|
|
builder,
|
|
debug,
|
|
db,
|
|
dev,
|
|
pruning,
|
|
};
|
|
|
|
// Register the prometheus recorder before creating the database,
|
|
// because database init needs it to register metrics.
|
|
let _ = node_config.install_prometheus_recorder()?;
|
|
|
|
let data_dir = datadir.unwrap_or_chain_default(node_config.chain.chain);
|
|
let db_path = data_dir.db();
|
|
|
|
tracing::info!(target: "reth::cli", path = ?db_path, "Opening database");
|
|
let database = Arc::new(init_db(db_path.clone(), self.db.database_args())?.with_metrics());
|
|
|
|
if with_unused_ports {
|
|
node_config = node_config.with_unused_ports();
|
|
}
|
|
|
|
let builder = NodeBuilder::new(node_config)
|
|
.with_database(database)
|
|
.with_launch_context(ctx.task_executor, data_dir);
|
|
|
|
launcher(builder, ext).await
|
|
}
|
|
}
|
|
|
|
/// No Additional arguments
|
|
#[derive(Debug, Clone, Copy, Default, Args)]
|
|
#[non_exhaustive]
|
|
pub struct NoArgs;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use reth_discv4::DEFAULT_DISCOVERY_PORT;
|
|
use std::{
|
|
net::{IpAddr, Ipv4Addr},
|
|
path::Path,
|
|
};
|
|
|
|
#[test]
|
|
fn parse_help_node_command() {
|
|
let err = NodeCommand::try_parse_args_from(["reth", "--help"]).unwrap_err();
|
|
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_common_node_command_chain_args() {
|
|
for chain in SUPPORTED_CHAINS {
|
|
let args: NodeCommand = NodeCommand::<NoArgs>::parse_from(["reth", "--chain", chain]);
|
|
assert_eq!(args.chain.chain, chain.parse::<reth_primitives::Chain>().unwrap());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn parse_discovery_addr() {
|
|
let cmd =
|
|
NodeCommand::try_parse_args_from(["reth", "--discovery.addr", "127.0.0.1"]).unwrap();
|
|
assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_addr() {
|
|
let cmd = NodeCommand::try_parse_args_from([
|
|
"reth",
|
|
"--discovery.addr",
|
|
"127.0.0.1",
|
|
"--addr",
|
|
"127.0.0.1",
|
|
])
|
|
.unwrap();
|
|
assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
|
assert_eq!(cmd.network.addr, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_discovery_port() {
|
|
let cmd = NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300"]).unwrap();
|
|
assert_eq!(cmd.network.discovery.port, 300);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_port() {
|
|
let cmd =
|
|
NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300", "--port", "99"])
|
|
.unwrap();
|
|
assert_eq!(cmd.network.discovery.port, 300);
|
|
assert_eq!(cmd.network.port, 99);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_metrics_port() {
|
|
let cmd = NodeCommand::try_parse_args_from(["reth", "--metrics", "9001"]).unwrap();
|
|
assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001)));
|
|
|
|
let cmd = NodeCommand::try_parse_args_from(["reth", "--metrics", ":9001"]).unwrap();
|
|
assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001)));
|
|
|
|
let cmd =
|
|
NodeCommand::try_parse_args_from(["reth", "--metrics", "localhost:9001"]).unwrap();
|
|
assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001)));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_config_path() {
|
|
let cmd =
|
|
NodeCommand::try_parse_args_from(["reth", "--config", "my/path/to/reth.toml"]).unwrap();
|
|
// always store reth.toml in the data dir, not the chain specific data dir
|
|
let data_dir = cmd.datadir.unwrap_or_chain_default(cmd.chain.chain);
|
|
let config_path = cmd.config.unwrap_or_else(|| data_dir.config());
|
|
assert_eq!(config_path, Path::new("my/path/to/reth.toml"));
|
|
|
|
let cmd = NodeCommand::try_parse_args_from(["reth"]).unwrap();
|
|
|
|
// always store reth.toml in the data dir, not the chain specific data dir
|
|
let data_dir = cmd.datadir.unwrap_or_chain_default(cmd.chain.chain);
|
|
let config_path = cmd.config.clone().unwrap_or_else(|| data_dir.config());
|
|
let end = format!("reth/{}/reth.toml", SUPPORTED_CHAINS[0]);
|
|
assert!(config_path.ends_with(end), "{:?}", cmd.config);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_db_path() {
|
|
let cmd = NodeCommand::try_parse_args_from(["reth"]).unwrap();
|
|
let data_dir = cmd.datadir.unwrap_or_chain_default(cmd.chain.chain);
|
|
let db_path = data_dir.db();
|
|
let end = format!("reth/{}/db", SUPPORTED_CHAINS[0]);
|
|
assert!(db_path.ends_with(end), "{:?}", cmd.config);
|
|
|
|
let cmd =
|
|
NodeCommand::try_parse_args_from(["reth", "--datadir", "my/custom/path"]).unwrap();
|
|
let data_dir = cmd.datadir.unwrap_or_chain_default(cmd.chain.chain);
|
|
let db_path = data_dir.db();
|
|
assert_eq!(db_path, Path::new("my/custom/path/db"));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(feature = "optimism"))] // dev mode not yet supported in op-reth
|
|
fn parse_dev() {
|
|
let cmd = NodeCommand::<NoArgs>::parse_from(["reth", "--dev"]);
|
|
let chain = reth_primitives::DEV.clone();
|
|
assert_eq!(cmd.chain.chain, chain.chain);
|
|
assert_eq!(cmd.chain.genesis_hash, chain.genesis_hash);
|
|
assert_eq!(
|
|
cmd.chain.paris_block_and_final_difficulty,
|
|
chain.paris_block_and_final_difficulty
|
|
);
|
|
assert_eq!(cmd.chain.hardforks, chain.hardforks);
|
|
|
|
assert!(cmd.rpc.http);
|
|
assert!(cmd.network.discovery.disable_discovery);
|
|
|
|
assert!(cmd.dev.dev);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_instance() {
|
|
let mut cmd = NodeCommand::<NoArgs>::parse_from(["reth"]);
|
|
cmd.rpc.adjust_instance_ports(cmd.instance);
|
|
cmd.network.port = DEFAULT_DISCOVERY_PORT + cmd.instance - 1;
|
|
// check rpc port numbers
|
|
assert_eq!(cmd.rpc.auth_port, 8551);
|
|
assert_eq!(cmd.rpc.http_port, 8545);
|
|
assert_eq!(cmd.rpc.ws_port, 8546);
|
|
// check network listening port number
|
|
assert_eq!(cmd.network.port, 30303);
|
|
|
|
let mut cmd = NodeCommand::<NoArgs>::parse_from(["reth", "--instance", "2"]);
|
|
cmd.rpc.adjust_instance_ports(cmd.instance);
|
|
cmd.network.port = DEFAULT_DISCOVERY_PORT + cmd.instance - 1;
|
|
// check rpc port numbers
|
|
assert_eq!(cmd.rpc.auth_port, 8651);
|
|
assert_eq!(cmd.rpc.http_port, 8544);
|
|
assert_eq!(cmd.rpc.ws_port, 8548);
|
|
// check network listening port number
|
|
assert_eq!(cmd.network.port, 30304);
|
|
|
|
let mut cmd = NodeCommand::<NoArgs>::parse_from(["reth", "--instance", "3"]);
|
|
cmd.rpc.adjust_instance_ports(cmd.instance);
|
|
cmd.network.port = DEFAULT_DISCOVERY_PORT + cmd.instance - 1;
|
|
// check rpc port numbers
|
|
assert_eq!(cmd.rpc.auth_port, 8751);
|
|
assert_eq!(cmd.rpc.http_port, 8543);
|
|
assert_eq!(cmd.rpc.ws_port, 8550);
|
|
// check network listening port number
|
|
assert_eq!(cmd.network.port, 30305);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_with_unused_ports() {
|
|
let cmd = NodeCommand::<NoArgs>::parse_from(["reth", "--with-unused-ports"]);
|
|
assert!(cmd.with_unused_ports);
|
|
}
|
|
|
|
#[test]
|
|
fn with_unused_ports_conflicts_with_instance() {
|
|
let err =
|
|
NodeCommand::try_parse_args_from(["reth", "--with-unused-ports", "--instance", "2"])
|
|
.unwrap_err();
|
|
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
|
}
|
|
|
|
#[test]
|
|
fn with_unused_ports_check_zero() {
|
|
let mut cmd = NodeCommand::<NoArgs>::parse_from(["reth"]);
|
|
cmd.rpc = cmd.rpc.with_unused_ports();
|
|
cmd.network = cmd.network.with_unused_ports();
|
|
|
|
// make sure the rpc ports are zero
|
|
assert_eq!(cmd.rpc.auth_port, 0);
|
|
assert_eq!(cmd.rpc.http_port, 0);
|
|
assert_eq!(cmd.rpc.ws_port, 0);
|
|
|
|
// make sure the network ports are zero
|
|
assert_eq!(cmd.network.port, 0);
|
|
assert_eq!(cmd.network.discovery.port, 0);
|
|
|
|
// make sure the ipc path is not the default
|
|
assert_ne!(cmd.rpc.ipcpath, String::from("/tmp/reth.ipc"));
|
|
}
|
|
}
|