chore(cli): move utils to reth-cli-utils crate (#9297)

This commit is contained in:
Luca Provini
2024-07-04 14:34:55 +02:00
committed by GitHub
parent 610110bb67
commit f759ca6036
20 changed files with 234 additions and 180 deletions

View File

@@ -0,0 +1,23 @@
[package]
name = "reth-cli-util"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
exclude.workspace = true
[dependencies]
reth-fs-util.workspace = true
reth-network.workspace = true
reth-primitives.workspace = true
secp256k1.workspace = true
thiserror.workspace = true
eyre.workspace = true
[dev-dependencies]
proptest.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,17 @@
//! This crate defines a set of commonly used cli utils.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
/// Helper function to load a secret key from a file.
pub mod load_secret_key;
pub use load_secret_key::get_secret_key;
/// Cli parsers functions.
pub mod parsers;
pub use parsers::{hash_or_num_value_parser, parse_duration_from_secs, parse_socket_address};

View File

@@ -0,0 +1,96 @@
use reth_primitives::{BlockHashOrNumber, B256};
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs},
str::FromStr,
time::Duration,
};
/// Helper to parse a [Duration] from seconds
pub fn parse_duration_from_secs(arg: &str) -> eyre::Result<Duration, std::num::ParseIntError> {
let seconds = arg.parse()?;
Ok(Duration::from_secs(seconds))
}
/// Parse [`BlockHashOrNumber`]
pub fn hash_or_num_value_parser(value: &str) -> eyre::Result<BlockHashOrNumber, eyre::Error> {
match B256::from_str(value) {
Ok(hash) => Ok(BlockHashOrNumber::Hash(hash)),
Err(_) => Ok(BlockHashOrNumber::Number(value.parse()?)),
}
}
/// Error thrown while parsing a socket address.
#[derive(thiserror::Error, Debug)]
pub enum SocketAddressParsingError {
/// Failed to convert the string into a socket addr
#[error("could not parse socket address: {0}")]
Io(#[from] std::io::Error),
/// Input must not be empty
#[error("cannot parse socket address from empty string")]
Empty,
/// Failed to parse the address
#[error("could not parse socket address from {0}")]
Parse(String),
/// Failed to parse port
#[error("could not parse port: {0}")]
Port(#[from] std::num::ParseIntError),
}
/// Parse a [`SocketAddr`] from a `str`.
///
/// The following formats are checked:
///
/// - If the value can be parsed as a `u16` or starts with `:` it is considered a port, and the
/// hostname is set to `localhost`.
/// - If the value contains `:` it is assumed to be the format `<host>:<port>`
/// - Otherwise it is assumed to be a hostname
///
/// An error is returned if the value is empty.
pub fn parse_socket_address(value: &str) -> eyre::Result<SocketAddr, SocketAddressParsingError> {
if value.is_empty() {
return Err(SocketAddressParsingError::Empty)
}
if let Some(port) = value.strip_prefix(':').or_else(|| value.strip_prefix("localhost:")) {
let port: u16 = port.parse()?;
return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
}
if let Ok(port) = value.parse::<u16>() {
return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
}
value
.to_socket_addrs()?
.next()
.ok_or_else(|| SocketAddressParsingError::Parse(value.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::Rng;
use secp256k1::rand::thread_rng;
#[test]
fn parse_socket_addresses() {
for value in ["localhost:9000", ":9000", "9000"] {
let socket_addr = parse_socket_address(value)
.unwrap_or_else(|_| panic!("could not parse socket address: {value}"));
assert!(socket_addr.ip().is_loopback());
assert_eq!(socket_addr.port(), 9000);
}
}
#[test]
fn parse_socket_address_random() {
let port: u16 = thread_rng().gen();
for value in [format!("localhost:{port}"), format!(":{port}"), port.to_string()] {
let socket_addr = parse_socket_address(&value)
.unwrap_or_else(|_| panic!("could not parse socket address: {value}"));
assert!(socket_addr.ip().is_loopback());
assert_eq!(socket_addr.port(), port);
}
}
}

View File

@@ -46,6 +46,7 @@ reth-consensus.workspace = true
reth-consensus-debug-client.workspace = true
reth-rpc-types.workspace = true
reth-engine-util.workspace = true
reth-cli-util.workspace = true
## async
futures.workspace = true

View File

@@ -11,6 +11,7 @@ use crate::{
};
use futures::Future;
use reth_chainspec::ChainSpec;
use reth_cli_util::get_secret_key;
use reth_db::{
test_utils::{create_test_rw_db_with_path, tempdir_path, TempDatabase},
DatabaseEnv,
@@ -25,7 +26,7 @@ use reth_network::{
};
use reth_node_api::{FullNodeTypes, FullNodeTypesAdapter, NodeTypes};
use reth_node_core::{
args::{get_secret_key, DatadirArgs},
args::DatadirArgs,
cli::config::{PayloadBuilderConfig, RethTransactionPoolConfig},
dirs::{ChainPath, DataDirPath, MaybePlatformPath},
node_config::NodeConfig,

View File

@@ -14,6 +14,7 @@ workspace = true
# reth
reth-chainspec.workspace = true
reth-primitives.workspace = true
reth-cli-util.workspace = true
reth-fs-util.workspace = true
reth-db = { workspace = true, features = ["mdbx"] }
reth-db-api.workspace = true
@@ -57,7 +58,6 @@ metrics-util.workspace = true
eyre.workspace = true
clap = { workspace = true, features = ["derive"] }
humantime.workspace = true
thiserror.workspace = true
const_format.workspace = true
rand.workspace = true
derive_more.workspace = true

View File

@@ -24,9 +24,6 @@ pub use database::DatabaseArgs;
mod log;
pub use log::{ColorMode, LogArgs};
mod secret_key;
pub use secret_key::{get_secret_key, SecretKeyError};
/// `PayloadBuilderArgs` struct for configuring the payload builder
mod payload_builder;
pub use payload_builder::PayloadBuilderArgs;

View File

@@ -1,11 +1,9 @@
use crate::{
args::utils::parse_duration_from_secs, cli::config::PayloadBuilderConfig,
version::default_extradata,
};
use crate::{cli::config::PayloadBuilderConfig, version::default_extradata};
use clap::{
builder::{RangedU64ValueParser, TypedValueParser},
Arg, Args, Command,
};
use reth_cli_util::parse_duration_from_secs;
use reth_primitives::constants::{
ETHEREUM_BLOCK_GAS_LIMIT, MAXIMUM_EXTRA_DATA_SIZE, SLOT_DURATION,
};

View File

@@ -3,14 +3,7 @@
use alloy_genesis::Genesis;
use reth_chainspec::ChainSpec;
use reth_fs_util as fs;
use reth_primitives::{BlockHashOrNumber, B256};
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs},
path::PathBuf,
str::FromStr,
sync::Arc,
time::Duration,
};
use std::{path::PathBuf, sync::Arc};
use reth_chainspec::DEV;
@@ -27,12 +20,6 @@ pub const SUPPORTED_CHAINS: &[&str] = &["optimism", "optimism-sepolia", "base",
/// Chains supported by reth. First value should be used as the default.
pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "sepolia", "goerli", "holesky", "dev"];
/// Helper to parse a [Duration] from seconds
pub fn parse_duration_from_secs(arg: &str) -> eyre::Result<Duration, std::num::ParseIntError> {
let seconds = arg.parse()?;
Ok(Duration::from_secs(seconds))
}
/// The help info for the --chain flag
pub fn chain_help() -> String {
format!("The chain this node is running.\nPossible values are either a built-in chain or the path to a chain specification file.\n\nBuilt-in chains:\n {}", SUPPORTED_CHAINS.join(", "))
@@ -83,64 +70,9 @@ pub fn chain_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Error>
})
}
/// Parse [`BlockHashOrNumber`]
pub fn hash_or_num_value_parser(value: &str) -> eyre::Result<BlockHashOrNumber, eyre::Error> {
match B256::from_str(value) {
Ok(hash) => Ok(BlockHashOrNumber::Hash(hash)),
Err(_) => Ok(BlockHashOrNumber::Number(value.parse()?)),
}
}
/// Error thrown while parsing a socket address.
#[derive(thiserror::Error, Debug)]
pub enum SocketAddressParsingError {
/// Failed to convert the string into a socket addr
#[error("could not parse socket address: {0}")]
Io(#[from] std::io::Error),
/// Input must not be empty
#[error("cannot parse socket address from empty string")]
Empty,
/// Failed to parse the address
#[error("could not parse socket address from {0}")]
Parse(String),
/// Failed to parse port
#[error("could not parse port: {0}")]
Port(#[from] std::num::ParseIntError),
}
/// Parse a [`SocketAddr`] from a `str`.
///
/// The following formats are checked:
///
/// - If the value can be parsed as a `u16` or starts with `:` it is considered a port, and the
/// hostname is set to `localhost`.
/// - If the value contains `:` it is assumed to be the format `<host>:<port>`
/// - Otherwise it is assumed to be a hostname
///
/// An error is returned if the value is empty.
pub fn parse_socket_address(value: &str) -> eyre::Result<SocketAddr, SocketAddressParsingError> {
if value.is_empty() {
return Err(SocketAddressParsingError::Empty)
}
if let Some(port) = value.strip_prefix(':').or_else(|| value.strip_prefix("localhost:")) {
let port: u16 = port.parse()?;
return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
}
if let Ok(port) = value.parse::<u16>() {
return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
}
value
.to_socket_addrs()?
.next()
.ok_or_else(|| SocketAddressParsingError::Parse(value.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::Rng;
use secp256k1::rand::thread_rng;
#[test]
fn parse_known_chain_spec() {
@@ -148,28 +80,4 @@ mod tests {
chain_value_parser(chain).unwrap();
}
}
#[test]
fn parse_socket_addresses() {
for value in ["localhost:9000", ":9000", "9000"] {
let socket_addr = parse_socket_address(value)
.unwrap_or_else(|_| panic!("could not parse socket address: {value}"));
assert!(socket_addr.ip().is_loopback());
assert_eq!(socket_addr.port(), 9000);
}
}
#[test]
fn parse_socket_address_random() {
let port: u16 = thread_rng().gen();
for value in [format!("localhost:{port}"), format!(":{port}"), port.to_string()] {
let socket_addr = parse_socket_address(&value)
.unwrap_or_else(|_| panic!("could not parse socket address: {value}"));
assert!(socket_addr.ip().is_loopback());
assert_eq!(socket_addr.port(), port);
}
}
}