From 1b31a55d62ac9641e06d3ca4d57dd20a461201f1 Mon Sep 17 00:00:00 2001 From: pistomat Date: Tue, 25 Jul 2023 13:33:23 +0200 Subject: [PATCH] feat: add a `--dev` option (#3866) --- bin/reth/src/args/dev_args.rs | 99 ++++++++++++++++++++++++++ bin/reth/src/args/mod.rs | 4 ++ bin/reth/src/args/network_args.rs | 2 +- bin/reth/src/args/rpc_server_args.rs | 2 +- bin/reth/src/args/utils.rs | 4 +- bin/reth/src/node/mod.rs | 53 +++++++++++--- crates/consensus/auto-seal/src/lib.rs | 2 +- crates/primitives/res/genesis/dev.json | 75 +++++++++++++++++++ crates/primitives/src/chain/mod.rs | 7 +- crates/primitives/src/chain/spec.rs | 52 +++++++++++++- crates/primitives/src/constants.rs | 4 ++ crates/primitives/src/lib.rs | 4 +- 12 files changed, 291 insertions(+), 17 deletions(-) create mode 100644 bin/reth/src/args/dev_args.rs create mode 100644 crates/primitives/res/genesis/dev.json diff --git a/bin/reth/src/args/dev_args.rs b/bin/reth/src/args/dev_args.rs new file mode 100644 index 0000000000..5cc02522d1 --- /dev/null +++ b/bin/reth/src/args/dev_args.rs @@ -0,0 +1,99 @@ +//! clap [Args](clap::Args) for Dev testnet configuration +use std::time::Duration; + +use clap::Args; +use humantime::parse_duration; + +/// Parameters for Dev testnet configuration +#[derive(Debug, Args, PartialEq, Default, Clone, Copy)] +#[command(next_help_heading = "Dev testnet")] +pub struct DevArgs { + /// Start the node in dev mode + /// + /// This mode uses a local proof-of-authority consensus engine with either fixed block times + /// or automatically mined blocks. + /// Disables network discovery and enables local http server. + /// Prefunds 20 accounts derived by mnemonic "test test test test test test test test test test + /// test junk" with 10 000 ETH each. + #[arg(long = "dev", alias = "auto-mine", help_heading = "Dev testnet", verbatim_doc_comment)] + pub dev: bool, + + /// How many transactions to mine per block. + #[arg( + long = "dev.block_max_transactions", + help_heading = "Dev testnet", + conflicts_with = "block_time" + )] + pub block_max_transactions: Option, + + /// Interval between blocks. + /// + /// Parses strings using [humantime::parse_duration] + /// --dev.block_time 12s + #[arg( + long = "dev.block_time", + help_heading = "Dev testnet", + conflicts_with = "block_max_transactions", + value_parser = parse_duration, + verbatim_doc_comment + )] + pub block_time: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::Parser; + + /// A helper type to parse Args more easily + #[derive(Parser)] + struct CommandParser { + #[clap(flatten)] + args: T, + } + + #[test] + fn test_parse_dev_args() { + let args = CommandParser::::parse_from(["reth"]).args; + assert_eq!(args, DevArgs { dev: false, block_max_transactions: None, block_time: None }); + + let args = CommandParser::::parse_from(["reth", "--dev"]).args; + assert_eq!(args, DevArgs { dev: true, block_max_transactions: None, block_time: None }); + + let args = CommandParser::::parse_from(["reth", "--auto-mine"]).args; + assert_eq!(args, DevArgs { dev: true, block_max_transactions: None, block_time: None }); + + let args = CommandParser::::parse_from([ + "reth", + "--dev", + "--dev.block_max_transactions", + "2", + ]) + .args; + assert_eq!(args, DevArgs { dev: true, block_max_transactions: Some(2), block_time: None }); + + let args = + CommandParser::::parse_from(["reth", "--dev", "--dev.block_time", "1s"]).args; + assert_eq!( + args, + DevArgs { + dev: true, + block_max_transactions: None, + block_time: Some(std::time::Duration::from_secs(1)) + } + ); + } + + #[test] + fn test_parse_dev_args_conflicts() { + let args = CommandParser::::try_parse_from([ + "reth", + "--dev", + "--dev.block_max_transactions", + "2", + "--dev.block_time", + "1s", + ]); + assert!(args.is_err()); + } +} diff --git a/bin/reth/src/args/mod.rs b/bin/reth/src/args/mod.rs index 05d691e8a3..dd4dd83d0b 100644 --- a/bin/reth/src/args/mod.rs +++ b/bin/reth/src/args/mod.rs @@ -35,4 +35,8 @@ pub use gas_price_oracle_args::GasPriceOracleArgs; mod txpool_args; pub use txpool_args::TxPoolArgs; +/// DevArgs for configuring the dev testnet +mod dev_args; +pub use dev_args::DevArgs; + pub mod utils; diff --git a/bin/reth/src/args/network_args.rs b/bin/reth/src/args/network_args.rs index 8027c3022c..c945fdaf32 100644 --- a/bin/reth/src/args/network_args.rs +++ b/bin/reth/src/args/network_args.rs @@ -110,7 +110,7 @@ impl NetworkArgs { #[derive(Debug, Args)] pub struct DiscoveryArgs { /// Disable the discovery service. - #[arg(short, long)] + #[arg(short, long, default_value_if("dev", "true", "true"))] pub disable_discovery: bool, /// Disable the DNS discovery. diff --git a/bin/reth/src/args/rpc_server_args.rs b/bin/reth/src/args/rpc_server_args.rs index a38cd69944..baf3f94f9f 100644 --- a/bin/reth/src/args/rpc_server_args.rs +++ b/bin/reth/src/args/rpc_server_args.rs @@ -56,7 +56,7 @@ pub(crate) const RPC_DEFAULT_MAX_TRACING_REQUESTS: u32 = 25; #[command(next_help_heading = "RPC")] pub struct RpcServerArgs { /// Enable the HTTP-RPC server - #[arg(long)] + #[arg(long, default_value_if("dev", "true", "true"))] pub http: bool, /// Http server address to listen on diff --git a/bin/reth/src/args/utils.rs b/bin/reth/src/args/utils.rs index 4a11339450..c9fe80685d 100644 --- a/bin/reth/src/args/utils.rs +++ b/bin/reth/src/args/utils.rs @@ -1,7 +1,7 @@ //! Clap parser utilities use reth_primitives::{ - fs, AllGenesisFormats, BlockHashOrNumber, ChainSpec, GOERLI, MAINNET, SEPOLIA, + fs, AllGenesisFormats, BlockHashOrNumber, ChainSpec, DEV, GOERLI, MAINNET, SEPOLIA, }; use reth_revm::primitives::B256 as H256; use std::{ @@ -25,6 +25,7 @@ pub fn chain_spec_value_parser(s: &str) -> eyre::Result, eyre::Er "mainnet" => MAINNET.clone(), "goerli" => GOERLI.clone(), "sepolia" => SEPOLIA.clone(), + "dev" => DEV.clone(), _ => { let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?; serde_json::from_str(&raw)? @@ -39,6 +40,7 @@ pub fn genesis_value_parser(s: &str) -> eyre::Result, eyre::Error "mainnet" => MAINNET.clone(), "goerli" => GOERLI.clone(), "sepolia" => SEPOLIA.clone(), + "dev" => DEV.clone(), _ => { let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?; let genesis: AllGenesisFormats = serde_json::from_str(&raw)?; diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 6a1da252f0..98fa3b1130 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -2,7 +2,7 @@ //! //! Starts the client use crate::{ - args::{get_secret_key, DebugArgs, NetworkArgs, RpcServerArgs, TxPoolArgs}, + args::{get_secret_key, DebugArgs, DevArgs, NetworkArgs, RpcServerArgs, TxPoolArgs}, dirs::DataDirPath, init::init_genesis, prometheus_exporter, @@ -14,7 +14,7 @@ use clap::Parser; use eyre::Context; use fdlimit::raise_fd_limit; use futures::{future::Either, pin_mut, stream, stream_select, StreamExt}; -use reth_auto_seal_consensus::{AutoSealBuilder, AutoSealConsensus}; +use reth_auto_seal_consensus::{AutoSealBuilder, AutoSealConsensus, MiningMode}; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; use reth_beacon_consensus::{BeaconConsensus, BeaconConsensusEngine, MIN_BLOCKS_FOR_PIPELINE_RUN}; use reth_blockchain_tree::{ @@ -112,12 +112,15 @@ pub struct Command { /// - mainnet /// - goerli /// - sepolia + /// - dev #[arg( long, value_name = "CHAIN_OR_PATH", verbatim_doc_comment, default_value = "mainnet", - value_parser = genesis_value_parser + default_value_if("dev", "true", "dev"), + value_parser = genesis_value_parser, + required = false, )] chain: Arc, @@ -145,9 +148,8 @@ pub struct Command { #[clap(flatten)] db: DatabaseArgs, - /// Automatically mine blocks for new transactions - #[arg(long)] - auto_mine: bool, + #[clap(flatten)] + dev: DevArgs, } impl Command { @@ -181,7 +183,7 @@ impl Command { info!(target: "reth::cli", "{}", DisplayHardforks::from(self.chain.hardforks().clone())); - let consensus: Arc = if self.auto_mine { + let consensus: Arc = if self.dev.dev { debug!(target: "reth::cli", "Using auto seal"); Arc::new(AutoSealConsensus::new(Arc::clone(&self.chain))) } else { @@ -304,13 +306,28 @@ impl Command { }; // Configure the pipeline - let (mut pipeline, client) = if self.auto_mine { + let (mut pipeline, client) = if self.dev.dev { + info!(target: "reth::cli", "Starting Reth in dev mode"); + + let mining_mode = if let Some(interval) = self.dev.block_time { + MiningMode::interval(interval) + } else if let Some(max_transactions) = self.dev.block_max_transactions { + MiningMode::instant( + max_transactions, + transaction_pool.pending_transactions_listener(), + ) + } else { + info!(target: "reth::cli", "No mining mode specified, defaulting to ReadyTransaction"); + MiningMode::instant(1, transaction_pool.pending_transactions_listener()) + }; + let (_, client, mut task) = AutoSealBuilder::new( Arc::clone(&self.chain), blockchain_db.clone(), transaction_pool.clone(), consensus_engine_tx.clone(), canon_state_notification_sender, + mining_mode, ) .build(); @@ -798,6 +815,8 @@ async fn run_network_until_shutdown( #[cfg(test)] mod tests { + use reth_primitives::DEV; + use super::*; use std::{net::IpAddr, path::Path}; @@ -869,4 +888,22 @@ mod tests { let db_path = data_dir.db_path(); assert_eq!(db_path, Path::new("my/custom/path/db")); } + + #[test] + fn parse_dev() { + let cmd = Command::parse_from(["reth", "--dev"]); + let chain = 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); + } } diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index 76bf16e28f..668a2117f6 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -115,13 +115,13 @@ where pool: Pool, to_engine: UnboundedSender, canon_state_notification: CanonStateNotificationSender, + mode: MiningMode, ) -> Self { let latest_header = client .latest_header() .ok() .flatten() .unwrap_or_else(|| chain_spec.sealed_genesis_header()); - let mode = MiningMode::interval(std::time::Duration::from_secs(1)); Self { storage: Storage::new(latest_header), diff --git a/crates/primitives/res/genesis/dev.json b/crates/primitives/res/genesis/dev.json new file mode 100644 index 0000000000..46018b128f --- /dev/null +++ b/crates/primitives/res/genesis/dev.json @@ -0,0 +1,75 @@ +{ + "nonce": "0x0", + "timestamp": "0x6490fdd2", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494", + "alloc": { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x90F79bf6EB2c4f870365E785982E1f101E93b906": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x976EA74026E726554dB657fA54763abd0C3a0aa9": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xBcd4042DE499D14e55001CcbB24a551F3b954096": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x71bE63f3384f5fb98995898A86B02Fb2426c5788": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xFABB0ac9d68B0B445fB7357272Ff202C5651694a": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xcd3B766CCDd6AE721141F452C550Ca635964ce71": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x2546BcD3c84621e976D8185a91A922aE77ECEc30": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xbDA5747bFD65F08deb54cb465eB87D40e51B197E": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdD2FD4581271e230360230F9337D5c0430Bf44C0": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199": { + "balance": "0xD3C21BCECCEDA1000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/crates/primitives/src/chain/mod.rs b/crates/primitives/src/chain/mod.rs index 425939ce44..b46519f325 100644 --- a/crates/primitives/src/chain/mod.rs +++ b/crates/primitives/src/chain/mod.rs @@ -11,7 +11,7 @@ use std::{fmt, str::FromStr}; mod spec; pub use spec::{ AllGenesisFormats, ChainSpec, ChainSpecBuilder, DisplayHardforks, ForkCondition, - ForkTimestamps, GOERLI, MAINNET, SEPOLIA, + ForkTimestamps, DEV, GOERLI, MAINNET, SEPOLIA, }; // The chain info module. @@ -44,6 +44,11 @@ impl Chain { Chain::Named(ethers_core::types::Chain::Sepolia) } + /// Returns the dev chain. + pub const fn dev() -> Self { + Chain::Named(ethers_core::types::Chain::Dev) + } + /// The id of the chain pub fn id(&self) -> u64 { match self { diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 0c84dc2753..356c9409b7 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -130,6 +130,43 @@ pub static SEPOLIA: Lazy> = Lazy::new(|| { .into() }); +/// Dev testnet specification +/// +/// Includes 20 prefunded accounts with 10_000 ETH each derived from mnemonic "test test test test +/// test test test test test test test junk". +pub static DEV: Lazy> = Lazy::new(|| { + ChainSpec { + chain: Chain::dev(), + genesis: serde_json::from_str(include_str!("../../res/genesis/dev.json")) + .expect("Can't deserialize Dev testnet genesis json"), + genesis_hash: Some(H256(hex!( + "2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c" + ))), + paris_block_and_final_difficulty: Some((0, U256::from(0))), + fork_timestamps: ForkTimestamps::default().shanghai(0), + hardforks: BTreeMap::from([ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Dao, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(0)), + (Hardfork::MuirGlacier, ForkCondition::Block(0)), + (Hardfork::Berlin, ForkCondition::Block(0)), + (Hardfork::London, ForkCondition::Block(0)), + ( + Hardfork::Paris, + ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) }, + ), + (Hardfork::Shanghai, ForkCondition::Timestamp(0)), + ]), + } + .into() +}); + /// An Ethereum chain specification. /// /// A chain specification describes: @@ -887,8 +924,8 @@ where mod tests { use crate::{ Address, AllGenesisFormats, Chain, ChainSpec, ChainSpecBuilder, DisplayHardforks, - ForkCondition, ForkHash, ForkId, Genesis, Hardfork, Head, GOERLI, H256, MAINNET, SEPOLIA, - U256, + ForkCondition, ForkHash, ForkId, Genesis, Hardfork, Head, DEV, GOERLI, H256, MAINNET, + SEPOLIA, U256, }; use bytes::BytesMut; use ethers_core::types as EtherType; @@ -1188,6 +1225,17 @@ Post-merge hard forks (timestamp based): ); } + #[test] + fn dev_forkids() { + test_fork_ids( + &DEV, + &[( + Head { number: 0, ..Default::default() }, + ForkId { hash: ForkHash([0x45, 0xb8, 0x36, 0x12]), next: 0 }, + )], + ) + } + /// Checks that time-based forks work /// /// This is based off of the test vectors here: https://github.com/ethereum/go-ethereum/blob/5c8cc10d1e05c23ff1108022f4150749e73c0ca1/core/forkid/forkid_test.go#L155-L188 diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs index 048a8801c4..c592204827 100644 --- a/crates/primitives/src/constants.rs +++ b/crates/primitives/src/constants.rs @@ -77,6 +77,10 @@ pub const GOERLI_GENESIS: H256 = pub const SEPOLIA_GENESIS: H256 = H256(hex!("25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9")); +/// Testnet genesis hash. +pub const DEV_GENESIS: H256 = + H256(hex!("2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c")); + /// Keccak256 over empty array. pub const KECCAK_EMPTY: H256 = H256(hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")); diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 35df6932c1..64faa1e03b 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -60,11 +60,11 @@ pub use block::{ pub use bloom::Bloom; pub use chain::{ AllGenesisFormats, Chain, ChainInfo, ChainSpec, ChainSpecBuilder, DisplayHardforks, - ForkCondition, ForkTimestamps, GOERLI, MAINNET, SEPOLIA, + ForkCondition, ForkTimestamps, DEV, GOERLI, MAINNET, SEPOLIA, }; pub use compression::*; pub use constants::{ - EMPTY_OMMER_ROOT, GOERLI_GENESIS, KECCAK_EMPTY, MAINNET_GENESIS, SEPOLIA_GENESIS, + DEV_GENESIS, EMPTY_OMMER_ROOT, GOERLI_GENESIS, KECCAK_EMPTY, MAINNET_GENESIS, SEPOLIA_GENESIS, }; pub use forkid::{ForkFilter, ForkHash, ForkId, ForkTransition, ValidationError}; pub use genesis::{Genesis, GenesisAccount};