Feat: add signers (#6826)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Nil Medvedev
2024-03-11 17:40:30 +00:00
committed by GitHub
parent ef1a196c54
commit 3c2d3a0833
11 changed files with 60 additions and 15 deletions

2
Cargo.lock generated
View File

@ -6461,6 +6461,7 @@ dependencies = [
"async-trait",
"bytes",
"derive_more",
"dyn-clone",
"futures",
"http",
"http-body",
@ -6468,6 +6469,7 @@ dependencies = [
"jsonrpsee",
"jsonwebtoken",
"metrics",
"parking_lot 0.12.1",
"pin-project",
"rand 0.8.5",
"reqwest",

View File

@ -237,6 +237,7 @@ once_cell = "1.17"
syn = "2.0"
nybbles = "0.2.1"
smallvec = "1"
dyn-clone = "1.0.17"
# proc-macros
proc-macro2 = "1.0"

View File

@ -559,6 +559,7 @@ where
// Configure the pipeline
let (mut pipeline, client) = if config.dev.dev {
info!(target: "reth::cli", "Starting Reth in dev mode");
for (idx, (address, alloc)) in config.chain.genesis.alloc.iter().enumerate() {
info!(target: "reth::cli", "Allocated Genesis Account: {:02}. {} ({} ETH)", idx, address.to_string(), format_ether(alloc.balance));
}
@ -691,7 +692,7 @@ where
// Start RPC servers
let (rpc_server_handles, rpc_registry) = crate::rpc::launch_rpc_servers(
let (rpc_server_handles, mut rpc_registry) = crate::rpc::launch_rpc_servers(
node_components.clone(),
engine_api,
&config,
@ -700,6 +701,11 @@ where
)
.await?;
// in dev mode we generate 20 random dev-signer accounts
if config.dev.dev {
rpc_registry.eth_api().with_dev_accounts();
}
// Run consensus engine to completion
let (tx, rx) = oneshot::channel();
info!(target: "reth::cli", "Starting consensus engine");

View File

@ -27,7 +27,7 @@ pub trait EthApi {
/// Returns a list of addresses owned by client.
#[method(name = "accounts")]
async fn accounts(&self) -> RpcResult<Vec<Address>>;
fn accounts(&self) -> RpcResult<Vec<Address>>;
/// Returns the number of most recent block.
#[method(name = "blockNumber")]

View File

@ -59,6 +59,7 @@ tokio = { workspace = true, features = ["sync"] }
tower = "0.4"
tokio-stream = { workspace = true, features = ["sync"] }
pin-project.workspace = true
parking_lot.workspace = true
# metrics
reth-metrics.workspace = true
@ -80,6 +81,7 @@ tracing-futures = "0.2"
schnellru.workspace = true
futures.workspace = true
derive_more.workspace = true
dyn-clone.workspace = true
[dev-dependencies]
jsonrpsee = { workspace = true, features = ["client"] }

View File

@ -39,6 +39,7 @@ use tokio::sync::{oneshot, Mutex};
mod block;
mod call;
pub(crate) mod fee_history;
mod fees;
#[cfg(feature = "optimism")]
mod optimism;
@ -144,7 +145,7 @@ where
provider,
pool,
network,
signers: Default::default(),
signers: parking_lot::RwLock::new(Default::default()),
eth_cache,
gas_oracle,
gas_cap,
@ -404,7 +405,7 @@ where
}
fn accounts(&self) -> Vec<Address> {
self.inner.signers.iter().flat_map(|s| s.accounts()).collect()
self.inner.signers.read().iter().flat_map(|s| s.accounts()).collect()
}
fn is_syncing(&self) -> bool {
@ -469,7 +470,7 @@ struct EthApiInner<Provider, Pool, Network, EvmConfig> {
/// An interface to interact with the network
network: Network,
/// All configured Signers
signers: Vec<Box<dyn EthSigner>>,
signers: parking_lot::RwLock<Vec<Box<dyn EthSigner>>>,
/// The async cache frontend for eth related data
eth_cache: EthStateCache,
/// The async gas oracle frontend for gas price suggestions

View File

@ -65,7 +65,7 @@ where
}
/// Handler for: `eth_accounts`
async fn accounts(&self) -> Result<Vec<Address>> {
fn accounts(&self) -> Result<Vec<Address>> {
trace!(target: "rpc::eth", "Serving eth_accounts");
Ok(EthApiSpec::accounts(self))
}
@ -408,7 +408,7 @@ where
/// Handler for: `eth_signTypedData`
async fn sign_typed_data(&self, address: Address, data: Value) -> Result<Bytes> {
trace!(target: "rpc::eth", ?address, ?data, "Serving eth_signTypedData");
Ok(EthApi::sign_typed_data(self, data, address).await?)
Ok(EthApi::sign_typed_data(self, data, address)?)
}
/// Handler for: `eth_getProof`

View File

@ -3,21 +3,20 @@
use crate::{
eth::{
error::{EthResult, SignError},
signer::EthSigner,
signer::{DevSigner, EthSigner},
},
EthApi,
};
use alloy_dyn_abi::TypedData;
use reth_primitives::{Address, Bytes};
use serde_json::Value;
use std::ops::Deref;
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig> {
pub(crate) async fn sign(&self, account: Address, message: Bytes) -> EthResult<Bytes> {
Ok(self.find_signer(&account)?.sign(account, &message).await?.to_hex_bytes())
}
pub(crate) async fn sign_typed_data(&self, data: Value, account: Address) -> EthResult<Bytes> {
pub(crate) fn sign_typed_data(&self, data: Value, account: Address) -> EthResult<Bytes> {
Ok(self
.find_signer(&account)?
.sign_typed_data(
@ -31,12 +30,20 @@ impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConf
pub(crate) fn find_signer(
&self,
account: &Address,
) -> Result<&(dyn EthSigner + 'static), SignError> {
) -> Result<Box<(dyn EthSigner + 'static)>, SignError> {
self.inner
.signers
.read()
.iter()
.find(|signer| signer.is_signer_for(account))
.map(|signer| signer.deref())
.map(|signer| dyn_clone::clone_box(&**signer))
.ok_or(SignError::NoAccount)
}
/// Generates 20 random developer accounts.
/// Used in DEV mode.
pub fn with_dev_accounts(&mut self) {
let mut signers = self.inner.signers.write();
*signers = DevSigner::random_signers(20);
}
}

View File

@ -1340,7 +1340,7 @@ where
from: &Address,
request: TypedTransactionRequest,
) -> EthResult<TransactionSigned> {
for signer in self.inner.signers.iter() {
for signer in self.inner.signers.read().iter() {
if signer.is_signer_for(from) {
return match signer.sign_transaction(request, from) {
Ok(tx) => Ok(tx),

View File

@ -7,6 +7,7 @@ use reth_primitives::{
};
use reth_rpc_types::TypedTransactionRequest;
use dyn_clone::DynClone;
use reth_rpc_types_compat::transaction::to_primitive_transaction;
use secp256k1::SecretKey;
use std::collections::HashMap;
@ -15,7 +16,7 @@ type Result<T> = std::result::Result<T, SignError>;
/// An Ethereum Signer used via RPC.
#[async_trait::async_trait]
pub(crate) trait EthSigner: Send + Sync {
pub(crate) trait EthSigner: Send + Sync + DynClone {
/// Returns the available accounts for this signer.
fn accounts(&self) -> Vec<Address>;
@ -38,13 +39,38 @@ pub(crate) trait EthSigner: Send + Sync {
fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result<Signature>;
}
dyn_clone::clone_trait_object!(EthSigner);
/// Holds developer keys
#[derive(Clone)]
pub(crate) struct DevSigner {
addresses: Vec<Address>,
accounts: HashMap<Address, SecretKey>,
}
#[allow(dead_code)]
impl DevSigner {
/// Generates a random dev signer which satisfies [EthSigner] trait
pub(crate) fn random() -> Box<dyn EthSigner> {
let mut signers = Self::random_signers(1);
signers.pop().expect("expect to generate at leas one signer")
}
/// Generates provided number of random dev signers
/// which satisfy [EthSigner] trait
pub(crate) fn random_signers(num: u32) -> Vec<Box<dyn EthSigner + 'static>> {
let mut signers = Vec::new();
for _ in 0..num {
let (sk, pk) = secp256k1::generate_keypair(&mut rand::thread_rng());
let address = reth_primitives::public_key_to_address(pk);
let addresses = vec![address];
let accounts = HashMap::from([(address, sk)]);
signers.push(Box::new(DevSigner { addresses, accounts }) as Box<dyn EthSigner>);
}
signers
}
fn get_key(&self, account: Address) -> Result<&SecretKey> {
self.accounts.get(&account).ok_or(SignError::NoAccount)
}

View File

@ -25,7 +25,7 @@ metrics.workspace = true
# misc
tracing.workspace = true
thiserror.workspace = true
dyn-clone = "1.0"
dyn-clone.workspace = true
# feature `rayon`
rayon = { workspace = true, optional = true }