From 3c2d3a083346b5c7710734f7076c528ca4ce18ae Mon Sep 17 00:00:00 2001 From: Nil Medvedev Date: Mon, 11 Mar 2024 17:40:30 +0000 Subject: [PATCH] Feat: add signers (#6826) Co-authored-by: Matthias Seitz --- Cargo.lock | 2 ++ Cargo.toml | 1 + crates/node-builder/src/builder.rs | 8 ++++++- crates/rpc/rpc-api/src/eth.rs | 2 +- crates/rpc/rpc/Cargo.toml | 2 ++ crates/rpc/rpc/src/eth/api/mod.rs | 7 +++--- crates/rpc/rpc/src/eth/api/server.rs | 4 ++-- crates/rpc/rpc/src/eth/api/sign.rs | 17 +++++++++---- crates/rpc/rpc/src/eth/api/transactions.rs | 2 +- crates/rpc/rpc/src/eth/signer.rs | 28 +++++++++++++++++++++- crates/tasks/Cargo.toml | 2 +- 11 files changed, 60 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c20503f43..ca2cd0b5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 25315d5b0..d9d4bfb3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/node-builder/src/builder.rs b/crates/node-builder/src/builder.rs index 2e1a8151d..a5461d6ed 100644 --- a/crates/node-builder/src/builder.rs +++ b/crates/node-builder/src/builder.rs @@ -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"); diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-api/src/eth.rs index 7dc39333b..79e2251d6 100644 --- a/crates/rpc/rpc-api/src/eth.rs +++ b/crates/rpc/rpc-api/src/eth.rs @@ -27,7 +27,7 @@ pub trait EthApi { /// Returns a list of addresses owned by client. #[method(name = "accounts")] - async fn accounts(&self) -> RpcResult>; + fn accounts(&self) -> RpcResult>; /// Returns the number of most recent block. #[method(name = "blockNumber")] diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 4d2b23c1d..9cf244381 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -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"] } diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index 0d229e0a7..43d7f0d30 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -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
{ - 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 { /// An interface to interact with the network network: Network, /// All configured Signers - signers: Vec>, + signers: parking_lot::RwLock>>, /// The async cache frontend for eth related data eth_cache: EthStateCache, /// The async gas oracle frontend for gas price suggestions diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index d6adca562..0a2d3ab98 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -65,7 +65,7 @@ where } /// Handler for: `eth_accounts` - async fn accounts(&self) -> Result> { + fn accounts(&self) -> Result> { 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 { 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` diff --git a/crates/rpc/rpc/src/eth/api/sign.rs b/crates/rpc/rpc/src/eth/api/sign.rs index bf1891011..66df0e8de 100644 --- a/crates/rpc/rpc/src/eth/api/sign.rs +++ b/crates/rpc/rpc/src/eth/api/sign.rs @@ -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 EthApi { pub(crate) async fn sign(&self, account: Address, message: Bytes) -> EthResult { 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 { + pub(crate) fn sign_typed_data(&self, data: Value, account: Address) -> EthResult { Ok(self .find_signer(&account)? .sign_typed_data( @@ -31,12 +30,20 @@ impl EthApi Result<&(dyn EthSigner + 'static), SignError> { + ) -> Result, 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); + } } diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index d2f308ff5..d49ec2d35 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -1340,7 +1340,7 @@ where from: &Address, request: TypedTransactionRequest, ) -> EthResult { - 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), diff --git a/crates/rpc/rpc/src/eth/signer.rs b/crates/rpc/rpc/src/eth/signer.rs index 7dd050492..b744d83ef 100644 --- a/crates/rpc/rpc/src/eth/signer.rs +++ b/crates/rpc/rpc/src/eth/signer.rs @@ -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 = std::result::Result; /// 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
; @@ -38,13 +39,38 @@ pub(crate) trait EthSigner: Send + Sync { fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result; } +dyn_clone::clone_trait_object!(EthSigner); + /// Holds developer keys +#[derive(Clone)] pub(crate) struct DevSigner { addresses: Vec
, accounts: HashMap, } +#[allow(dead_code)] impl DevSigner { + /// Generates a random dev signer which satisfies [EthSigner] trait + pub(crate) fn random() -> Box { + 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> { + 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); + } + signers + } + fn get_key(&self, account: Address) -> Result<&SecretKey> { self.accounts.get(&account).ok_or(SignError::NoAccount) } diff --git a/crates/tasks/Cargo.toml b/crates/tasks/Cargo.toml index 4abeba770..63eb870fc 100644 --- a/crates/tasks/Cargo.toml +++ b/crates/tasks/Cargo.toml @@ -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 }