mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-09 15:28:01 -05:00
feat: derive dev accounts from mnemonic in dev mode (#18299)
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
This commit is contained in:
@@ -23,8 +23,8 @@ use reth_node_core::{
|
||||
version::{version_metadata, CLIENT_CODE},
|
||||
};
|
||||
use reth_payload_builder::{PayloadBuilderHandle, PayloadStore};
|
||||
use reth_rpc::eth::{core::EthRpcConverterFor, EthApiTypes, FullEthApiServer};
|
||||
use reth_rpc_api::{eth::helpers::AddDevSigners, IntoEngineApiRpcModule};
|
||||
use reth_rpc::eth::{core::EthRpcConverterFor, DevSigner, EthApiTypes, FullEthApiServer};
|
||||
use reth_rpc_api::{eth::helpers::EthTransactions, IntoEngineApiRpcModule};
|
||||
use reth_rpc_builder::{
|
||||
auth::{AuthRpcModule, AuthServerHandle},
|
||||
config::RethRpcServerConfig,
|
||||
@@ -991,7 +991,8 @@ where
|
||||
|
||||
// in dev mode we generate 20 random dev-signer accounts
|
||||
if config.dev.dev {
|
||||
registry.eth_api().with_dev_accounts();
|
||||
let signers = DevSigner::from_mnemonic(config.dev.dev_mnemonic.as_str(), 20);
|
||||
registry.eth_api().signers().write().extend(signers);
|
||||
}
|
||||
|
||||
let mut registry = RpcRegistry { registry };
|
||||
@@ -1163,7 +1164,6 @@ pub trait EthApiBuilder<N: FullNodeComponents>: Default + Send + 'static {
|
||||
/// The Ethapi implementation this builder will build.
|
||||
type EthApi: EthApiTypes
|
||||
+ FullEthApiServer<Provider = N::Provider, Pool = N::Pool>
|
||||
+ AddDevSigners
|
||||
+ Unpin
|
||||
+ 'static;
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ use std::time::Duration;
|
||||
use clap::Args;
|
||||
use humantime::parse_duration;
|
||||
|
||||
const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
|
||||
|
||||
/// Parameters for Dev testnet configuration
|
||||
#[derive(Debug, Args, PartialEq, Eq, Default, Clone, Copy)]
|
||||
#[derive(Debug, Args, PartialEq, Eq, Clone)]
|
||||
#[command(next_help_heading = "Dev testnet")]
|
||||
pub struct DevArgs {
|
||||
/// Start the node in dev mode
|
||||
@@ -39,6 +41,28 @@ pub struct DevArgs {
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
pub block_time: Option<Duration>,
|
||||
|
||||
/// Derive dev accounts from a fixed mnemonic instead of random ones.
|
||||
#[arg(
|
||||
long = "dev.mnemonic",
|
||||
help_heading = "Dev testnet",
|
||||
value_name = "MNEMONIC",
|
||||
requires = "dev",
|
||||
verbatim_doc_comment,
|
||||
default_value = DEFAULT_MNEMONIC
|
||||
)]
|
||||
pub dev_mnemonic: String,
|
||||
}
|
||||
|
||||
impl Default for DevArgs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dev: false,
|
||||
block_max_transactions: None,
|
||||
block_time: None,
|
||||
dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -56,13 +80,37 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_dev_args() {
|
||||
let args = CommandParser::<DevArgs>::parse_from(["reth"]).args;
|
||||
assert_eq!(args, DevArgs { dev: false, block_max_transactions: None, block_time: None });
|
||||
assert_eq!(
|
||||
args,
|
||||
DevArgs {
|
||||
dev: false,
|
||||
block_max_transactions: None,
|
||||
block_time: None,
|
||||
dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
|
||||
}
|
||||
);
|
||||
|
||||
let args = CommandParser::<DevArgs>::parse_from(["reth", "--dev"]).args;
|
||||
assert_eq!(args, DevArgs { dev: true, block_max_transactions: None, block_time: None });
|
||||
assert_eq!(
|
||||
args,
|
||||
DevArgs {
|
||||
dev: true,
|
||||
block_max_transactions: None,
|
||||
block_time: None,
|
||||
dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
|
||||
}
|
||||
);
|
||||
|
||||
let args = CommandParser::<DevArgs>::parse_from(["reth", "--auto-mine"]).args;
|
||||
assert_eq!(args, DevArgs { dev: true, block_max_transactions: None, block_time: None });
|
||||
assert_eq!(
|
||||
args,
|
||||
DevArgs {
|
||||
dev: true,
|
||||
block_max_transactions: None,
|
||||
block_time: None,
|
||||
dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
|
||||
}
|
||||
);
|
||||
|
||||
let args = CommandParser::<DevArgs>::parse_from([
|
||||
"reth",
|
||||
@@ -71,7 +119,15 @@ mod tests {
|
||||
"2",
|
||||
])
|
||||
.args;
|
||||
assert_eq!(args, DevArgs { dev: true, block_max_transactions: Some(2), block_time: None });
|
||||
assert_eq!(
|
||||
args,
|
||||
DevArgs {
|
||||
dev: true,
|
||||
block_max_transactions: Some(2),
|
||||
block_time: None,
|
||||
dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
|
||||
}
|
||||
);
|
||||
|
||||
let args =
|
||||
CommandParser::<DevArgs>::parse_from(["reth", "--dev", "--dev.block-time", "1s"]).args;
|
||||
@@ -80,7 +136,8 @@ mod tests {
|
||||
DevArgs {
|
||||
dev: true,
|
||||
block_max_transactions: None,
|
||||
block_time: Some(std::time::Duration::from_secs(1))
|
||||
block_time: Some(std::time::Duration::from_secs(1)),
|
||||
dev_mnemonic: DEFAULT_MNEMONIC.to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ impl<ChainSpec> NodeConfig<ChainSpec> {
|
||||
}
|
||||
|
||||
/// Set the dev args for the node
|
||||
pub const fn with_dev(mut self, dev: DevArgs) -> Self {
|
||||
pub fn with_dev(mut self, dev: DevArgs) -> Self {
|
||||
self.dev = dev;
|
||||
self
|
||||
}
|
||||
@@ -519,7 +519,7 @@ impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
|
||||
builder: self.builder.clone(),
|
||||
debug: self.debug.clone(),
|
||||
db: self.db,
|
||||
dev: self.dev,
|
||||
dev: self.dev.clone(),
|
||||
pruning: self.pruning.clone(),
|
||||
datadir: self.datadir.clone(),
|
||||
engine: self.engine.clone(),
|
||||
|
||||
@@ -26,19 +26,19 @@ use reth_optimism_flashblocks::{
|
||||
ExecutionPayloadBaseV1, FlashBlockBuildInfo, FlashBlockCompleteSequenceRx, FlashBlockService,
|
||||
InProgressFlashBlockRx, PendingBlockRx, PendingFlashBlock, WsFlashBlockStream,
|
||||
};
|
||||
use reth_rpc::eth::{core::EthApiInner, DevSigner};
|
||||
use reth_rpc::eth::core::EthApiInner;
|
||||
use reth_rpc_eth_api::{
|
||||
helpers::{
|
||||
pending_block::BuildPendingEnv, AddDevSigners, EthApiSpec, EthFees, EthState, LoadFee,
|
||||
LoadPendingBlock, LoadState, SpawnBlocking, Trace,
|
||||
pending_block::BuildPendingEnv, EthApiSpec, EthFees, EthState, LoadFee, LoadPendingBlock,
|
||||
LoadState, SpawnBlocking, Trace,
|
||||
},
|
||||
EthApiTypes, FromEvmError, FullEthApiServer, RpcConvert, RpcConverter, RpcNodeCore,
|
||||
RpcNodeCoreExt, RpcTypes, SignableTxRequest,
|
||||
RpcNodeCoreExt, RpcTypes,
|
||||
};
|
||||
use reth_rpc_eth_types::{
|
||||
EthStateCache, FeeHistoryCache, GasPriceOracle, PendingBlock, PendingBlockEnvOrigin,
|
||||
};
|
||||
use reth_storage_api::{ProviderHeader, ProviderTx};
|
||||
use reth_storage_api::ProviderHeader;
|
||||
use reth_tasks::{
|
||||
pool::{BlockingTaskGuard, BlockingTaskPool},
|
||||
TaskSpawner,
|
||||
@@ -335,18 +335,6 @@ where
|
||||
{
|
||||
}
|
||||
|
||||
impl<N, Rpc> AddDevSigners for OpEthApi<N, Rpc>
|
||||
where
|
||||
N: RpcNodeCore,
|
||||
Rpc: RpcConvert<
|
||||
Network: RpcTypes<TransactionRequest: SignableTxRequest<ProviderTx<N::Provider>>>,
|
||||
>,
|
||||
{
|
||||
fn with_dev_accounts(&self) {
|
||||
*self.inner.eth_api.signers().write() = DevSigner::random_signers(20)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: RpcNodeCore, Rpc: RpcConvert> fmt::Debug for OpEthApi<N, Rpc> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("OpEthApi").finish_non_exhaustive()
|
||||
@@ -483,7 +471,7 @@ where
|
||||
NetworkT: RpcTypes,
|
||||
OpRpcConvert<N, NetworkT>: RpcConvert<Network = NetworkT>,
|
||||
OpEthApi<N, OpRpcConvert<N, NetworkT>>:
|
||||
FullEthApiServer<Provider = N::Provider, Pool = N::Pool> + AddDevSigners,
|
||||
FullEthApiServer<Provider = N::Provider, Pool = N::Pool>,
|
||||
{
|
||||
type EthApi = OpEthApi<N, OpRpcConvert<N, NetworkT>>;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
fees::{CallFees, CallFeesError},
|
||||
RpcHeader, RpcReceipt, RpcTransaction, RpcTxReq, RpcTypes,
|
||||
RpcHeader, RpcReceipt, RpcTransaction, RpcTxReq, RpcTypes, SignableTxRequest,
|
||||
};
|
||||
use alloy_consensus::{
|
||||
error::ValueError, transaction::Recovered, EthereumTxEnvelope, Sealable, TxEip4844,
|
||||
@@ -128,7 +128,7 @@ pub trait RpcConvert: Send + Sync + Unpin + Debug + DynClone + 'static {
|
||||
|
||||
/// Associated upper layer JSON-RPC API network requests and responses to convert from and into
|
||||
/// types of [`Self::Primitives`].
|
||||
type Network: RpcTypes + Send + Sync + Unpin + Clone + Debug;
|
||||
type Network: RpcTypes<TransactionRequest: SignableTxRequest<TxTy<Self::Primitives>>>;
|
||||
|
||||
/// An associated RPC conversion error.
|
||||
type Error: error::Error + Into<jsonrpsee_types::ErrorObject<'static>>;
|
||||
@@ -901,7 +901,7 @@ impl<N, Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv> RpcConvert
|
||||
for RpcConverter<Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv>
|
||||
where
|
||||
N: NodePrimitives,
|
||||
Network: RpcTypes + Send + Sync + Unpin + Clone + Debug,
|
||||
Network: RpcTypes<TransactionRequest: SignableTxRequest<N::SignedTx>>,
|
||||
Evm: ConfigureEvm<Primitives = N> + 'static,
|
||||
Receipt: ReceiptConverter<
|
||||
N,
|
||||
|
||||
@@ -34,7 +34,7 @@ pub use call::{Call, EthCall};
|
||||
pub use fee::{EthFees, LoadFee};
|
||||
pub use pending_block::LoadPendingBlock;
|
||||
pub use receipt::LoadReceipt;
|
||||
pub use signer::{AddDevSigners, EthSigner};
|
||||
pub use signer::EthSigner;
|
||||
pub use spec::EthApiSpec;
|
||||
pub use state::{EthState, LoadState};
|
||||
pub use trace::Trace;
|
||||
|
||||
@@ -32,11 +32,3 @@ pub trait EthSigner<T, TxReq = TransactionRequest>: Send + Sync + DynClone {
|
||||
}
|
||||
|
||||
dyn_clone::clone_trait_object!(<T> EthSigner<T>);
|
||||
|
||||
/// Adds 20 random dev signers for access via the API. Used in dev mode.
|
||||
#[auto_impl::auto_impl(&)]
|
||||
pub trait AddDevSigners {
|
||||
/// Generates 20 random developer accounts.
|
||||
/// Used in DEV mode.
|
||||
fn with_dev_accounts(&self);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
|
||||
use crate::{AsEthApiError, FromEthApiError, RpcNodeCore};
|
||||
use alloy_rpc_types_eth::Block;
|
||||
use reth_chain_state::CanonStateSubscriptions;
|
||||
use reth_rpc_convert::RpcConvert;
|
||||
use reth_rpc_convert::{RpcConvert, SignableTxRequest};
|
||||
pub use reth_rpc_convert::{RpcTransaction, RpcTxReq, RpcTypes};
|
||||
use reth_storage_api::{ProviderTx, ReceiptProvider, TransactionsProvider};
|
||||
use reth_transaction_pool::{PoolTransaction, TransactionPool};
|
||||
use reth_storage_api::ProviderTx;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self},
|
||||
@@ -52,12 +50,11 @@ pub type RpcError<T> = <T as EthApiTypes>::Error;
|
||||
/// Helper trait holds necessary trait bounds on [`EthApiTypes`] to implement `eth` API.
|
||||
pub trait FullEthApiTypes
|
||||
where
|
||||
Self: RpcNodeCore<
|
||||
Provider: TransactionsProvider + ReceiptProvider + CanonStateSubscriptions,
|
||||
Pool: TransactionPool<
|
||||
Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>,
|
||||
Self: RpcNodeCore
|
||||
+ EthApiTypes<
|
||||
NetworkTypes: RpcTypes<
|
||||
TransactionRequest: SignableTxRequest<ProviderTx<Self::Provider>>,
|
||||
>,
|
||||
> + EthApiTypes<
|
||||
RpcConvert: RpcConvert<
|
||||
Primitives = Self::Primitives,
|
||||
Network = Self::NetworkTypes,
|
||||
@@ -68,12 +65,11 @@ where
|
||||
}
|
||||
|
||||
impl<T> FullEthApiTypes for T where
|
||||
T: RpcNodeCore<
|
||||
Provider: TransactionsProvider + ReceiptProvider + CanonStateSubscriptions,
|
||||
Pool: TransactionPool<
|
||||
Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>,
|
||||
T: RpcNodeCore
|
||||
+ EthApiTypes<
|
||||
NetworkTypes: RpcTypes<
|
||||
TransactionRequest: SignableTxRequest<ProviderTx<Self::Provider>>,
|
||||
>,
|
||||
> + EthApiTypes<
|
||||
RpcConvert: RpcConvert<
|
||||
Primitives = <Self as RpcNodeCore>::Primitives,
|
||||
Network = Self::NetworkTypes,
|
||||
|
||||
@@ -45,7 +45,7 @@ reth-trie-common.workspace = true
|
||||
alloy-evm = { workspace = true, features = ["overrides"] }
|
||||
alloy-consensus.workspace = true
|
||||
alloy-signer.workspace = true
|
||||
alloy-signer-local.workspace = true
|
||||
alloy-signer-local = { workspace = true, features = ["mnemonic"] }
|
||||
alloy-eips = { workspace = true, features = ["kzg"] }
|
||||
alloy-dyn-abi.workspace = true
|
||||
alloy-genesis.workspace = true
|
||||
|
||||
@@ -1,33 +1,14 @@
|
||||
//! An abstraction over ethereum signers.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::EthApi;
|
||||
use alloy_dyn_abi::TypedData;
|
||||
use alloy_eips::eip2718::Decodable2718;
|
||||
use alloy_primitives::{eip191_hash_message, Address, Signature, B256};
|
||||
use alloy_signer::SignerSync;
|
||||
use alloy_signer_local::PrivateKeySigner;
|
||||
use reth_rpc_convert::{RpcConvert, RpcTypes, SignableTxRequest};
|
||||
use reth_rpc_eth_api::{
|
||||
helpers::{signer::Result, AddDevSigners, EthSigner},
|
||||
FromEvmError, RpcNodeCore,
|
||||
};
|
||||
use reth_rpc_eth_types::{EthApiError, SignError};
|
||||
use reth_storage_api::ProviderTx;
|
||||
|
||||
impl<N, Rpc> AddDevSigners for EthApi<N, Rpc>
|
||||
where
|
||||
N: RpcNodeCore,
|
||||
EthApiError: FromEvmError<N::Evm>,
|
||||
Rpc: RpcConvert<
|
||||
Network: RpcTypes<TransactionRequest: SignableTxRequest<ProviderTx<N::Provider>>>,
|
||||
>,
|
||||
{
|
||||
fn with_dev_accounts(&self) {
|
||||
*self.inner.signers().write() = DevSigner::random_signers(20)
|
||||
}
|
||||
}
|
||||
use alloy_signer_local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner};
|
||||
use reth_rpc_convert::SignableTxRequest;
|
||||
use reth_rpc_eth_api::helpers::{signer::Result, EthSigner};
|
||||
use reth_rpc_eth_types::SignError;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Holds developer keys
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -55,6 +36,32 @@ impl DevSigner {
|
||||
signers
|
||||
}
|
||||
|
||||
/// Generates dev signers deterministically from a fixed mnemonic.
|
||||
/// Uses the Ethereum derivation path: `m/44'/60'/0'/0/{index}`
|
||||
pub fn from_mnemonic<T: Decodable2718, TxReq: SignableTxRequest<T>>(
|
||||
mnemonic: &str,
|
||||
num: u32,
|
||||
) -> Vec<Box<dyn EthSigner<T, TxReq> + 'static>> {
|
||||
let mut signers = Vec::with_capacity(num as usize);
|
||||
|
||||
for i in 0..num {
|
||||
let sk = MnemonicBuilder::<English>::default()
|
||||
.phrase(mnemonic)
|
||||
.index(i)
|
||||
.expect("invalid derivation path")
|
||||
.build()
|
||||
.expect("failed to build signer from mnemonic");
|
||||
|
||||
let address = sk.address();
|
||||
let addresses = vec![address];
|
||||
let accounts = HashMap::from([(address, sk)]);
|
||||
|
||||
signers.push(Box::new(Self { addresses, accounts }) as Box<dyn EthSigner<T, TxReq>>);
|
||||
}
|
||||
|
||||
signers
|
||||
}
|
||||
|
||||
fn get_key(&self, account: Address) -> Result<&PrivateKeySigner> {
|
||||
self.accounts.get(&account).ok_or(SignError::NoAccount)
|
||||
}
|
||||
|
||||
@@ -734,6 +734,11 @@ Dev testnet:
|
||||
Parses strings using [`humantime::parse_duration`]
|
||||
--dev.block-time 12s
|
||||
|
||||
--dev.mnemonic <MNEMONIC>
|
||||
Derive dev accounts from a fixed mnemonic instead of random ones.
|
||||
|
||||
[default: "test test test test test test test test test test test junk"]
|
||||
|
||||
Pruning:
|
||||
--full
|
||||
Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored
|
||||
|
||||
Reference in New Issue
Block a user