mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
refactor: move cli utils to new reth-cli-utils crate (#790)
* Move bin/src/util to reth-cli-utils * Add reth-cli-utils to workspace members * Fix imports in bin/src * Create reth-cli-utils crate * Add utils import
This commit is contained in:
24
crates/cli/utils/Cargo.toml
Normal file
24
crates/cli/utils/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "reth-cli-utils"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/paradigmxyz/reth"
|
||||
readme = "README.md"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
reth-primitives = { path = "../../primitives" }
|
||||
reth-consensus = { path = "../../consensus", features = ["serde"] }
|
||||
reth-db = {path = "../../storage/db", features = ["mdbx", "test-utils"] }
|
||||
|
||||
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
eyre = "0.6.8"
|
||||
shellexpand = "2.1"
|
||||
walkdir = "2.3"
|
||||
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
89
crates/cli/utils/src/chainspec.rs
Normal file
89
crates/cli/utils/src/chainspec.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use reth_primitives::{
|
||||
utils::serde_helpers::deserialize_stringified_u64, Address, Bytes, Header, H256, U256,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
/// Defines a chain, including it's genesis block, chain ID and fork block numbers.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ChainSpecification {
|
||||
/// Consensus configuration.
|
||||
#[serde(rename = "config")]
|
||||
pub consensus: reth_consensus::Config,
|
||||
/// The genesis block of the chain.
|
||||
#[serde(flatten)]
|
||||
pub genesis: Genesis,
|
||||
}
|
||||
|
||||
/// The genesis block specification.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Genesis {
|
||||
/// The genesis header nonce.
|
||||
#[serde(deserialize_with = "deserialize_stringified_u64")]
|
||||
pub nonce: u64,
|
||||
/// The genesis header timestamp.
|
||||
#[serde(deserialize_with = "deserialize_stringified_u64")]
|
||||
pub timestamp: u64,
|
||||
/// The genesis header extra data.
|
||||
pub extra_data: Bytes,
|
||||
/// The genesis header gas limit.
|
||||
#[serde(deserialize_with = "deserialize_stringified_u64")]
|
||||
pub gas_limit: u64,
|
||||
/// The genesis header difficulty.
|
||||
pub difficulty: U256,
|
||||
/// The genesis header mix hash.
|
||||
pub mix_hash: H256,
|
||||
/// The genesis header coinbase address.
|
||||
pub coinbase: Address,
|
||||
/// The genesis state root.
|
||||
pub state_root: H256,
|
||||
/// The initial state of accounts in the genesis block.
|
||||
pub alloc: HashMap<Address, GenesisAccount>,
|
||||
}
|
||||
|
||||
impl From<Genesis> for Header {
|
||||
fn from(genesis: Genesis) -> Header {
|
||||
Header {
|
||||
gas_limit: genesis.gas_limit,
|
||||
difficulty: genesis.difficulty,
|
||||
nonce: genesis.nonce,
|
||||
extra_data: genesis.extra_data,
|
||||
state_root: genesis.state_root,
|
||||
timestamp: genesis.timestamp,
|
||||
mix_hash: genesis.mix_hash,
|
||||
beneficiary: genesis.coinbase,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An account in the state of the genesis block.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct GenesisAccount {
|
||||
/// The nonce of the account at genesis.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nonce: Option<u64>,
|
||||
/// The balance of the account at genesis.
|
||||
pub balance: U256,
|
||||
}
|
||||
|
||||
/// Clap value parser for [ChainSpecification]s that takes either a built-in chainspec or the path
|
||||
/// to a custom one.
|
||||
pub fn chain_spec_value_parser(s: &str) -> Result<ChainSpecification, eyre::Error> {
|
||||
Ok(match s {
|
||||
"mainnet" => {
|
||||
serde_json::from_str(include_str!("../../../../bin/reth/res/chainspec/mainnet.json"))?
|
||||
}
|
||||
"goerli" => {
|
||||
serde_json::from_str(include_str!("../../../../bin/reth/res/chainspec/goerli.json"))?
|
||||
}
|
||||
"sepolia" => {
|
||||
serde_json::from_str(include_str!("../../../../bin/reth/res/chainspec/sepolia.json"))?
|
||||
}
|
||||
_ => {
|
||||
let raw = std::fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?;
|
||||
serde_json::from_str(&raw)?
|
||||
}
|
||||
})
|
||||
}
|
||||
61
crates/cli/utils/src/init.rs
Normal file
61
crates/cli/utils/src/init.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use crate::chainspec::Genesis;
|
||||
use reth_db::{
|
||||
cursor::DbCursorRO,
|
||||
database::Database,
|
||||
mdbx::{Env, WriteMap},
|
||||
tables,
|
||||
transaction::{DbTx, DbTxMut},
|
||||
};
|
||||
use reth_primitives::{Account, Header, H256};
|
||||
use std::{path::Path, sync::Arc};
|
||||
use tracing::debug;
|
||||
|
||||
/// Opens up an existing database or creates a new one at the specified path.
|
||||
pub fn init_db<P: AsRef<Path>>(path: P) -> eyre::Result<Env<WriteMap>> {
|
||||
std::fs::create_dir_all(path.as_ref())?;
|
||||
let db = reth_db::mdbx::Env::<reth_db::mdbx::WriteMap>::open(
|
||||
path.as_ref(),
|
||||
reth_db::mdbx::EnvKind::RW,
|
||||
)?;
|
||||
db.create_tables()?;
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
/// Write the genesis block if it has not already been written
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
pub fn init_genesis<DB: Database>(db: Arc<DB>, genesis: Genesis) -> Result<H256, reth_db::Error> {
|
||||
let tx = db.tx()?;
|
||||
if let Some((_, hash)) = tx.cursor::<tables::CanonicalHeaders>()?.first()? {
|
||||
debug!("Genesis already written, skipping.");
|
||||
return Ok(hash)
|
||||
}
|
||||
drop(tx);
|
||||
debug!("Writing genesis block.");
|
||||
let tx = db.tx_mut()?;
|
||||
|
||||
// Insert account state
|
||||
for (address, account) in &genesis.alloc {
|
||||
tx.put::<tables::PlainAccountState>(
|
||||
*address,
|
||||
Account {
|
||||
nonce: account.nonce.unwrap_or_default(),
|
||||
balance: account.balance,
|
||||
bytecode_hash: None,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
// Insert header
|
||||
let header: Header = genesis.into();
|
||||
let hash = header.hash_slow();
|
||||
tx.put::<tables::CanonicalHeaders>(0, hash)?;
|
||||
tx.put::<tables::HeaderNumbers>(hash, 0)?;
|
||||
tx.put::<tables::BlockBodies>((0, hash).into(), Default::default())?;
|
||||
tx.put::<tables::BlockTransitionIndex>((0, hash).into(), 0)?;
|
||||
tx.put::<tables::HeaderTD>((0, hash).into(), header.difficulty.into())?;
|
||||
tx.put::<tables::Headers>((0, hash).into(), header)?;
|
||||
|
||||
tx.commit()?;
|
||||
Ok(hash)
|
||||
}
|
||||
154
crates/cli/utils/src/lib.rs
Normal file
154
crates/cli/utils/src/lib.rs
Normal file
@ -0,0 +1,154 @@
|
||||
#![warn(missing_docs, unreachable_pub)]
|
||||
#![deny(unused_must_use, rust_2018_idioms)]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
|
||||
//! Utility functions.
|
||||
use reth_primitives::{BlockHashOrNumber, H256};
|
||||
use std::{
|
||||
env::VarError,
|
||||
net::{SocketAddr, ToSocketAddrs},
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
/// Utilities for parsing chainspecs
|
||||
pub mod chainspec;
|
||||
|
||||
/// Utilities for initializing parts of the chain
|
||||
pub mod init;
|
||||
|
||||
/// Finds all files in a directory with a given postfix.
|
||||
pub fn find_all_files_with_postfix(path: &Path, postfix: &str) -> Vec<PathBuf> {
|
||||
WalkDir::new(path)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_name().to_string_lossy().ends_with(postfix))
|
||||
.map(DirEntry::into_path)
|
||||
.collect::<Vec<PathBuf>>()
|
||||
}
|
||||
|
||||
/// Parses a user-specified path with support for environment variables and common shorthands (e.g.
|
||||
/// ~ for the user's home directory).
|
||||
pub fn parse_path(value: &str) -> Result<PathBuf, shellexpand::LookupError<VarError>> {
|
||||
shellexpand::full(value).map(|path| PathBuf::from(path.into_owned()))
|
||||
}
|
||||
|
||||
/// Parse [BlockHashOrNumber]
|
||||
pub fn hash_or_num_value_parser(value: &str) -> Result<BlockHashOrNumber, eyre::Error> {
|
||||
match H256::from_str(value) {
|
||||
Ok(hash) => Ok(BlockHashOrNumber::Hash(hash)),
|
||||
Err(_) => Ok(BlockHashOrNumber::Number(value.parse()?)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) -> Result<SocketAddr, eyre::Error> {
|
||||
if value.is_empty() {
|
||||
eyre::bail!("Cannot parse socket address from an empty string");
|
||||
}
|
||||
|
||||
if value.starts_with(':') || value.parse::<u16>().is_ok() {
|
||||
("localhost", 9000).to_socket_addrs()
|
||||
} else if value.contains(':') {
|
||||
value.to_socket_addrs()
|
||||
} else {
|
||||
(value, 9000).to_socket_addrs()
|
||||
}?
|
||||
.next()
|
||||
.ok_or_else(|| eyre::eyre!("Could not parse socket address from {}", value))
|
||||
}
|
||||
|
||||
/// Tracing utility
|
||||
pub mod reth_tracing {
|
||||
use tracing::Subscriber;
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
|
||||
/// Tracing modes
|
||||
pub enum TracingMode {
|
||||
/// Enable all traces.
|
||||
All,
|
||||
/// Enable debug traces.
|
||||
Debug,
|
||||
/// Enable info traces.
|
||||
Info,
|
||||
/// Enable warn traces.
|
||||
Warn,
|
||||
/// Enable error traces.
|
||||
Error,
|
||||
/// Disable tracing.
|
||||
Silent,
|
||||
}
|
||||
|
||||
impl TracingMode {
|
||||
fn into_env_filter(self) -> EnvFilter {
|
||||
match self {
|
||||
Self::All => EnvFilter::new("reth=trace"),
|
||||
Self::Debug => EnvFilter::new("reth=debug"),
|
||||
Self::Info => EnvFilter::new("reth=info"),
|
||||
Self::Warn => EnvFilter::new("reth=warn"),
|
||||
Self::Error => EnvFilter::new("reth=error"),
|
||||
Self::Silent => EnvFilter::new(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for TracingMode {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
0 => Self::Error,
|
||||
1 => Self::Warn,
|
||||
2 => Self::Info,
|
||||
3 => Self::Debug,
|
||||
_ => Self::All,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build subscriber
|
||||
// TODO: JSON/systemd support
|
||||
pub fn build_subscriber(mods: TracingMode) -> impl Subscriber {
|
||||
// TODO: Auto-detect
|
||||
let no_color = std::env::var("RUST_LOG_STYLE").map(|val| val == "never").unwrap_or(false);
|
||||
let with_target = std::env::var("RUST_LOG_TARGET").map(|val| val != "0").unwrap_or(false);
|
||||
|
||||
// Take env over config
|
||||
let filter = if std::env::var(EnvFilter::DEFAULT_ENV).unwrap_or_default().is_empty() {
|
||||
mods.into_env_filter()
|
||||
} else {
|
||||
EnvFilter::from_default_env()
|
||||
};
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer().with_ansi(!no_color).with_target(with_target))
|
||||
.with(filter)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_socket_addresses() {
|
||||
for value in ["localhost:9000", ":9000", "9000", "localhost"] {
|
||||
let socket_addr = parse_socket_address(value)
|
||||
.expect(&format!("could not parse socket address: {}", value));
|
||||
|
||||
assert!(socket_addr.ip().is_loopback());
|
||||
assert_eq!(socket_addr.port(), 9000);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user