mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
chore(cli): move utils to reth-cli-utils crate (#9297)
This commit is contained in:
23
crates/cli/util/Cargo.toml
Normal file
23
crates/cli/util/Cargo.toml
Normal 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
|
||||
17
crates/cli/util/src/lib.rs
Normal file
17
crates/cli/util/src/lib.rs
Normal 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};
|
||||
62
crates/cli/util/src/load_secret_key.rs
Normal file
62
crates/cli/util/src/load_secret_key.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use reth_fs_util::{self as fs, FsPathError};
|
||||
use reth_network::config::rng_secret_key;
|
||||
use reth_primitives::hex::encode as hex_encode;
|
||||
use secp256k1::{Error as SecretKeyBaseError, SecretKey};
|
||||
use std::{
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors returned by loading a [`SecretKey`], including IO errors.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SecretKeyError {
|
||||
/// Error encountered during decoding of the secret key.
|
||||
#[error(transparent)]
|
||||
SecretKeyDecodeError(#[from] SecretKeyBaseError),
|
||||
|
||||
/// Error related to file system path operations.
|
||||
#[error(transparent)]
|
||||
SecretKeyFsPathError(#[from] FsPathError),
|
||||
|
||||
/// Represents an error when failed to access the key file.
|
||||
#[error("failed to access key file {secret_file:?}: {error}")]
|
||||
FailedToAccessKeyFile {
|
||||
/// The encountered IO error.
|
||||
error: io::Error,
|
||||
/// Path to the secret key file.
|
||||
secret_file: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
/// 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 provided path. I/O errors might occur during write
|
||||
/// operations in the form of a [`SecretKeyError`]
|
||||
pub fn get_secret_key(secret_key_path: &Path) -> Result<SecretKey, SecretKeyError> {
|
||||
let exists = secret_key_path.try_exists();
|
||||
|
||||
match exists {
|
||||
Ok(true) => {
|
||||
let contents = fs::read_to_string(secret_key_path)?;
|
||||
Ok(contents
|
||||
.as_str()
|
||||
.parse::<SecretKey>()
|
||||
.map_err(SecretKeyError::SecretKeyDecodeError)?)
|
||||
}
|
||||
Ok(false) => {
|
||||
if let Some(dir) = secret_key_path.parent() {
|
||||
// Create parent directory
|
||||
fs::create_dir_all(dir)?;
|
||||
}
|
||||
|
||||
let secret = rng_secret_key();
|
||||
let hex = hex_encode(secret.as_ref());
|
||||
fs::write(secret_key_path, hex)?;
|
||||
Ok(secret)
|
||||
}
|
||||
Err(error) => Err(SecretKeyError::FailedToAccessKeyFile {
|
||||
error,
|
||||
secret_file: secret_key_path.to_path_buf(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
96
crates/cli/util/src/parsers.rs
Normal file
96
crates/cli/util/src/parsers.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user