From 0c0a99cc56b78aa6a79c3d57b4f4b051a5e357a3 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sun, 6 Nov 2022 11:33:35 -0500 Subject: [PATCH 01/54] tell Geth to expose the admin namespace --- ethers-core/src/utils/geth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 2a65ab943..9927438df 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -10,7 +10,7 @@ use std::{ const GETH_STARTUP_TIMEOUT_MILLIS: u64 = 10_000; /// The exposed APIs -const API: &str = "eth,net,web3,txpool"; +const API: &str = "eth,net,web3,txpool,admin"; /// The geth command const GETH: &str = "geth"; From a350324d3e1ccd980b53ac39a3b1c06c6a4dc990 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:47:14 -0500 Subject: [PATCH 02/54] wip: add admin namespace support * add networking and peer related structs --- Cargo.lock | 20 ++++ ethers-providers/Cargo.toml | 3 + ethers-providers/src/admin.rs | 151 +++++++++++++++++++++++++++++++ ethers-providers/src/lib.rs | 31 +++++++ ethers-providers/src/provider.rs | 35 ++++++- 5 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 ethers-providers/src/admin.rs diff --git a/Cargo.lock b/Cargo.lock index 4b8b47ef0..0745f9e24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1185,6 +1185,25 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "enr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" +dependencies = [ + "base64 0.13.1", + "bs58", + "bytes", + "hex", + "k256", + "log", + "rand 0.8.5", + "rlp", + "serde", + "sha3", + "zeroize", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -1479,6 +1498,7 @@ dependencies = [ "auto_impl 1.0.1", "base64 0.13.1", "bytes", + "enr", "ethers-core", "futures-channel", "futures-core", diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml index 9e4829234..52ae45c0a 100644 --- a/ethers-providers/Cargo.toml +++ b/ethers-providers/Cargo.toml @@ -35,6 +35,9 @@ futures-timer = { version = "3.0.2", default-features = false } futures-channel = { version = "0.3.16", default-features = false, optional = true } pin-project = { version = "1.0.11", default-features = false } +# peer-related amin namespace +enr = { version = "0.7.0", default-features = false, features = ["k256", "serde"] } + # tracing tracing = { version = "0.1.37", default-features = false } tracing-futures = { version = "0.2.5", default-features = false, features = ["std-future"] } diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs new file mode 100644 index 000000000..872742a53 --- /dev/null +++ b/ethers-providers/src/admin.rs @@ -0,0 +1,151 @@ +use crate::{H256, U256}; +use enr::{k256::ecdsa::SigningKey, Enr}; +use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, SocketAddr}; + +/// This includes general information about a running node, spanning networking and protocol +/// details. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct NodeInfo { + /// TODO: docs - what kind of key is this? + pub id: String, + + /// The client user agent, containing a client name, version, OS, and other metadata. + pub name: String, + + /// The enode URL of the running client. + pub enode: String, + + /// The [ENR](https://eips.ethereum.org/EIPS/eip-778) of the running client. + pub enr: Enr, + + /// The IP address of the running client. + pub ip: IpAddr, + + /// The client's listening ports. + pub ports: Ports, + + /// The client's listening address. + pub listen_addr: String, + + /// The protocols that the client supports, with protocol metadata. + pub protocols: Vec, +} + +/// Represents a node's discovery and listener ports. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Ports { + /// The discovery port of the running client. + pub discovery: u16, + + /// The listener port of the running client. + pub listener: u16, +} + +/// Represents a protocol that the client supports. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum ProtocolInfo { + Eth(EthProtocolInfo), + Snap(SnapProtocolInfo), +} + +/// Represents a short summary of the `eth` sub-protocol metadata known about the host peer. +/// +/// See [geth's `NodeInfo` +/// struct](https://github.com/ethereum/go-ethereum/blob/c2e0abce2eedc1ba2a1b32c46fd07ef18a25354a/eth/protocols/eth/handler.go#L129) +/// for how these fields are determined. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EthProtocolInfo { + /// The ethereum network ID. + pub network: u64, + + /// The total difficulty of the host's blockchain. + pub difficulty: U256, + + /// The Keccak hash of the host's genesis block. + pub genesis: H256, + + /// The chain configuration for the host's fork rules. + pub config: ChainConfig, + + /// The hash of the host's best known block. + pub head: H256, +} + +/// Represents a short summary of the `snap` sub-protocol metadata known about the host peer. +/// +/// This is just an empty struct, because [geth's internal representation is +/// empty](https://github.com/ethereum/go-ethereum/blob/c2e0abce2eedc1ba2a1b32c46fd07ef18a25354a/eth/protocols/snap/handler.go#L571-L576). +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SnapProtocolInfo {} + +/// Represents a node's chain configuration. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ChainConfig { + /// The network's chain ID. + pub chain_id: u64, + + /// The homestead switch block (None = no fork, 0 = already homestead). + pub homestead_block: Option, + + /// The DAO fork switch block (None = no fork). + pub dao_fork_block: Option, + + /// Whether or not the node supports the DAO hard-fork. + pub dao_fork_support: bool, + + /// The EIP-150 hard fork block (None = no fork). + pub eip150_block: Option, + + /// The EIP-150 hard fork hash. + // TODO: confirm that this is the right type - should it be an Option? + pub eip150_hash: H256, + + /// The EIP-155 hard fork block. + pub eip155_block: Option, + // TODO: rest +} + +/// Represents a short summary of information known about a connected peer. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PeerInfo { + /// The peer's ENR. + pub enr: Option>, + + /// The peer's enode URL. + pub enode: String, + + /// The peer's network ID. + pub id: String, + + /// The peer's name. + pub name: String, + + /// The peer's capabilities. + pub caps: Vec, + + pub network: PeerNetworkInfo, + + /// The protocols that the peer supports, with protocol metadata. + pub protocols: Vec, +} + +/// Represents networking related information about the peer, including details about whether or +/// not it is inbound, trusted, or static. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PeerNetworkInfo { + /// The local endpoint of the TCP connection. + pub local_address: SocketAddr, + + /// The remote endpoint of the TCP connection. + pub remote_address: SocketAddr, + + /// Whether or not the peer is inbound. + pub inbound: bool, + + /// Whether or not the peer is trusted. + pub trusted: bool, + + /// Whether or not the peer is a static peer. + pub static_node: bool, +} diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs index 6905b14af..684e42cfa 100644 --- a/ethers-providers/src/lib.rs +++ b/ethers-providers/src/lib.rs @@ -10,6 +10,10 @@ pub use transports::*; mod provider; pub use provider::{is_local_endpoint, FilterKind, Provider, ProviderError, ProviderExt}; +// types for the admin api +pub mod admin; +pub use admin::{NodeInfo, PeerInfo}; + // ENS support pub mod ens; @@ -36,6 +40,7 @@ pub mod erc; use async_trait::async_trait; use auto_impl::auto_impl; +use enr::{k256::ecdsa::SigningKey, Enr}; use ethers_core::types::transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed}; use serde::{de::DeserializeOwned, Serialize}; use std::{error::Error, fmt::Debug, future::Future, pin::Pin}; @@ -488,6 +493,32 @@ pub trait Middleware: Sync + Send + Debug { self.inner().get_proof(from, locations, block).await.map_err(FromErr::from) } + // Admin namespace + + async fn add_peer(&self, node_record: Enr) -> Result { + self.inner().add_peer(node_record).await.map_err(FromErr::from) + } + + async fn add_trusted_peer(&self, node_record: Enr) -> Result { + self.inner().add_trusted_peer(node_record).await.map_err(FromErr::from) + } + + async fn node_info(&self) -> Result { + self.inner().node_info().await.map_err(FromErr::from) + } + + async fn peers(&self) -> Result, Self::Error> { + self.inner().peers().await.map_err(FromErr::from) + } + + async fn remove_peer(&self, node_record: Enr) -> Result { + self.inner().remove_peer(node_record).await.map_err(FromErr::from) + } + + async fn remove_trusted_peer(&self, node_record: Enr) -> Result { + self.inner().remove_trusted_peer(node_record).await.map_err(FromErr::from) + } + // Mempool inspection for Geth's API async fn txpool_content(&self) -> Result { diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index e38464da2..3f8c1397e 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -4,7 +4,7 @@ use crate::{ pubsub::{PubsubClient, SubscriptionStream}, stream::{FilterWatcher, DEFAULT_LOCAL_POLL_INTERVAL, DEFAULT_POLL_INTERVAL}, FromErr, Http as HttpProvider, JsonRpcClient, JsonRpcClientWrapper, LogQuery, MockProvider, - PendingTransaction, QuorumProvider, RwClient, SyncingStatus, + NodeInfo, PeerInfo, PendingTransaction, QuorumProvider, RwClient, SyncingStatus, }; #[cfg(all(not(target_arch = "wasm32"), feature = "ws"))] @@ -33,6 +33,7 @@ use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; use url::{ParseError, Url}; +use enr::{k256::ecdsa::SigningKey, Enr}; use ethers_core::types::Chain; use futures_util::{lock::Mutex, try_join}; use std::{ @@ -798,6 +799,38 @@ impl Middleware for Provider

{ self.request("eth_getProof", [from, locations, block]).await } + // Admin namespace + + /// docs: TODO + async fn add_peer(&self, node_record: Enr) -> Result { + todo!() + } + + /// docs: TODO + async fn add_trusted_peer(&self, node_record: Enr) -> Result { + todo!() + } + + /// docs: TODO + async fn node_info(&self) -> Result { + todo!() + } + + /// docs: TODO + async fn peers(&self) -> Result, Self::Error> { + todo!() + } + + /// docs: TODO + async fn remove_peer(&self, node_record: Enr) -> Result { + todo!() + } + + /// docs: TODO + async fn remove_trusted_peer(&self, node_record: Enr) -> Result { + todo!() + } + ////// Ethereum Naming Service // The Ethereum Naming Service (ENS) allows easy to remember and use names to // be assigned to Ethereum addresses. Any provider operation which takes an address From 0fce96ce8b41b66c92363c8d34ef62f95c35bdc3 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 17:50:36 -0500 Subject: [PATCH 03/54] add rest of chain config fields --- ethers-providers/src/admin.rs | 75 ++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index 872742a53..dbb1c54c2 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -45,8 +45,8 @@ pub struct Ports { /// Represents a protocol that the client supports. #[derive(Clone, Debug, Deserialize, Serialize)] pub enum ProtocolInfo { - Eth(EthProtocolInfo), - Snap(SnapProtocolInfo), + Eth(Box), + Snap(Box), } /// Represents a short summary of the `eth` sub-protocol metadata known about the host peer. @@ -98,12 +98,62 @@ pub struct ChainConfig { pub eip150_block: Option, /// The EIP-150 hard fork hash. - // TODO: confirm that this is the right type - should it be an Option? - pub eip150_hash: H256, + pub eip150_hash: Option, /// The EIP-155 hard fork block. pub eip155_block: Option, - // TODO: rest + + /// The EIP-158 hard fork block. + pub eip158_block: Option, + + /// The Byzantium hard fork block. + pub byzantium_block: Option, + + /// The Constantinople hard fork block. + pub constantinople_block: Option, + + /// The Petersburg hard fork block. + pub petersburg_block: Option, + + /// The Istanbul hard fork block. + pub istanbul_block: Option, + + /// The Muir Glacier hard fork block. + pub muir_glacier_block: Option, + + /// The Berlin hard fork block. + pub berlin_block: Option, + + /// The London hard fork block. + pub london_block: Option, + + /// The Arrow Glacier hard fork block. + pub arrow_glacier_block: Option, + + /// The Gray Glacier hard fork block. + pub gray_glacier_block: Option, + + /// Virtual fork after the merge to use as a network splitter. + pub merge_netsplit_block: Option, + + /// The Shanghai hard fork block. + pub shanghai_block: Option, + + /// The Cancun hard fork block. + pub cancun_block: Option, + + /// Total difficulty reached that triggers the merge consensus upgrade. + pub terminal_total_difficulty: Option, + + /// A flag specifying that the network already passed the terminal total difficulty. Its + /// purpose is to disable legacy sync without having seen the TTD locally. + pub terminal_total_difficulty_passed: bool, + + /// Ethash parameters. + pub ethash: Option, + + /// Clique parameters. + pub clique: Option, } /// Represents a short summary of information known about a connected peer. @@ -124,6 +174,7 @@ pub struct PeerInfo { /// The peer's capabilities. pub caps: Vec, + /// Networking information about the peer. pub network: PeerNetworkInfo, /// The protocols that the peer supports, with protocol metadata. @@ -149,3 +200,17 @@ pub struct PeerNetworkInfo { /// Whether or not the peer is a static peer. pub static_node: bool, } + +/// Empty consensus configuration for proof-of-work networks. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EthashConfig {} + +/// Consensus configuration for Clique. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CliqueConfig { + /// Number of seconds between blocks to enforce. + pub period: u64, + + /// Epoch length to reset votes and checkpoints. + pub epoch: u64, +} From 4adffd4c10c85c1f27ea537b851aa9a4de403b19 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 18:55:36 -0500 Subject: [PATCH 04/54] add datadir to geth --- ethers-core/src/utils/geth.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 9927438df..563f3d7de 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -22,6 +22,7 @@ pub struct GethInstance { pid: Child, port: u16, ipc: Option, + data_dir: Option, } impl GethInstance { @@ -40,9 +41,15 @@ impl GethInstance { format!("ws://localhost:{}", self.port) } + /// Returns the path to this instances' IPC socket pub fn ipc_path(&self) -> &Option { &self.ipc } + + /// Returns the path to this instances' data directory + pub fn data_dir(&self) -> &Option { + &self.data_dir + } } impl Drop for GethInstance { @@ -77,6 +84,7 @@ pub struct Geth { port: Option, block_time: Option, ipc_path: Option, + data_dir: Option, } impl Geth { @@ -107,6 +115,13 @@ impl Geth { self } + /// Sets the data directory for geth. + #[must_use] + pub fn data_dir>(mut self, path: T) -> Self { + self.data_dir = Some(path.into()); + self + } + /// Consumes the builder and spawns `geth` with stdout redirected /// to /dev/null. pub fn spawn(self) -> GethInstance { @@ -125,6 +140,10 @@ impl Geth { cmd.arg("--ws.port").arg(port.to_string()); cmd.arg("--ws.api").arg(API); + if let Some(data_dir) = self.data_dir { + cmd.arg("--datadir").arg(data_dir); + } + // Dev mode with custom block time cmd.arg("--dev"); if let Some(block_time) = self.block_time { @@ -158,6 +177,6 @@ impl Geth { child.stderr = Some(reader.into_inner()); - GethInstance { pid: child, port, ipc: self.ipc_path } + GethInstance { pid: child, port, ipc: self.ipc_path, data_dir: self.data_dir } } } From 0562f0bcca9c54011ce2a48a2a30afc3f6f5099b Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 19:23:55 -0500 Subject: [PATCH 05/54] fix data dir ref --- ethers-core/src/utils/geth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 563f3d7de..bf0a271ce 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -140,7 +140,7 @@ impl Geth { cmd.arg("--ws.port").arg(port.to_string()); cmd.arg("--ws.api").arg(API); - if let Some(data_dir) = self.data_dir { + if let Some(ref data_dir) = self.data_dir { cmd.arg("--datadir").arg(data_dir); } From 26c0d662d6a69cd1a6d22304cddb6ca0feec2e57 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 19:29:47 -0500 Subject: [PATCH 06/54] add dev flag to geth --- ethers-core/src/utils/geth.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index bf0a271ce..31819d4d5 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -85,6 +85,7 @@ pub struct Geth { block_time: Option, ipc_path: Option, data_dir: Option, + dev: bool, } impl Geth { @@ -105,6 +106,7 @@ impl Geth { #[must_use] pub fn block_time>(mut self, block_time: T) -> Self { self.block_time = Some(block_time.into()); + self.dev = true; self } @@ -115,6 +117,15 @@ impl Geth { self } + /// Sets whether or not the geth instance will be run in `dev` mode. + /// + /// This will automatically be `true` if `block_time` is set. + #[must_use] + pub fn dev(mut self, dev: bool) -> Self { + self.dev = dev; + self + } + /// Sets the data directory for geth. #[must_use] pub fn data_dir>(mut self, path: T) -> Self { @@ -145,9 +156,11 @@ impl Geth { } // Dev mode with custom block time - cmd.arg("--dev"); - if let Some(block_time) = self.block_time { - cmd.arg("--dev.period").arg(block_time.to_string()); + if self.dev { + cmd.arg("--dev"); + if let Some(block_time) = self.block_time { + cmd.arg("--dev.period").arg(block_time.to_string()); + } } if let Some(ref ipc) = self.ipc_path { From 2bedcd2110439ee8151739b67e1edfb01331c8c6 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 19:32:18 -0500 Subject: [PATCH 07/54] set dev only if block_time is not set --- ethers-core/src/utils/geth.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 31819d4d5..da2ca58f0 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -103,6 +103,8 @@ impl Geth { } /// Sets the block-time which will be used when the `geth-cli` instance is launched. + /// + /// This will set the `dev` flag to true. #[must_use] pub fn block_time>(mut self, block_time: T) -> Self { self.block_time = Some(block_time.into()); @@ -119,10 +121,12 @@ impl Geth { /// Sets whether or not the geth instance will be run in `dev` mode. /// - /// This will automatically be `true` if `block_time` is set. + /// This will not be set to `false` if `block_time` is already set. #[must_use] pub fn dev(mut self, dev: bool) -> Self { - self.dev = dev; + if self.block_time.is_none() { + self.dev = dev; + } self } From 6a237b31938bb9326018e245db89bbbc5bfa72ad Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 19:49:29 -0500 Subject: [PATCH 08/54] put mutually exclusive options in an enum --- ethers-core/src/utils/geth.rs | 47 ++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index da2ca58f0..218be302c 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -58,6 +58,35 @@ impl Drop for GethInstance { } } +/// Whether or not geth is in `dev` mode and configuration options that depend on the mode. +#[derive(Debug, Clone)] +pub enum GethMode { + /// Options that can be set in dev mode + Dev(DevOptions), + /// Options that cannot be set in dev mode + NonDev(PrivateNetOptions), +} + +impl Default for GethMode { + fn default() -> Self { + Self::Dev(Default::default()) + } +} + +/// Configuration options that can be set in dev mode. +#[derive(Debug, Clone, Default)] +pub struct DevOptions { + /// The interval at which the dev chain will mine new blocks. + pub block_time: Option, +} + +/// Configuration options that cannot be set in dev mode. +#[derive(Debug, Clone, Default)] +pub struct PrivateNetOptions { + /// The p2p port to use. + pub p2p_port: Option, +} + /// Builder for launching `geth`. /// /// # Panics @@ -85,7 +114,7 @@ pub struct Geth { block_time: Option, ipc_path: Option, data_dir: Option, - dev: bool, + mode: GethMode, } impl Geth { @@ -108,7 +137,6 @@ impl Geth { #[must_use] pub fn block_time>(mut self, block_time: T) -> Self { self.block_time = Some(block_time.into()); - self.dev = true; self } @@ -119,17 +147,6 @@ impl Geth { self } - /// Sets whether or not the geth instance will be run in `dev` mode. - /// - /// This will not be set to `false` if `block_time` is already set. - #[must_use] - pub fn dev(mut self, dev: bool) -> Self { - if self.block_time.is_none() { - self.dev = dev; - } - self - } - /// Sets the data directory for geth. #[must_use] pub fn data_dir>(mut self, path: T) -> Self { @@ -160,9 +177,9 @@ impl Geth { } // Dev mode with custom block time - if self.dev { + if let GethMode::Dev(options) = self.mode { cmd.arg("--dev"); - if let Some(block_time) = self.block_time { + if let Some(block_time) = options.block_time { cmd.arg("--dev.period").arg(block_time.to_string()); } } From d6b9687d5b1b3fe61a6cc57aea3392a8c74fe467 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 19:59:02 -0500 Subject: [PATCH 09/54] make block_time use the devmode enum --- ethers-core/src/utils/geth.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 218be302c..ed2101308 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -111,7 +111,6 @@ pub struct PrivateNetOptions { #[derive(Clone, Default)] pub struct Geth { port: Option, - block_time: Option, ipc_path: Option, data_dir: Option, mode: GethMode, @@ -133,10 +132,11 @@ impl Geth { /// Sets the block-time which will be used when the `geth-cli` instance is launched. /// - /// This will set the `dev` flag to true. + /// This will put the geth instance in `dev` mode, discarding any previously set options that + /// cannot be used in dev mode. #[must_use] pub fn block_time>(mut self, block_time: T) -> Self { - self.block_time = Some(block_time.into()); + self.mode = GethMode::Dev(DevOptions { block_time: Some(block_time.into()) }); self } From fce6c40a84e5c5ab0529f6e6c2c6ece7dad396d8 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:10:55 -0500 Subject: [PATCH 10/54] add p2p port to geth --- ethers-core/src/utils/geth.rs | 37 ++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index ed2101308..d6ece304f 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -23,6 +23,7 @@ pub struct GethInstance { port: u16, ipc: Option, data_dir: Option, + p2p_port: Option, } impl GethInstance { @@ -31,6 +32,11 @@ impl GethInstance { self.port } + /// Returns the p2p port of this instance + pub fn p2p_port(&self) -> Option { + self.p2p_port + } + /// Returns the HTTP endpoint of this instance pub fn endpoint(&self) -> String { format!("http://localhost:{}", self.port) @@ -130,6 +136,16 @@ impl Geth { self } + /// Sets the port which will be used for incoming p2p connections. + /// + /// This will put the geth instance into non-dev mode, discarding any previously set dev-mode + /// options. + #[must_use] + pub fn p2p_port>(mut self, port: T) -> Self { + self.mode = GethMode::NonDev(PrivateNetOptions { p2p_port: Some(port.into()) }); + self + } + /// Sets the block-time which will be used when the `geth-cli` instance is launched. /// /// This will put the geth instance in `dev` mode, discarding any previously set options that @@ -177,10 +193,17 @@ impl Geth { } // Dev mode with custom block time - if let GethMode::Dev(options) = self.mode { - cmd.arg("--dev"); - if let Some(block_time) = options.block_time { - cmd.arg("--dev.period").arg(block_time.to_string()); + match self.mode { + GethMode::Dev(DevOptions { block_time }) => { + cmd.arg("--dev"); + if let Some(block_time) = block_time { + cmd.arg("--dev.period").arg(block_time.to_string()); + } + } + GethMode::NonDev(PrivateNetOptions { p2p_port }) => { + if let Some(p2p_port) = p2p_port { + cmd.arg("--port").arg(p2p_port.to_string()); + } } } @@ -210,7 +233,11 @@ impl Geth { } child.stderr = Some(reader.into_inner()); + let p2p_port = match self.mode { + GethMode::Dev(_) => None, + GethMode::NonDev(PrivateNetOptions { p2p_port }) => p2p_port, + }; - GethInstance { pid: child, port, ipc: self.ipc_path, data_dir: self.data_dir } + GethInstance { pid: child, port, ipc: self.ipc_path, data_dir: self.data_dir, p2p_port } } } From 52a7a35d4cd831fc9f19b9a6d95efcee4591a61c Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:12:47 -0500 Subject: [PATCH 11/54] add basic impls for admin endpoints --- ethers-providers/src/provider.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 3f8c1397e..e569bde6b 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -803,32 +803,36 @@ impl Middleware for Provider

{ /// docs: TODO async fn add_peer(&self, node_record: Enr) -> Result { - todo!() + let node_record = utils::serialize(&node_record); + self.request("admin_addPeer", [node_record]).await } /// docs: TODO async fn add_trusted_peer(&self, node_record: Enr) -> Result { - todo!() + let node_record = utils::serialize(&node_record); + self.request("admin_addTrustedPeer", [node_record]).await } /// docs: TODO async fn node_info(&self) -> Result { - todo!() + self.request("admin_nodeInfo", ()).await } /// docs: TODO async fn peers(&self) -> Result, Self::Error> { - todo!() + self.request("admin_peers", ()).await } /// docs: TODO async fn remove_peer(&self, node_record: Enr) -> Result { - todo!() + let node_record = utils::serialize(&node_record); + self.request("admin_removePeer", [node_record]).await } /// docs: TODO async fn remove_trusted_peer(&self, node_record: Enr) -> Result { - todo!() + let node_record = utils::serialize(&node_record); + self.request("admin_removeTrustedPeer", [node_record]).await } ////// Ethereum Naming Service From e28d1a118575c1b5c3c1b99afdfb7e01c3cb17c8 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 21:51:08 -0500 Subject: [PATCH 12/54] move from_int_or_hex to ethers-core utils --- ethers-core/src/types/fee.rs | 22 ++-------------------- ethers-core/src/utils/mod.rs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/ethers-core/src/types/fee.rs b/ethers-core/src/types/fee.rs index f05df7e2c..c6274e666 100644 --- a/ethers-core/src/types/fee.rs +++ b/ethers-core/src/types/fee.rs @@ -1,7 +1,5 @@ -use std::str::FromStr; - -use crate::types::U256; -use serde::{de::Deserializer, Deserialize, Serialize}; +use crate::{types::U256, utils::from_int_or_hex}; +use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] @@ -19,19 +17,3 @@ pub struct FeeHistory { #[serde(default)] pub reward: Vec>, } - -fn from_int_or_hex<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - #[serde(untagged)] - enum IntOrHex { - Int(u64), - Hex(String), - } - match IntOrHex::deserialize(deserializer)? { - IntOrHex::Int(n) => Ok(U256::from(n)), - IntOrHex::Hex(s) => U256::from_str(s.as_str()).map_err(serde::de::Error::custom), - } -} diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index f3e7d7bc1..ebaeb555c 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -23,6 +23,7 @@ mod hash; pub use hash::{hash_message, id, keccak256, serialize}; mod units; +use serde::{Deserialize, Deserializer}; pub use units::Units; /// Re-export RLP @@ -37,6 +38,7 @@ use ethabi::ethereum_types::FromDecStrErr; use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey}; use std::{ convert::{TryFrom, TryInto}, + str::FromStr, fmt, }; use thiserror::Error; @@ -452,6 +454,23 @@ pub fn eip1559_default_estimator(base_fee_per_gas: U256, rewards: Vec> (max_fee_per_gas, max_priority_fee_per_gas) } +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings. +pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum IntOrHex { + Int(u64), + Hex(String), + } + match IntOrHex::deserialize(deserializer)? { + IntOrHex::Int(n) => Ok(U256::from(n)), + IntOrHex::Hex(s) => U256::from_str(s.as_str()).map_err(serde::de::Error::custom), + } +} + fn estimate_priority_fee(rewards: Vec>) -> U256 { let mut rewards: Vec = rewards.iter().map(|r| r[0]).filter(|r| *r > U256::zero()).collect(); From 4cfaffbb638cafbb9a23a639b1d0a592471c6334 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 22:05:22 -0500 Subject: [PATCH 13/54] fix nodeinfo protocol field * the type is better represented by a struct which can have either eth or snap --- ethers-providers/src/admin.rs | 38 +++++++++++++++++++++++++++----- ethers-providers/src/provider.rs | 17 +++++++++++++- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index dbb1c54c2..8853936c1 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -1,5 +1,6 @@ use crate::{H256, U256}; use enr::{k256::ecdsa::SigningKey, Enr}; +use ethers_core::utils::from_int_or_hex; use serde::{Deserialize, Serialize}; use std::net::{IpAddr, SocketAddr}; @@ -26,10 +27,11 @@ pub struct NodeInfo { pub ports: Ports, /// The client's listening address. + #[serde(rename = "listenAddr")] pub listen_addr: String, /// The protocols that the client supports, with protocol metadata. - pub protocols: Vec, + pub protocols: ProtocolInfo, } /// Represents a node's discovery and listener ports. @@ -42,11 +44,11 @@ pub struct Ports { pub listener: u16, } -/// Represents a protocol that the client supports. +/// Represents the protocols that the client supports. #[derive(Clone, Debug, Deserialize, Serialize)] -pub enum ProtocolInfo { - Eth(Box), - Snap(Box), +pub struct ProtocolInfo { + pub eth: Option, + pub snap: Option, } /// Represents a short summary of the `eth` sub-protocol metadata known about the host peer. @@ -60,6 +62,7 @@ pub struct EthProtocolInfo { pub network: u64, /// The total difficulty of the host's blockchain. + #[serde(deserialize_with = "from_int_or_hex")] pub difficulty: U256, /// The Keccak hash of the host's genesis block. @@ -80,73 +83,96 @@ pub struct EthProtocolInfo { pub struct SnapProtocolInfo {} /// Represents a node's chain configuration. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +#[serde(default)] pub struct ChainConfig { /// The network's chain ID. + #[serde(rename = "chainId")] pub chain_id: u64, /// The homestead switch block (None = no fork, 0 = already homestead). + #[serde(rename = "homesteadBlock")] pub homestead_block: Option, /// The DAO fork switch block (None = no fork). + #[serde(rename = "daoForkBlock")] pub dao_fork_block: Option, /// Whether or not the node supports the DAO hard-fork. + #[serde(rename = "daoForkSupport")] pub dao_fork_support: bool, /// The EIP-150 hard fork block (None = no fork). + #[serde(rename = "eip150Block")] pub eip150_block: Option, /// The EIP-150 hard fork hash. + #[serde(rename = "eip150Hash")] pub eip150_hash: Option, /// The EIP-155 hard fork block. + #[serde(rename = "eip155Block")] pub eip155_block: Option, /// The EIP-158 hard fork block. + #[serde(rename = "eip158Block")] pub eip158_block: Option, /// The Byzantium hard fork block. + #[serde(rename = "byzantiumBlock")] pub byzantium_block: Option, /// The Constantinople hard fork block. + #[serde(rename = "constantinopleBlock")] pub constantinople_block: Option, /// The Petersburg hard fork block. + #[serde(rename = "petersburgBlock")] pub petersburg_block: Option, /// The Istanbul hard fork block. + #[serde(rename = "istanbulBlock")] pub istanbul_block: Option, /// The Muir Glacier hard fork block. + #[serde(rename = "muirGlacierBlock")] pub muir_glacier_block: Option, /// The Berlin hard fork block. + #[serde(rename = "berlinBlock")] pub berlin_block: Option, /// The London hard fork block. + #[serde(rename = "londonBlock")] pub london_block: Option, /// The Arrow Glacier hard fork block. + #[serde(rename = "arrowGlacierBlock")] pub arrow_glacier_block: Option, /// The Gray Glacier hard fork block. + #[serde(rename = "grayGlacierBlock")] pub gray_glacier_block: Option, /// Virtual fork after the merge to use as a network splitter. + #[serde(rename = "mergeNetsplitBlock")] pub merge_netsplit_block: Option, /// The Shanghai hard fork block. + #[serde(rename = "shanghaiBlock")] pub shanghai_block: Option, /// The Cancun hard fork block. + #[serde(rename = "cancunBlock")] pub cancun_block: Option, /// Total difficulty reached that triggers the merge consensus upgrade. + #[serde(rename = "terminalTotalDifficulty")] pub terminal_total_difficulty: Option, /// A flag specifying that the network already passed the terminal total difficulty. Its /// purpose is to disable legacy sync without having seen the TTD locally. + #[serde(rename = "terminalTotalDifficultyPassed")] pub terminal_total_difficulty_passed: bool, /// Ethash parameters. diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index e569bde6b..41a26f383 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -1796,7 +1796,7 @@ mod tests { types::{ transaction::eip2930::AccessList, Eip1559TransactionRequest, TransactionRequest, H256, }, - utils::Anvil, + utils::{Anvil, Geth}, }; use futures_util::StreamExt; @@ -2161,4 +2161,19 @@ mod tests { "ens name not found: `ox63616e.eth` resolver (0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2) is invalid." ); } + + #[tokio::test] + async fn geth_admin_nodeinfo() { + // we can't use the test provider because infura does not expose admin endpoints + let port = 8545u16; + let url = format!("http://localhost:{}", port); + + let geth = Geth::new().port(port).spawn(); + let provider = Provider::try_from(url).unwrap(); + + let info = provider.node_info().await.unwrap(); + dbg!(&info); + + drop(geth); // this will kill the instance + } } From 1033b13eca93aa25f0b3c50789895bec73506979 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 22:44:18 -0500 Subject: [PATCH 14/54] add chain id and discovery toggle for Geth --- ethers-core/src/utils/geth.rs | 63 +++++++++++++++++++++++++++++--- ethers-providers/src/admin.rs | 30 +++++++++++++++ ethers-providers/src/provider.rs | 22 +++++++++-- 3 files changed, 107 insertions(+), 8 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index d6ece304f..2691d74dd 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -87,10 +87,22 @@ pub struct DevOptions { } /// Configuration options that cannot be set in dev mode. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct PrivateNetOptions { /// The p2p port to use. pub p2p_port: Option, + + /// Whether or not peer discovery is enabled. + pub discovery: bool, +} + +impl Default for PrivateNetOptions { + fn default() -> Self { + Self { + p2p_port: None, + discovery: true, + } + } } /// Builder for launching `geth`. @@ -119,6 +131,7 @@ pub struct Geth { port: Option, ipc_path: Option, data_dir: Option, + chain_id: Option, mode: GethMode, } @@ -141,8 +154,14 @@ impl Geth { /// This will put the geth instance into non-dev mode, discarding any previously set dev-mode /// options. #[must_use] - pub fn p2p_port>(mut self, port: T) -> Self { - self.mode = GethMode::NonDev(PrivateNetOptions { p2p_port: Some(port.into()) }); + pub fn p2p_port(mut self, port: u16) -> Self { + match self.mode { + GethMode::Dev(_) => self.mode = GethMode::NonDev(PrivateNetOptions { + p2p_port: Some(port), + ..Default::default() + }), + GethMode::NonDev(ref mut opts) => opts.p2p_port = Some(port), + } self } @@ -156,6 +175,29 @@ impl Geth { self } + /// Sets the chain id for the geth instance. + #[must_use] + pub fn chain_id>(mut self, chain_id: T) -> Self { + self.chain_id = Some(chain_id.into()); + self + } + + /// Disable discovery for the geth instance. + /// + /// This will put the geth instance into non-dev mode, discarding any previously set dev-mode + /// options. + #[must_use] + pub fn disable_discovery(mut self) -> Self { + match self.mode { + GethMode::Dev(_) => self.mode = GethMode::NonDev(PrivateNetOptions { + discovery: false, + ..Default::default() + }), + GethMode::NonDev(ref mut opts) => opts.discovery = false, + } + self + } + /// Manually sets the IPC path for the socket manually. #[must_use] pub fn ipc_path>(mut self, path: T) -> Self { @@ -200,13 +242,24 @@ impl Geth { cmd.arg("--dev.period").arg(block_time.to_string()); } } - GethMode::NonDev(PrivateNetOptions { p2p_port }) => { + GethMode::NonDev(PrivateNetOptions { p2p_port, .. }) => { if let Some(p2p_port) = p2p_port { cmd.arg("--port").arg(p2p_port.to_string()); } } } + if let Some(chain_id) = self.chain_id { + cmd.arg("--networkid").arg(chain_id.to_string()); + } + + // disable discovery if the flag is set + if let GethMode::NonDev(PrivateNetOptions { discovery, .. }) = self.mode { + if !discovery { + cmd.arg("--nodiscover"); + } + } + if let Some(ref ipc) = self.ipc_path { cmd.arg("--ipcpath").arg(ipc); } @@ -235,7 +288,7 @@ impl Geth { child.stderr = Some(reader.into_inner()); let p2p_port = match self.mode { GethMode::Dev(_) => None, - GethMode::NonDev(PrivateNetOptions { p2p_port }) => p2p_port, + GethMode::NonDev(PrivateNetOptions { p2p_port, .. }) => p2p_port, }; GethInstance { pid: child, port, ipc: self.ipc_path, data_dir: self.data_dir, p2p_port } diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index 8853936c1..a3e625726 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -240,3 +240,33 @@ pub struct CliqueConfig { /// Epoch length to reset votes and checkpoints. pub epoch: u64, } + +/// An event emitted by geth when a peer is added or removed, or when a message is sent or +/// received. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PeerEvent { + /// The type of event. + #[serde(rename = "type")] + pub event_type: String, + + /// The peer's enode ID. + pub peer: String, + + /// The error associated with the event. + pub error: Option, + + /// The protocol associated with the event. + pub protocol: Option, + + /// The message code. + pub msg_code: Option, + + /// The message size. + pub msg_size: Option, + + /// The local address of the peer. + pub local_address: Option, + + /// The remote address of the peer. + pub remote_address: Option, +} diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 41a26f383..ebf18eca4 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -2168,12 +2168,28 @@ mod tests { let port = 8545u16; let url = format!("http://localhost:{}", port); - let geth = Geth::new().port(port).spawn(); + let p2p_listener_port = 13337u16; + let network = 1337u64; + // we disable discovery because otherwise it will peer with nodes with chainid 1337 and + // try to sync + let geth = Geth::new() + .port(port) + .p2p_port(p2p_listener_port) + .chain_id(network) + .disable_discovery() + .spawn(); let provider = Provider::try_from(url).unwrap(); let info = provider.node_info().await.unwrap(); - dbg!(&info); + drop(geth); - drop(geth); // this will kill the instance + // check that the port we set works + assert_eq!(info.ports.listener, p2p_listener_port); + + // make sure it is running eth + assert!(info.protocols.eth.is_some()); + + // check that the network id is correct + assert_eq!(info.protocols.eth.unwrap().network, network); } } From 5e3eb65f6ce160ea35284a0ed8a005c70257bf9e Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 22:45:28 -0500 Subject: [PATCH 15/54] remove PeerEvent * should re-add when peer event endpoints are implemented --- ethers-providers/src/admin.rs | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index a3e625726..8853936c1 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -240,33 +240,3 @@ pub struct CliqueConfig { /// Epoch length to reset votes and checkpoints. pub epoch: u64, } - -/// An event emitted by geth when a peer is added or removed, or when a message is sent or -/// received. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct PeerEvent { - /// The type of event. - #[serde(rename = "type")] - pub event_type: String, - - /// The peer's enode ID. - pub peer: String, - - /// The error associated with the event. - pub error: Option, - - /// The protocol associated with the event. - pub protocol: Option, - - /// The message code. - pub msg_code: Option, - - /// The message size. - pub msg_size: Option, - - /// The local address of the peer. - pub local_address: Option, - - /// The remote address of the peer. - pub remote_address: Option, -} From 2bb8b320dc9a74b2fd4088d51c74af9e98c63e7c Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 23:37:00 -0500 Subject: [PATCH 16/54] simplify serde options for admin responses --- ethers-providers/src/admin.rs | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index 8853936c1..515561536 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -8,8 +8,8 @@ use std::net::{IpAddr, SocketAddr}; /// details. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NodeInfo { - /// TODO: docs - what kind of key is this? - pub id: String, + /// The node's secp256k1 public key. + pub id: H256, /// The client user agent, containing a client name, version, OS, and other metadata. pub name: String, @@ -84,95 +84,73 @@ pub struct SnapProtocolInfo {} /// Represents a node's chain configuration. #[derive(Clone, Debug, Deserialize, Serialize, Default)] -#[serde(default)] +#[serde(default, rename_all = "camelCase")] pub struct ChainConfig { /// The network's chain ID. - #[serde(rename = "chainId")] pub chain_id: u64, /// The homestead switch block (None = no fork, 0 = already homestead). - #[serde(rename = "homesteadBlock")] pub homestead_block: Option, /// The DAO fork switch block (None = no fork). - #[serde(rename = "daoForkBlock")] pub dao_fork_block: Option, /// Whether or not the node supports the DAO hard-fork. - #[serde(rename = "daoForkSupport")] pub dao_fork_support: bool, /// The EIP-150 hard fork block (None = no fork). - #[serde(rename = "eip150Block")] pub eip150_block: Option, /// The EIP-150 hard fork hash. - #[serde(rename = "eip150Hash")] pub eip150_hash: Option, /// The EIP-155 hard fork block. - #[serde(rename = "eip155Block")] pub eip155_block: Option, /// The EIP-158 hard fork block. - #[serde(rename = "eip158Block")] pub eip158_block: Option, /// The Byzantium hard fork block. - #[serde(rename = "byzantiumBlock")] pub byzantium_block: Option, /// The Constantinople hard fork block. - #[serde(rename = "constantinopleBlock")] pub constantinople_block: Option, /// The Petersburg hard fork block. - #[serde(rename = "petersburgBlock")] pub petersburg_block: Option, /// The Istanbul hard fork block. - #[serde(rename = "istanbulBlock")] pub istanbul_block: Option, /// The Muir Glacier hard fork block. - #[serde(rename = "muirGlacierBlock")] pub muir_glacier_block: Option, /// The Berlin hard fork block. - #[serde(rename = "berlinBlock")] pub berlin_block: Option, /// The London hard fork block. - #[serde(rename = "londonBlock")] pub london_block: Option, /// The Arrow Glacier hard fork block. - #[serde(rename = "arrowGlacierBlock")] pub arrow_glacier_block: Option, /// The Gray Glacier hard fork block. - #[serde(rename = "grayGlacierBlock")] pub gray_glacier_block: Option, /// Virtual fork after the merge to use as a network splitter. - #[serde(rename = "mergeNetsplitBlock")] pub merge_netsplit_block: Option, /// The Shanghai hard fork block. - #[serde(rename = "shanghaiBlock")] pub shanghai_block: Option, /// The Cancun hard fork block. - #[serde(rename = "cancunBlock")] pub cancun_block: Option, /// Total difficulty reached that triggers the merge consensus upgrade. - #[serde(rename = "terminalTotalDifficulty")] pub terminal_total_difficulty: Option, /// A flag specifying that the network already passed the terminal total difficulty. Its /// purpose is to disable legacy sync without having seen the TTD locally. - #[serde(rename = "terminalTotalDifficultyPassed")] pub terminal_total_difficulty_passed: bool, /// Ethash parameters. @@ -186,6 +164,7 @@ pub struct ChainConfig { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct PeerInfo { /// The peer's ENR. + #[serde(default)] pub enr: Option>, /// The peer's enode URL. @@ -210,6 +189,7 @@ pub struct PeerInfo { /// Represents networking related information about the peer, including details about whether or /// not it is inbound, trusted, or static. #[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] pub struct PeerNetworkInfo { /// The local endpoint of the TCP connection. pub local_address: SocketAddr, From 3dcacb441a4f664e0afb4dacb5a1df64665773f3 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 23:39:13 -0500 Subject: [PATCH 17/54] change signature for peer modification apis * these admin apis accept an enode, which _may_ be an enr, but could also be a legacy enode "v4" url. --- ethers-providers/src/lib.rs | 17 ++++++------- ethers-providers/src/provider.rs | 43 ++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs index 684e42cfa..9c7c22a12 100644 --- a/ethers-providers/src/lib.rs +++ b/ethers-providers/src/lib.rs @@ -40,7 +40,6 @@ pub mod erc; use async_trait::async_trait; use auto_impl::auto_impl; -use enr::{k256::ecdsa::SigningKey, Enr}; use ethers_core::types::transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed}; use serde::{de::DeserializeOwned, Serialize}; use std::{error::Error, fmt::Debug, future::Future, pin::Pin}; @@ -495,12 +494,12 @@ pub trait Middleware: Sync + Send + Debug { // Admin namespace - async fn add_peer(&self, node_record: Enr) -> Result { - self.inner().add_peer(node_record).await.map_err(FromErr::from) + async fn add_peer(&self, enode_url: String) -> Result { + self.inner().add_peer(enode_url).await.map_err(FromErr::from) } - async fn add_trusted_peer(&self, node_record: Enr) -> Result { - self.inner().add_trusted_peer(node_record).await.map_err(FromErr::from) + async fn add_trusted_peer(&self, enode_url: String) -> Result { + self.inner().add_trusted_peer(enode_url).await.map_err(FromErr::from) } async fn node_info(&self) -> Result { @@ -511,12 +510,12 @@ pub trait Middleware: Sync + Send + Debug { self.inner().peers().await.map_err(FromErr::from) } - async fn remove_peer(&self, node_record: Enr) -> Result { - self.inner().remove_peer(node_record).await.map_err(FromErr::from) + async fn remove_peer(&self, enode_url: String) -> Result { + self.inner().remove_peer(enode_url).await.map_err(FromErr::from) } - async fn remove_trusted_peer(&self, node_record: Enr) -> Result { - self.inner().remove_trusted_peer(node_record).await.map_err(FromErr::from) + async fn remove_trusted_peer(&self, enode_url: String) -> Result { + self.inner().remove_trusted_peer(enode_url).await.map_err(FromErr::from) } // Mempool inspection for Geth's API diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index ebf18eca4..244a773ef 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -33,7 +33,6 @@ use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; use url::{ParseError, Url}; -use enr::{k256::ecdsa::SigningKey, Enr}; use ethers_core::types::Chain; use futures_util::{lock::Mutex, try_join}; use std::{ @@ -801,38 +800,44 @@ impl Middleware for Provider

{ // Admin namespace - /// docs: TODO - async fn add_peer(&self, node_record: Enr) -> Result { - let node_record = utils::serialize(&node_record); - self.request("admin_addPeer", [node_record]).await + /// Requests adding the given peer, returning a boolean representing whether or not the peer + /// was accepted for tracking. + async fn add_peer(&self, enode_url: String) -> Result { + let enode_url = utils::serialize(&enode_url); + self.request("admin_addPeer", [enode_url]).await } - /// docs: TODO - async fn add_trusted_peer(&self, node_record: Enr) -> Result { - let node_record = utils::serialize(&node_record); - self.request("admin_addTrustedPeer", [node_record]).await + /// Requests adding the given peer as a trusted peer, which the node will always connect to + /// even when its peer slots are full. + async fn add_trusted_peer(&self, enode_url: String) -> Result { + let enode_url = utils::serialize(&enode_url); + self.request("admin_addTrustedPeer", [enode_url]).await } - /// docs: TODO + /// Returns general information about the node as well as information about the running p2p + /// protocols (e.g. `eth`, `snap`). async fn node_info(&self) -> Result { self.request("admin_nodeInfo", ()).await } - /// docs: TODO + /// Returns the list of peers currently connected to the node. async fn peers(&self) -> Result, Self::Error> { self.request("admin_peers", ()).await } - /// docs: TODO - async fn remove_peer(&self, node_record: Enr) -> Result { - let node_record = utils::serialize(&node_record); - self.request("admin_removePeer", [node_record]).await + /// Requests to remove the given peer, returning true if the enode was successfully parsed and + /// the peer was removed. + async fn remove_peer(&self, enode_url: String) -> Result { + let enode_url = utils::serialize(&enode_url); + self.request("admin_removePeer", [enode_url]).await } - /// docs: TODO - async fn remove_trusted_peer(&self, node_record: Enr) -> Result { - let node_record = utils::serialize(&node_record); - self.request("admin_removeTrustedPeer", [node_record]).await + /// Requests to remove the given peer, returning a boolean representing whether or not the + /// enode url passed was validated. A return value of `true` does not necessarily mean that the + /// peer was disconnected. + async fn remove_trusted_peer(&self, enode_url: String) -> Result { + let enode_url = utils::serialize(&enode_url); + self.request("admin_removeTrustedPeer", [enode_url]).await } ////// Ethereum Naming Service From 280a3d3cf9b6fc94369cbbeb05c2fc5eb08455e4 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 23:48:41 -0500 Subject: [PATCH 18/54] add note on where `ChainConfig` fields come from --- ethers-providers/src/admin.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index 515561536..e408848fb 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -83,6 +83,10 @@ pub struct EthProtocolInfo { pub struct SnapProtocolInfo {} /// Represents a node's chain configuration. +/// +/// See [geth's `ChainConfig` +/// struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/params/config.go#L349) +/// for the source of each field. #[derive(Clone, Debug, Deserialize, Serialize, Default)] #[serde(default, rename_all = "camelCase")] pub struct ChainConfig { From 0c1cf6d00d4d7bba8c0cecef682e52fc74b24c96 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 21 Nov 2022 23:55:48 -0500 Subject: [PATCH 19/54] add note on PeerInfo about the source of fields --- ethers-providers/src/admin.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index e408848fb..1d22aa53c 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -165,6 +165,8 @@ pub struct ChainConfig { } /// Represents a short summary of information known about a connected peer. +/// +/// See [geth's `PeerInfo` struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/p2p/peer.go#L484) for the source of each field. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct PeerInfo { /// The peer's ENR. From 23fa94113358ec98a5562c6c77ef05447adb4157 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 22 Nov 2022 00:01:34 -0500 Subject: [PATCH 20/54] add admin namespace support to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c226545..177adb29c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -221,6 +221,8 @@ ### Unreleased +- Add a subset of the `admin` namespace + [????](https://github.com/gakonst/ethers-rs/pull/????) - Return String for net version [1376](https://github.com/gakonst/ethers-rs/pull/1376) - Stream of paginated logs that load logs in small pages From 09c45095f3354a68770b7d63f6f425b8227f4846 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 22 Nov 2022 00:13:46 -0500 Subject: [PATCH 21/54] update pr number in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 177adb29c..a3c817c3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -222,7 +222,7 @@ ### Unreleased - Add a subset of the `admin` namespace - [????](https://github.com/gakonst/ethers-rs/pull/????) + [1880](https://github.com/gakonst/ethers-rs/pull/1880) - Return String for net version [1376](https://github.com/gakonst/ethers-rs/pull/1376) - Stream of paginated logs that load logs in small pages From f9a324aaf0a2a5e6a79230bb178319e206cd1082 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 22 Nov 2022 00:20:48 -0500 Subject: [PATCH 22/54] cargo fmt --- ethers-core/src/utils/geth.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 2691d74dd..b8e1a0107 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -98,10 +98,7 @@ pub struct PrivateNetOptions { impl Default for PrivateNetOptions { fn default() -> Self { - Self { - p2p_port: None, - discovery: true, - } + Self { p2p_port: None, discovery: true } } } @@ -156,10 +153,12 @@ impl Geth { #[must_use] pub fn p2p_port(mut self, port: u16) -> Self { match self.mode { - GethMode::Dev(_) => self.mode = GethMode::NonDev(PrivateNetOptions { - p2p_port: Some(port), - ..Default::default() - }), + GethMode::Dev(_) => { + self.mode = GethMode::NonDev(PrivateNetOptions { + p2p_port: Some(port), + ..Default::default() + }) + } GethMode::NonDev(ref mut opts) => opts.p2p_port = Some(port), } self @@ -189,10 +188,10 @@ impl Geth { #[must_use] pub fn disable_discovery(mut self) -> Self { match self.mode { - GethMode::Dev(_) => self.mode = GethMode::NonDev(PrivateNetOptions { - discovery: false, - ..Default::default() - }), + GethMode::Dev(_) => { + self.mode = + GethMode::NonDev(PrivateNetOptions { discovery: false, ..Default::default() }) + } GethMode::NonDev(ref mut opts) => opts.discovery = false, } self From 285f0371a18d11644647d3a95cd4598758555a66 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:24:54 -0500 Subject: [PATCH 23/54] move chainconfig to genesis in utils --- ethers-core/src/utils/genesis.rs | 103 +++++++++++++++++++++++++++++++ ethers-providers/src/admin.rs | 98 +---------------------------- 2 files changed, 104 insertions(+), 97 deletions(-) create mode 100644 ethers-core/src/utils/genesis.rs diff --git a/ethers-core/src/utils/genesis.rs b/ethers-core/src/utils/genesis.rs new file mode 100644 index 000000000..65f724cf5 --- /dev/null +++ b/ethers-core/src/utils/genesis.rs @@ -0,0 +1,103 @@ +use crate::types::{H256, U256}; +use serde::{Deserialize, Serialize}; + +/// This represents the chain configuration, specifying the genesis block, header fields, and hard +/// fork switch blocks. +pub struct Genesis {} + +/// Represents a node's chain configuration. +/// +/// See [geth's `ChainConfig` +/// struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/params/config.go#L349) +/// for the source of each field. +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +#[serde(default, rename_all = "camelCase")] +pub struct ChainConfig { + /// The network's chain ID. + pub chain_id: u64, + + /// The homestead switch block (None = no fork, 0 = already homestead). + pub homestead_block: Option, + + /// The DAO fork switch block (None = no fork). + pub dao_fork_block: Option, + + /// Whether or not the node supports the DAO hard-fork. + pub dao_fork_support: bool, + + /// The EIP-150 hard fork block (None = no fork). + pub eip150_block: Option, + + /// The EIP-150 hard fork hash. + pub eip150_hash: Option, + + /// The EIP-155 hard fork block. + pub eip155_block: Option, + + /// The EIP-158 hard fork block. + pub eip158_block: Option, + + /// The Byzantium hard fork block. + pub byzantium_block: Option, + + /// The Constantinople hard fork block. + pub constantinople_block: Option, + + /// The Petersburg hard fork block. + pub petersburg_block: Option, + + /// The Istanbul hard fork block. + pub istanbul_block: Option, + + /// The Muir Glacier hard fork block. + pub muir_glacier_block: Option, + + /// The Berlin hard fork block. + pub berlin_block: Option, + + /// The London hard fork block. + pub london_block: Option, + + /// The Arrow Glacier hard fork block. + pub arrow_glacier_block: Option, + + /// The Gray Glacier hard fork block. + pub gray_glacier_block: Option, + + /// Virtual fork after the merge to use as a network splitter. + pub merge_netsplit_block: Option, + + /// The Shanghai hard fork block. + pub shanghai_block: Option, + + /// The Cancun hard fork block. + pub cancun_block: Option, + + /// Total difficulty reached that triggers the merge consensus upgrade. + pub terminal_total_difficulty: Option, + + /// A flag specifying that the network already passed the terminal total difficulty. Its + /// purpose is to disable legacy sync without having seen the TTD locally. + pub terminal_total_difficulty_passed: bool, + + /// Ethash parameters. + pub ethash: Option, + + /// Clique parameters. + pub clique: Option, +} + +/// Empty consensus configuration for proof-of-work networks. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EthashConfig {} + +/// Consensus configuration for Clique. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CliqueConfig { + /// Number of seconds between blocks to enforce. + pub period: u64, + + /// Epoch length to reset votes and checkpoints. + pub epoch: u64, +} + diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index 1d22aa53c..fdaec88d0 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -1,6 +1,6 @@ use crate::{H256, U256}; use enr::{k256::ecdsa::SigningKey, Enr}; -use ethers_core::utils::from_int_or_hex; +use ethers_core::utils::{from_int_or_hex, ChainConfig}; use serde::{Deserialize, Serialize}; use std::net::{IpAddr, SocketAddr}; @@ -82,88 +82,6 @@ pub struct EthProtocolInfo { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct SnapProtocolInfo {} -/// Represents a node's chain configuration. -/// -/// See [geth's `ChainConfig` -/// struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/params/config.go#L349) -/// for the source of each field. -#[derive(Clone, Debug, Deserialize, Serialize, Default)] -#[serde(default, rename_all = "camelCase")] -pub struct ChainConfig { - /// The network's chain ID. - pub chain_id: u64, - - /// The homestead switch block (None = no fork, 0 = already homestead). - pub homestead_block: Option, - - /// The DAO fork switch block (None = no fork). - pub dao_fork_block: Option, - - /// Whether or not the node supports the DAO hard-fork. - pub dao_fork_support: bool, - - /// The EIP-150 hard fork block (None = no fork). - pub eip150_block: Option, - - /// The EIP-150 hard fork hash. - pub eip150_hash: Option, - - /// The EIP-155 hard fork block. - pub eip155_block: Option, - - /// The EIP-158 hard fork block. - pub eip158_block: Option, - - /// The Byzantium hard fork block. - pub byzantium_block: Option, - - /// The Constantinople hard fork block. - pub constantinople_block: Option, - - /// The Petersburg hard fork block. - pub petersburg_block: Option, - - /// The Istanbul hard fork block. - pub istanbul_block: Option, - - /// The Muir Glacier hard fork block. - pub muir_glacier_block: Option, - - /// The Berlin hard fork block. - pub berlin_block: Option, - - /// The London hard fork block. - pub london_block: Option, - - /// The Arrow Glacier hard fork block. - pub arrow_glacier_block: Option, - - /// The Gray Glacier hard fork block. - pub gray_glacier_block: Option, - - /// Virtual fork after the merge to use as a network splitter. - pub merge_netsplit_block: Option, - - /// The Shanghai hard fork block. - pub shanghai_block: Option, - - /// The Cancun hard fork block. - pub cancun_block: Option, - - /// Total difficulty reached that triggers the merge consensus upgrade. - pub terminal_total_difficulty: Option, - - /// A flag specifying that the network already passed the terminal total difficulty. Its - /// purpose is to disable legacy sync without having seen the TTD locally. - pub terminal_total_difficulty_passed: bool, - - /// Ethash parameters. - pub ethash: Option, - - /// Clique parameters. - pub clique: Option, -} - /// Represents a short summary of information known about a connected peer. /// /// See [geth's `PeerInfo` struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/p2p/peer.go#L484) for the source of each field. @@ -212,17 +130,3 @@ pub struct PeerNetworkInfo { /// Whether or not the peer is a static peer. pub static_node: bool, } - -/// Empty consensus configuration for proof-of-work networks. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct EthashConfig {} - -/// Consensus configuration for Clique. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct CliqueConfig { - /// Number of seconds between blocks to enforce. - pub period: u64, - - /// Epoch length to reset votes and checkpoints. - pub epoch: u64, -} From 20446a35fa775a8ab858f3277066862e13376067 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:44:49 -0500 Subject: [PATCH 24/54] accept genesis file in geth --- ethers-core/src/utils/genesis.rs | 48 ++++++++++++++++++++++++++++++-- ethers-core/src/utils/geth.rs | 40 ++++++++++++++++++++++---- ethers-core/src/utils/mod.rs | 3 ++ 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/ethers-core/src/utils/genesis.rs b/ethers-core/src/utils/genesis.rs index 65f724cf5..384dd562f 100644 --- a/ethers-core/src/utils/genesis.rs +++ b/ethers-core/src/utils/genesis.rs @@ -1,9 +1,52 @@ -use crate::types::{H256, U256}; +use std::collections::HashMap; + +use crate::types::{Address, Bytes, H256, U256}; use serde::{Deserialize, Serialize}; /// This represents the chain configuration, specifying the genesis block, header fields, and hard /// fork switch blocks. -pub struct Genesis {} +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Genesis { + /// The fork configuration for this network. + pub config: ChainConfig, + + /// The genesis header nonce. + pub nonce: u64, + + /// The genesis header timestamp. + pub timestamp: u64, + + /// The genesis header extra data. + pub extra_data: Bytes, + + /// The genesis header gas limit. + 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 initial state of the genesis block. + pub alloc: HashMap, +} + +/// An account in the state of the genesis block. +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct GenesisAccount { + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, + pub balance: U256, + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub storage: Option>, +} /// Represents a node's chain configuration. /// @@ -100,4 +143,3 @@ pub struct CliqueConfig { /// Epoch length to reset votes and checkpoints. pub epoch: u64, } - diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index b8e1a0107..e6056c005 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -1,8 +1,9 @@ -use super::unused_port; +use super::{unused_port, Genesis}; use std::{ - io::{BufRead, BufReader}, + fs::File, + io::{BufRead, BufReader, Read}, path::PathBuf, - process::{Child, Command}, + process::{Child, ChildStderr, Command}, time::{Duration, Instant}, }; @@ -56,6 +57,11 @@ impl GethInstance { pub fn data_dir(&self) -> &Option { &self.data_dir } + + /// Return a `BufReader` for the stderr output of this instance + pub fn stderr(&mut self) -> BufReader { + BufReader::new(self.pid.stderr.take().unwrap()) + } } impl Drop for GethInstance { @@ -129,6 +135,7 @@ pub struct Geth { ipc_path: Option, data_dir: Option, chain_id: Option, + genesis: Option, mode: GethMode, } @@ -211,6 +218,18 @@ impl Geth { self } + /// Sets the `genesis.json` for the geth instance. + /// + /// If this is set, geth will be initialized with `geth init` and the `--datadir` option will be + /// set to the same value as `data_dir`. + /// + /// This is destructive and will overwrite any existing data in the data directory. + #[must_use] + pub fn genesis(mut self, genesis: Genesis) -> Self { + self.genesis = Some(genesis); + self + } + /// Consumes the builder and spawns `geth` with stdout redirected /// to /dev/null. pub fn spawn(self) -> GethInstance { @@ -279,12 +298,23 @@ impl Geth { reader.read_line(&mut line).expect("Failed to read line from geth process"); // geth 1.9.23 uses "server started" while 1.9.18 uses "endpoint opened" - if line.contains("HTTP endpoint opened") || line.contains("HTTP server started") { - break + match self.mode { + GethMode::Dev(_) => { + if line.contains("HTTP endpoint opened") || line.contains("HTTP server started") + { + break + } + } + GethMode::NonDev(_) => { + if line.contains("Started P2P networking") { + break + } + } } } child.stderr = Some(reader.into_inner()); + let p2p_port = match self.mode { GethMode::Dev(_) => None, GethMode::NonDev(PrivateNetOptions { p2p_port, .. }) => p2p_port, diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index ebaeb555c..65fb14a60 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -9,6 +9,9 @@ pub use ganache::{Ganache, GanacheInstance}; mod geth; #[cfg(not(target_arch = "wasm32"))] pub use geth::{Geth, GethInstance}; +#[cfg(not(target_arch = "wasm32"))] +mod genesis; +pub use genesis::{ChainConfig, Genesis}; /// Utilities for launching an anvil instance #[cfg(not(target_arch = "wasm32"))] From 0052e7243cc5ac9bad068f9ed3849741abefd241 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:59:16 -0500 Subject: [PATCH 25/54] add genesis writing to geth spawn --- ethers-core/src/utils/geth.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index e6056c005..09c6922f3 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -1,5 +1,6 @@ use super::{unused_port, Genesis}; use std::{ + env::temp_dir, fs::File, io::{BufRead, BufReader, Read}, path::PathBuf, @@ -248,6 +249,31 @@ impl Geth { cmd.arg("--ws.port").arg(port.to_string()); cmd.arg("--ws.api").arg(API); + // use geth init to initialize the datadir if the genesis exists + if let Some(genesis) = self.genesis { + // create a temp dir to store the genesis file + let temp_genesis_path = temp_dir().join("genesis.json"); + + // create the genesis file + let mut file = File::create(&temp_genesis_path).expect("could not create genesis file"); + + // serialize genesis and write to file + serde_json::to_writer_pretty(&mut file, &genesis) + .expect("could not write genesis to file"); + + let mut init_cmd = Command::new(GETH); + if let Some(ref data_dir) = self.data_dir { + init_cmd.arg("--datadir").arg(data_dir); + } + + init_cmd.arg("init").arg(temp_genesis_path); + init_cmd + .spawn() + .expect("failed to spawn geth init") + .wait() + .expect("failed to wait for geth init"); + } + if let Some(ref data_dir) = self.data_dir { cmd.arg("--datadir").arg(data_dir); } From 343fd89862af80871c68482d73296cfacf93802e Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 22 Nov 2022 23:09:06 -0500 Subject: [PATCH 26/54] finally get geth nodes to connect --- ethers-core/src/utils/genesis.rs | 29 ++++++- ethers-core/src/utils/geth.rs | 27 +++---- ethers-providers/src/admin.rs | 3 +- ethers-providers/src/provider.rs | 127 ++++++++++++++++++++++++++++--- 4 files changed, 156 insertions(+), 30 deletions(-) diff --git a/ethers-core/src/utils/genesis.rs b/ethers-core/src/utils/genesis.rs index 384dd562f..b65a81951 100644 --- a/ethers-core/src/utils/genesis.rs +++ b/ethers-core/src/utils/genesis.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::types::{Address, Bytes, H256, U256}; +use crate::types::{Address, Bytes, H256, U256, U64}; use serde::{Deserialize, Serialize}; /// This represents the chain configuration, specifying the genesis block, header fields, and hard @@ -12,16 +12,16 @@ pub struct Genesis { pub config: ChainConfig, /// The genesis header nonce. - pub nonce: u64, + pub nonce: U64, /// The genesis header timestamp. - pub timestamp: u64, + pub timestamp: U64, /// The genesis header extra data. pub extra_data: Bytes, /// The genesis header gas limit. - pub gas_limit: u64, + pub gas_limit: U64, /// The genesis header difficulty. pub difficulty: U256, @@ -60,63 +60,82 @@ pub struct ChainConfig { pub chain_id: u64, /// The homestead switch block (None = no fork, 0 = already homestead). + #[serde(skip_serializing_if = "Option::is_none")] pub homestead_block: Option, /// The DAO fork switch block (None = no fork). + #[serde(skip_serializing_if = "Option::is_none")] pub dao_fork_block: Option, /// Whether or not the node supports the DAO hard-fork. pub dao_fork_support: bool, /// The EIP-150 hard fork block (None = no fork). + #[serde(skip_serializing_if = "Option::is_none")] pub eip150_block: Option, /// The EIP-150 hard fork hash. + #[serde(skip_serializing_if = "Option::is_none")] pub eip150_hash: Option, /// The EIP-155 hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub eip155_block: Option, /// The EIP-158 hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub eip158_block: Option, /// The Byzantium hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub byzantium_block: Option, /// The Constantinople hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub constantinople_block: Option, /// The Petersburg hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub petersburg_block: Option, /// The Istanbul hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub istanbul_block: Option, /// The Muir Glacier hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub muir_glacier_block: Option, /// The Berlin hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub berlin_block: Option, /// The London hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub london_block: Option, /// The Arrow Glacier hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub arrow_glacier_block: Option, /// The Gray Glacier hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub gray_glacier_block: Option, /// Virtual fork after the merge to use as a network splitter. + #[serde(skip_serializing_if = "Option::is_none")] pub merge_netsplit_block: Option, /// The Shanghai hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub shanghai_block: Option, /// The Cancun hard fork block. + #[serde(skip_serializing_if = "Option::is_none")] pub cancun_block: Option, /// Total difficulty reached that triggers the merge consensus upgrade. + #[serde(skip_serializing_if = "Option::is_none")] pub terminal_total_difficulty: Option, /// A flag specifying that the network already passed the terminal total difficulty. Its @@ -124,9 +143,11 @@ pub struct ChainConfig { pub terminal_total_difficulty_passed: bool, /// Ethash parameters. + #[serde(skip_serializing_if = "Option::is_none")] pub ethash: Option, /// Clique parameters. + #[serde(skip_serializing_if = "Option::is_none")] pub clique: Option, } diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 09c6922f3..7c719134b 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -2,7 +2,7 @@ use super::{unused_port, Genesis}; use std::{ env::temp_dir, fs::File, - io::{BufRead, BufReader, Read}, + io::{BufRead, BufReader}, path::PathBuf, process::{Child, ChildStderr, Command}, time::{Duration, Instant}, @@ -304,6 +304,9 @@ impl Geth { } } + // verbosity 5 + cmd.arg("--verbosity").arg("5"); + if let Some(ref ipc) = self.ipc_path { cmd.arg("--ipcpath").arg(ipc); } @@ -315,6 +318,8 @@ impl Geth { let start = Instant::now(); let mut reader = BufReader::new(stdout); + let mut p2p_started = matches!(self.mode, GethMode::Dev(_)); + loop { if start + Duration::from_millis(GETH_STARTUP_TIMEOUT_MILLIS) <= Instant::now() { panic!("Timed out waiting for geth to start. Is geth installed?") @@ -323,19 +328,15 @@ impl Geth { let mut line = String::new(); reader.read_line(&mut line).expect("Failed to read line from geth process"); + if matches!(self.mode, GethMode::NonDev(_)) && line.contains("Started P2P networking") { + p2p_started = true; + } + // geth 1.9.23 uses "server started" while 1.9.18 uses "endpoint opened" - match self.mode { - GethMode::Dev(_) => { - if line.contains("HTTP endpoint opened") || line.contains("HTTP server started") - { - break - } - } - GethMode::NonDev(_) => { - if line.contains("Started P2P networking") { - break - } - } + if p2p_started && line.contains("HTTP endpoint opened") || + line.contains("HTTP server started") + { + break } } diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index fdaec88d0..718a63be7 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -107,7 +107,7 @@ pub struct PeerInfo { pub network: PeerNetworkInfo, /// The protocols that the peer supports, with protocol metadata. - pub protocols: Vec, + pub protocols: ProtocolInfo, } /// Represents networking related information about the peer, including details about whether or @@ -128,5 +128,6 @@ pub struct PeerNetworkInfo { pub trusted: bool, /// Whether or not the peer is a static peer. + #[serde(rename = "static")] pub static_node: bool, } diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 244a773ef..213cceec1 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -1795,15 +1795,18 @@ pub mod dev_rpc { #[cfg(test)] #[cfg(not(target_arch = "wasm32"))] mod tests { + use std::path::PathBuf; + use super::*; use crate::Http; use ethers_core::{ types::{ transaction::eip2930::AccessList, Eip1559TransactionRequest, TransactionRequest, H256, }, - utils::{Anvil, Geth}, + utils::{Anvil, Genesis, Geth, GethInstance}, }; use futures_util::StreamExt; + use tokio::time::sleep; #[test] fn convert_h256_u256_quantity() { @@ -2171,19 +2174,11 @@ mod tests { async fn geth_admin_nodeinfo() { // we can't use the test provider because infura does not expose admin endpoints let port = 8545u16; - let url = format!("http://localhost:{}", port); - let p2p_listener_port = 13337u16; let network = 1337u64; - // we disable discovery because otherwise it will peer with nodes with chainid 1337 and - // try to sync - let geth = Geth::new() - .port(port) - .p2p_port(p2p_listener_port) - .chain_id(network) - .disable_discovery() - .spawn(); - let provider = Provider::try_from(url).unwrap(); + + let (geth, provider) = + spawn_geth_and_create_provider(network, port, p2p_listener_port, None, None); let info = provider.node_info().await.unwrap(); drop(geth); @@ -2197,4 +2192,112 @@ mod tests { // check that the network id is correct assert_eq!(info.protocols.eth.unwrap().network, network); } + + /// Spawn a new `GethInstance` without discovery and crate a `Provider` for it. + /// + /// These will all use the same genesis config. + fn spawn_geth_and_create_provider( + chain_id: u64, + rpc_port: u16, + p2p_port: u16, + datadir: Option, + genesis: Option, + ) -> (GethInstance, Provider) { + let geth = + Geth::new().port(rpc_port).p2p_port(p2p_port).chain_id(chain_id).disable_discovery(); + + let geth = match genesis { + Some(genesis) => geth.genesis(genesis), + None => geth, + }; + + let geth = match datadir { + Some(dir) => geth.data_dir(dir), + None => geth, + } + .spawn(); + + let url = format!("http://localhost:{}", rpc_port); + let provider = Provider::try_from(url).unwrap(); + (geth, provider) + } + + #[tokio::test] + async fn add_second_geth_peer() { + // init each geth directory + let dir1 = tempfile::tempdir().unwrap().into_path(); + let dir2 = tempfile::tempdir().unwrap().into_path(); + + // use the default genesis + let genesis = utils::Genesis::default(); + + // record p2p ports so they can be used to construct enodes + let first_port = 30304u16; + let second_port = 30305u16; + + let (mut first_geth, first_peer) = spawn_geth_and_create_provider( + 1337, + 8546, + first_port, + Some(dir1.clone()), + Some(genesis.clone()), + ); + let (mut second_geth, second_peer) = spawn_geth_and_create_provider( + 1337, + 8547, + second_port, + Some(dir2.clone()), + Some(genesis.clone()), + ); + + // get nodeinfo for each geth instance + let first_info = first_peer.node_info().await.unwrap(); + let second_info = second_peer.node_info().await.unwrap(); + + println!("first_id: {}", hex::encode(first_info.id.0)); + println!("first_nodeinfo: {:?}", first_info); + + // replace the ip in the enode by putting + let first_prefix = first_info.enode.split('@').collect::>(); + let second_prefix = second_info.enode.split('@').collect::>(); + + // create enodes for each geth instance using each id and port + let first_enode = format!("{}@localhost:{}", first_prefix.first().unwrap(), first_port); + let second_enode = format!("{}@localhost:{}", second_prefix.first().unwrap(), second_port); + + // add the second geth as a peer for the first + let res = second_peer.add_peer(first_enode).await.unwrap(); + assert!(res); + + // wait for the geth dial loop to start and connect the static nodes + sleep(Duration::from_secs(20)).await; + + // check that second_geth exists in the first_geth peer list + let peers = first_peer.peers().await.unwrap(); + + let mut first_stderr = first_geth.stderr(); + let mut second_stderr = second_geth.stderr(); + + drop(first_geth); + drop(second_geth); + + // turn the stderrs into a string + let mut first_str = String::new(); + let mut second_str = String::new(); + first_stderr.read_to_string(&mut first_str).unwrap(); + second_stderr.read_to_string(&mut second_str).unwrap(); + + println!("first stderr: {}", first_str); + println!("second stderr: {}", second_str); + + // check that the second peer is in the list (it uses an enr so the enr should be Some) + assert_eq!(peers.len(), 1); + + let first_peer = peers.get(0).unwrap(); + assert_eq!(first_peer.enr, Some(second_info.enr)); + + // remove directories + // std::fs::remove_dir_all(dir1).unwrap(); + // std::fs::remove_dir_all(dir2).unwrap(); + } } From 02db1b12ecada22e6b9be766e4f4f39feff22cea Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 22 Nov 2022 23:10:55 -0500 Subject: [PATCH 27/54] import io::Read in provider tests --- ethers-providers/src/provider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 213cceec1..259f49025 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -1795,7 +1795,7 @@ pub mod dev_rpc { #[cfg(test)] #[cfg(not(target_arch = "wasm32"))] mod tests { - use std::path::PathBuf; + use std::{io::Read, path::PathBuf}; use super::*; use crate::Http; From 6f5b06f82035d125da2f6cc31a94acba2d06ca13 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 25 Nov 2022 21:30:31 -0500 Subject: [PATCH 28/54] fix PeerInfo and use enode id for provider test --- ethers-providers/src/admin.rs | 145 +++++++++++++++++++++++++++---- ethers-providers/src/provider.rs | 8 +- 2 files changed, 134 insertions(+), 19 deletions(-) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index 718a63be7..77bb60eb5 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -8,46 +8,52 @@ use std::net::{IpAddr, SocketAddr}; /// details. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NodeInfo { + // TODO: double check that this is a public key /// The node's secp256k1 public key. pub id: H256, - /// The client user agent, containing a client name, version, OS, and other metadata. + /// The node's user agent, containing a client name, version, OS, and other metadata. pub name: String, - /// The enode URL of the running client. + /// The enode URL of the connected node. pub enode: String, /// The [ENR](https://eips.ethereum.org/EIPS/eip-778) of the running client. pub enr: Enr, - /// The IP address of the running client. + /// The IP address of the connected node. pub ip: IpAddr, - /// The client's listening ports. + /// The node's listening ports. pub ports: Ports, - /// The client's listening address. + /// The node's listening address. #[serde(rename = "listenAddr")] pub listen_addr: String, - /// The protocols that the client supports, with protocol metadata. + /// The protocols that the node supports, with protocol metadata. pub protocols: ProtocolInfo, } /// Represents a node's discovery and listener ports. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Ports { - /// The discovery port of the running client. + /// The node's discovery port. pub discovery: u16, - /// The listener port of the running client. + /// The node's listener port. pub listener: u16, } -/// Represents the protocols that the client supports. +/// Represents protocols that the connected RPC node supports. +/// +/// This contains protocol information reported by the connected RPC node. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ProtocolInfo { + #[serde(default, skip_serializing_if = "Option::is_none")] pub eth: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] pub snap: Option, } @@ -58,7 +64,7 @@ pub struct ProtocolInfo { /// for how these fields are determined. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EthProtocolInfo { - /// The ethereum network ID. + /// The eth network version. pub network: u64, /// The total difficulty of the host's blockchain. @@ -75,27 +81,71 @@ pub struct EthProtocolInfo { pub head: H256, } -/// Represents a short summary of the `snap` sub-protocol metadata known about the host peer. +/// Represents a short summary of the host's `snap` sub-protocol metadata. /// /// This is just an empty struct, because [geth's internal representation is /// empty](https://github.com/ethereum/go-ethereum/blob/c2e0abce2eedc1ba2a1b32c46fd07ef18a25354a/eth/protocols/snap/handler.go#L571-L576). #[derive(Clone, Debug, Deserialize, Serialize)] pub struct SnapProtocolInfo {} +/// Represents the protocols that a peer supports. +/// +/// This differs from [`ProtocolInfo`] in that [`PeerProtocolInfo`] contains protocol information +/// gathered from the protocol handshake, and [`ProtocolInfo`] contains information reported by the +/// connected RPC node. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PeerProtocolInfo { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub eth: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub snap: Option, +} + +/// Represents a short summary of the `eth` sub-protocol metadata known about a connected peer +/// +/// See [geth's `ethPeerInfo` +/// struct](https://github.com/ethereum/go-ethereum/blob/53d1ae096ac0515173e17f0f81a553e5f39027f7/eth/peer.go#L28) +/// for how these fields are determined. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EthPeerInfo { + /// The negotiated eth version. + pub version: u64, + + /// The total difficulty of the peer's blockchain. + #[serde(deserialize_with = "from_int_or_hex")] + pub difficulty: U256, + + /// The hash of the peer's best known block. + pub head: H256, +} + +/// Represents a short summary of the `snap` sub-protocol metadata known about a connected peer. +/// +/// See [geth's `snapPeerInfo` +/// struct](https://github.com/ethereum/go-ethereum/blob/53d1ae096ac0515173e17f0f81a553e5f39027f7/eth/peer.go#L53) +/// for how these fields are determined. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SnapPeerInfo { + /// The negotiated snap version. + pub version: u64, +} + /// Represents a short summary of information known about a connected peer. /// /// See [geth's `PeerInfo` struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/p2p/peer.go#L484) for the source of each field. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct PeerInfo { /// The peer's ENR. - #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub enr: Option>, /// The peer's enode URL. pub enode: String, - /// The peer's network ID. - pub id: String, + // TODO: unify comment with the one in `NodeInfo` + /// The peer's enode ID. + pub id: H256, /// The peer's name. pub name: String, @@ -107,7 +157,7 @@ pub struct PeerInfo { pub network: PeerNetworkInfo, /// The protocols that the peer supports, with protocol metadata. - pub protocols: ProtocolInfo, + pub protocols: PeerProtocolInfo, } /// Represents networking related information about the peer, including details about whether or @@ -131,3 +181,68 @@ pub struct PeerNetworkInfo { #[serde(rename = "static")] pub static_node: bool, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize_peer_info_version() { + let response = r#"{ + "enode":"enode://bb37b7302f79e47c1226d6e3ccf0ef6d51146019efdcc1f6e861fd1c1a78d5e84e486225a6a8a503b93d5c50125ee980835c92bde7f7d12f074c16f4e439a578@127.0.0.1:60872", + "id":"ca23c04b7e796da5d6a5f04a62b81c88d41b1341537db85a2b6443e838d8339b", + "name":"Geth/v1.10.19-stable/darwin-arm64/go1.18.3", + "caps":["eth/66","eth/67","snap/1"], + "network":{ + "localAddress":"127.0.0.1:30304", + "remoteAddress":"127.0.0.1:60872", + "inbound":true, + "trusted":false, + "static":false + }, + "protocols":{ + "eth":{ + "version":67, + "difficulty":0, + "head":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea" + }, + "snap":{"version":1} + } + }"#; + let peer_info: PeerInfo = serde_json::from_str(response).unwrap(); + + assert_eq!(peer_info.enode, "enode://bb37b7302f79e47c1226d6e3ccf0ef6d51146019efdcc1f6e861fd1c1a78d5e84e486225a6a8a503b93d5c50125ee980835c92bde7f7d12f074c16f4e439a578@127.0.0.1:60872"); + } + + #[test] + fn deserialize_node_info_network() { + // this response also has an enr + let response = r#"{ + "id":"6e2fe698f3064cd99410926ce16734e35e3cc947d4354461d2594f2d2dd9f7b6", + "name":"Geth/v1.10.19-stable/darwin-arm64/go1.18.3", + "enode":"enode://d7dfaea49c7ef37701e668652bcf1bc63d3abb2ae97593374a949e175e4ff128730a2f35199f3462a56298b981dfc395a5abebd2d6f0284ffe5bdc3d8e258b86@***REMOVED***:30304?discport=0", + "enr":"enr:-Jy4QIvS0dKBLjTTV_RojS8hjriwWsJNHRVyOh4Pk4aUXc5SZjKRVIOeYc7BqzEmbCjLdIY4Ln7x5ZPf-2SsBAc2_zqGAYSwY1zog2V0aMfGhNegsXuAgmlkgnY0gmlwhBiT_DiJc2VjcDI1NmsxoQLX366knH7zdwHmaGUrzxvGPTq7Kul1kzdKlJ4XXk_xKIRzbmFwwIN0Y3CCdmA", + "ip":"***REMOVED***", + "ports":{ + "discovery":0, + "listener":30304 + }, + "listenAddr":"[::]:30304", + "protocols":{ + "eth":{ + "network":1337, + "difficulty":0, + "genesis":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea", + "config":{ + "chainId":0, + "eip150Hash":"0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "head":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea" + }, + "snap":{} + } + }"#; + + let _: NodeInfo = serde_json::from_str(response).unwrap(); + } +} diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 259f49025..98249a250 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -2293,11 +2293,11 @@ mod tests { // check that the second peer is in the list (it uses an enr so the enr should be Some) assert_eq!(peers.len(), 1); - let first_peer = peers.get(0).unwrap(); - assert_eq!(first_peer.enr, Some(second_info.enr)); + let peer = peers.get(0).unwrap(); + assert_eq!(peer.id, second_info.id); // remove directories - // std::fs::remove_dir_all(dir1).unwrap(); - // std::fs::remove_dir_all(dir2).unwrap(); + std::fs::remove_dir_all(dir1).unwrap(); + std::fs::remove_dir_all(dir2).unwrap(); } } From 037fbcfffa7b50988b2d6d7e37cf18ab89c954ce Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 25 Nov 2022 21:38:35 -0500 Subject: [PATCH 29/54] make clippy happy --- ethers-core/src/utils/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 65fb14a60..4e882a8a5 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -41,8 +41,8 @@ use ethabi::ethereum_types::FromDecStrErr; use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey}; use std::{ convert::{TryFrom, TryInto}, - str::FromStr, fmt, + str::FromStr, }; use thiserror::Error; From 567cf3858f9420895630a5ce20ce636faba59272 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 25 Nov 2022 21:42:32 -0500 Subject: [PATCH 30/54] improve documentation for genesis module --- ethers-core/src/utils/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 4e882a8a5..1e0736187 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -9,8 +9,11 @@ pub use ganache::{Ganache, GanacheInstance}; mod geth; #[cfg(not(target_arch = "wasm32"))] pub use geth::{Geth, GethInstance}; + +/// Utilities for working with a `genesis.json` and other chain config structs. #[cfg(not(target_arch = "wasm32"))] mod genesis; +#[cfg(not(target_arch = "wasm32"))] pub use genesis::{ChainConfig, Genesis}; /// Utilities for launching an anvil instance From 6183c871c94d9e011a05628bbba3d2706fd5e77c Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 25 Nov 2022 21:44:49 -0500 Subject: [PATCH 31/54] remove not(wasm) attributes on genesis module --- ethers-core/src/utils/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 1e0736187..55193bc68 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -11,9 +11,7 @@ mod geth; pub use geth::{Geth, GethInstance}; /// Utilities for working with a `genesis.json` and other chain config structs. -#[cfg(not(target_arch = "wasm32"))] mod genesis; -#[cfg(not(target_arch = "wasm32"))] pub use genesis::{ChainConfig, Genesis}; /// Utilities for launching an anvil instance From d27d0bcdffc939b5cee8e1446b4e957059277dab Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 25 Nov 2022 23:30:43 -0500 Subject: [PATCH 32/54] remove debugging printlns --- ethers-providers/src/provider.rs | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 98249a250..f715956b8 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -2235,14 +2235,14 @@ mod tests { let first_port = 30304u16; let second_port = 30305u16; - let (mut first_geth, first_peer) = spawn_geth_and_create_provider( + let (first_geth, first_peer) = spawn_geth_and_create_provider( 1337, 8546, first_port, Some(dir1.clone()), Some(genesis.clone()), ); - let (mut second_geth, second_peer) = spawn_geth_and_create_provider( + let (second_geth, second_peer) = spawn_geth_and_create_provider( 1337, 8547, second_port, @@ -2254,16 +2254,11 @@ mod tests { let first_info = first_peer.node_info().await.unwrap(); let second_info = second_peer.node_info().await.unwrap(); - println!("first_id: {}", hex::encode(first_info.id.0)); - println!("first_nodeinfo: {:?}", first_info); - // replace the ip in the enode by putting let first_prefix = first_info.enode.split('@').collect::>(); - let second_prefix = second_info.enode.split('@').collect::>(); // create enodes for each geth instance using each id and port let first_enode = format!("{}@localhost:{}", first_prefix.first().unwrap(), first_port); - let second_enode = format!("{}@localhost:{}", second_prefix.first().unwrap(), second_port); // add the second geth as a peer for the first let res = second_peer.add_peer(first_enode).await.unwrap(); @@ -2275,21 +2270,9 @@ mod tests { // check that second_geth exists in the first_geth peer list let peers = first_peer.peers().await.unwrap(); - let mut first_stderr = first_geth.stderr(); - let mut second_stderr = second_geth.stderr(); - drop(first_geth); drop(second_geth); - // turn the stderrs into a string - let mut first_str = String::new(); - let mut second_str = String::new(); - first_stderr.read_to_string(&mut first_str).unwrap(); - second_stderr.read_to_string(&mut second_str).unwrap(); - - println!("first stderr: {}", first_str); - println!("second stderr: {}", second_str); - // check that the second peer is in the list (it uses an enr so the enr should be Some) assert_eq!(peers.len(), 1); From a345fa3b7d31e2e0c45f9fa8c3b927765f0ed394 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 25 Nov 2022 23:31:55 -0500 Subject: [PATCH 33/54] remove io::Read from provider tests --- ethers-providers/src/provider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index f715956b8..12249ce07 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -1795,7 +1795,7 @@ pub mod dev_rpc { #[cfg(test)] #[cfg(not(target_arch = "wasm32"))] mod tests { - use std::{io::Read, path::PathBuf}; + use std::path::PathBuf; use super::*; use crate::Http; From 2c3375d6c4969894d03166740fdd3e4e97c7469f Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 25 Nov 2022 23:51:03 -0500 Subject: [PATCH 34/54] add failing post merge test case --- ethers-providers/src/admin.rs | 39 +++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index 77bb60eb5..88fe6a0fa 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -187,7 +187,7 @@ mod tests { use super::*; #[test] - fn deserialize_peer_info_version() { + fn deserialize_peer_info() { let response = r#"{ "enode":"enode://bb37b7302f79e47c1226d6e3ccf0ef6d51146019efdcc1f6e861fd1c1a78d5e84e486225a6a8a503b93d5c50125ee980835c92bde7f7d12f074c16f4e439a578@127.0.0.1:60872", "id":"ca23c04b7e796da5d6a5f04a62b81c88d41b1341537db85a2b6443e838d8339b", @@ -215,7 +215,7 @@ mod tests { } #[test] - fn deserialize_node_info_network() { + fn deserialize_node_info() { // this response also has an enr let response = r#"{ "id":"6e2fe698f3064cd99410926ce16734e35e3cc947d4354461d2594f2d2dd9f7b6", @@ -245,4 +245,39 @@ mod tests { let _: NodeInfo = serde_json::from_str(response).unwrap(); } + + #[test] + fn deserialize_node_info_post_merge() { + // this response also has an enr + let response = r#"{ + "id":"6e2fe698f3064cd99410926ce16734e35e3cc947d4354461d2594f2d2dd9f7b6", + "name":"Geth/v1.10.19-stable/darwin-arm64/go1.18.3", + "enode":"enode://d7dfaea49c7ef37701e668652bcf1bc63d3abb2ae97593374a949e175e4ff128730a2f35199f3462a56298b981dfc395a5abebd2d6f0284ffe5bdc3d8e258b86@***REMOVED***:30304?discport=0", + "enr":"enr:-Jy4QIvS0dKBLjTTV_RojS8hjriwWsJNHRVyOh4Pk4aUXc5SZjKRVIOeYc7BqzEmbCjLdIY4Ln7x5ZPf-2SsBAc2_zqGAYSwY1zog2V0aMfGhNegsXuAgmlkgnY0gmlwhBiT_DiJc2VjcDI1NmsxoQLX366knH7zdwHmaGUrzxvGPTq7Kul1kzdKlJ4XXk_xKIRzbmFwwIN0Y3CCdmA", + "ip":"***REMOVED***", + "ports":{ + "discovery":0, + "listener":30304 + }, + "listenAddr":"[::]:30304", + "protocols":{ + "eth":{ + "network":1337, + "difficulty":0, + "genesis":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea", + "config":{ + "chainId":0, + "eip150Hash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "terminalTotalDifficulty":58750000000000000000000, + "terminalTotalDifficultyPassed":true, + "ethash":{} + }, + "head":"0xb04009ddf4b0763f42778e7d5937e49bebf1e11b2d26c9dac6cefb5f84b6f8ea" + }, + "snap":{} + } + }"#; + + let _: NodeInfo = serde_json::from_str(response).unwrap(); + } } From 7efbd36a79ecc3668e55b49c1dd31b0eecb7d128 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sat, 26 Nov 2022 00:01:09 -0500 Subject: [PATCH 35/54] add full mainnet nodeinfo for testing --- ethers-providers/src/admin.rs | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index 88fe6a0fa..cd738f235 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -280,4 +280,53 @@ mod tests { let _: NodeInfo = serde_json::from_str(response).unwrap(); } + + #[test] + fn deserialize_node_info_mainnet_full() { + let actual_response = r#"{ + "id": "74477ca052fcf55ee9eafb369fafdb3e91ad7b64fbd7ae15a4985bfdc43696f2", + "name": "Geth/v1.10.26-stable/darwin-arm64/go1.19.3", + "enode": "enode://962184c6f2a19e064e2ddf0d5c5a788c8c5ed3a4909b7f75fb4dad967392ff542772bcc498cd7f15e13eecbde830265f379779c6da1f71fb8fe1a4734dfc0a1e@127.0.0.1:13337?discport=0", + "enr": "enr:-J-4QFttJyL3f2-B2TQmBZNFxex99TSBv1YtB_8jqUbXWkf6LOREKQAPW2bIn8kJ8QvHbWxCQNFzTX6sehjbrz1ZkSuGAYSyQ0_rg2V0aMrJhPxk7ASDEYwwgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQKWIYTG8qGeBk4t3w1cWniMjF7TpJCbf3X7Ta2Wc5L_VIRzbmFwwIN0Y3CCNBk", + "ip": "127.0.0.1", + "ports": { + "discovery": 0, + "listener": 13337 + }, + "listenAddr": "[::]:13337", + "protocols": { + "eth": { + "network": 1337, + "difficulty": 17179869184, + "genesis": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficulty": 58750000000000000000000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + }, + "head": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "snap": {} + } + }"#; + + let _: NodeInfo = serde_json::from_str(actual_response).unwrap(); + } } From 51b4638a3d2fdc6a60ad64555e7dcb2b1a21b2e1 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sat, 26 Nov 2022 00:25:28 -0500 Subject: [PATCH 36/54] support deserializing json bignums to U256 * the serde_json arbitrary_precision feature is necessary so the serde_json::Number variant of `IntOrHexOrBigNum` can be converted into a string and fed into U256::from_dec_string --- ethers-core/Cargo.toml | 2 +- ethers-core/src/utils/genesis.rs | 8 ++++++-- ethers-core/src/utils/mod.rs | 25 ++++++++++++++++++++----- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index e221c89d5..68ce8c73d 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -27,7 +27,7 @@ tiny-keccak = { version = "2.0.2", default-features = false } # misc chrono = { version = "0.4", default-features = false } serde = { version = "1.0.124", default-features = false, features = ["derive"] } -serde_json = { version = "1.0.64", default-features = false } +serde_json = { version = "1.0.64", default-features = false, features = ["arbitrary_precision"] } thiserror = { version = "1.0", default-features = false } bytes = { version = "1.3.0", features = ["serde"] } hex = { version = "0.4.3", default-features = false, features = ["std"] } diff --git a/ethers-core/src/utils/genesis.rs b/ethers-core/src/utils/genesis.rs index b65a81951..232dd7d4f 100644 --- a/ethers-core/src/utils/genesis.rs +++ b/ethers-core/src/utils/genesis.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; -use crate::types::{Address, Bytes, H256, U256, U64}; +use crate::{ + types::{Address, Bytes, H256, U256, U64}, + utils::{from_int_or_hex, from_int_or_hex_opt}, +}; use serde::{Deserialize, Serialize}; /// This represents the chain configuration, specifying the genesis block, header fields, and hard @@ -24,6 +27,7 @@ pub struct Genesis { pub gas_limit: U64, /// The genesis header difficulty. + #[serde(deserialize_with = "from_int_or_hex")] pub difficulty: U256, /// The genesis header mix hash. @@ -135,7 +139,7 @@ pub struct ChainConfig { pub cancun_block: Option, /// Total difficulty reached that triggers the merge consensus upgrade. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none", deserialize_with = "from_int_or_hex_opt")] pub terminal_total_difficulty: Option, /// A flag specifying that the network already passed the terminal total difficulty. Its diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 55193bc68..6392d2977 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -458,23 +458,38 @@ pub fn eip1559_default_estimator(base_fee_per_gas: U256, rewards: Vec> (max_fee_per_gas, max_priority_fee_per_gas) } -/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings. +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with +/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(untagged)] - enum IntOrHex { + enum IntOrHexOrBigNum { Int(u64), Hex(String), + BigNumber(serde_json::Number), } - match IntOrHex::deserialize(deserializer)? { - IntOrHex::Int(n) => Ok(U256::from(n)), - IntOrHex::Hex(s) => U256::from_str(s.as_str()).map_err(serde::de::Error::custom), + + match IntOrHexOrBigNum::deserialize(deserializer)? { + IntOrHexOrBigNum::Int(n) => Ok(U256::from(n)), + IntOrHexOrBigNum::Hex(s) => U256::from_str(s.as_str()).map_err(serde::de::Error::custom), + IntOrHexOrBigNum::BigNumber(b) => { + U256::from_dec_str(&b.to_string()).map_err(serde::de::Error::custom) + } } } +/// Deserializes the input into an Option, using [`from_int_or_hex`] to deserialize the inner +/// value. +pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Some(from_int_or_hex(deserializer)?)) +} + fn estimate_priority_fee(rewards: Vec>) -> U256 { let mut rewards: Vec = rewards.iter().map(|r| r[0]).filter(|r| *r > U256::zero()).collect(); From 302cb9c00573f5ed06242086024c8f902d3c8f47 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sat, 26 Nov 2022 00:30:29 -0500 Subject: [PATCH 37/54] fix from_int_or_hex_opt doc string --- ethers-core/src/utils/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 6392d2977..cb5ab9211 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -481,8 +481,8 @@ where } } -/// Deserializes the input into an Option, using [`from_int_or_hex`] to deserialize the inner -/// value. +/// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the +/// inner value. pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, From 54ad70c45584696e3d246eee27008da8d8d36b87 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sat, 26 Nov 2022 00:44:55 -0500 Subject: [PATCH 38/54] remove third variant from IntOrHex * unnecessary since serde_json::Number handles smaller ints as well --- ethers-core/src/utils/mod.rs | 14 +++++--------- ethers-providers/src/admin.rs | 8 ++++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index cb5ab9211..5a2e1315a 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -466,18 +466,14 @@ where { #[derive(Deserialize)] #[serde(untagged)] - enum IntOrHexOrBigNum { - Int(u64), + enum IntOrHex { + Int(serde_json::Number), Hex(String), - BigNumber(serde_json::Number), } - match IntOrHexOrBigNum::deserialize(deserializer)? { - IntOrHexOrBigNum::Int(n) => Ok(U256::from(n)), - IntOrHexOrBigNum::Hex(s) => U256::from_str(s.as_str()).map_err(serde::de::Error::custom), - IntOrHexOrBigNum::BigNumber(b) => { - U256::from_dec_str(&b.to_string()).map_err(serde::de::Error::custom) - } + match IntOrHex::deserialize(deserializer)? { + IntOrHex::Hex(s) => U256::from_str(s.as_str()).map_err(serde::de::Error::custom), + IntOrHex::Int(n) => U256::from_dec_str(&n.to_string()).map_err(serde::de::Error::custom), } } diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index cd738f235..cefae3711 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -220,9 +220,9 @@ mod tests { let response = r#"{ "id":"6e2fe698f3064cd99410926ce16734e35e3cc947d4354461d2594f2d2dd9f7b6", "name":"Geth/v1.10.19-stable/darwin-arm64/go1.18.3", - "enode":"enode://d7dfaea49c7ef37701e668652bcf1bc63d3abb2ae97593374a949e175e4ff128730a2f35199f3462a56298b981dfc395a5abebd2d6f0284ffe5bdc3d8e258b86@***REMOVED***:30304?discport=0", + "enode":"enode://d7dfaea49c7ef37701e668652bcf1bc63d3abb2ae97593374a949e175e4ff128730a2f35199f3462a56298b981dfc395a5abebd2d6f0284ffe5bdc3d8e258b86@127.0.0.1:30304?discport=0", "enr":"enr:-Jy4QIvS0dKBLjTTV_RojS8hjriwWsJNHRVyOh4Pk4aUXc5SZjKRVIOeYc7BqzEmbCjLdIY4Ln7x5ZPf-2SsBAc2_zqGAYSwY1zog2V0aMfGhNegsXuAgmlkgnY0gmlwhBiT_DiJc2VjcDI1NmsxoQLX366knH7zdwHmaGUrzxvGPTq7Kul1kzdKlJ4XXk_xKIRzbmFwwIN0Y3CCdmA", - "ip":"***REMOVED***", + "ip":"127.0.0.1", "ports":{ "discovery":0, "listener":30304 @@ -252,9 +252,9 @@ mod tests { let response = r#"{ "id":"6e2fe698f3064cd99410926ce16734e35e3cc947d4354461d2594f2d2dd9f7b6", "name":"Geth/v1.10.19-stable/darwin-arm64/go1.18.3", - "enode":"enode://d7dfaea49c7ef37701e668652bcf1bc63d3abb2ae97593374a949e175e4ff128730a2f35199f3462a56298b981dfc395a5abebd2d6f0284ffe5bdc3d8e258b86@***REMOVED***:30304?discport=0", + "enode":"enode://d7dfaea49c7ef37701e668652bcf1bc63d3abb2ae97593374a949e175e4ff128730a2f35199f3462a56298b981dfc395a5abebd2d6f0284ffe5bdc3d8e258b86@127.0.0.1:30304?discport=0", "enr":"enr:-Jy4QIvS0dKBLjTTV_RojS8hjriwWsJNHRVyOh4Pk4aUXc5SZjKRVIOeYc7BqzEmbCjLdIY4Ln7x5ZPf-2SsBAc2_zqGAYSwY1zog2V0aMfGhNegsXuAgmlkgnY0gmlwhBiT_DiJc2VjcDI1NmsxoQLX366knH7zdwHmaGUrzxvGPTq7Kul1kzdKlJ4XXk_xKIRzbmFwwIN0Y3CCdmA", - "ip":"***REMOVED***", + "ip":"127.0.0.1", "ports":{ "discovery":0, "listener":30304 From ae22226f7ce90ee8b27026c2390bc9272fb3e5a1 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sat, 26 Nov 2022 01:36:25 -0500 Subject: [PATCH 39/54] add comments for ids --- ethers-providers/src/admin.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index cefae3711..eefc430c8 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -8,8 +8,7 @@ use std::net::{IpAddr, SocketAddr}; /// details. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NodeInfo { - // TODO: double check that this is a public key - /// The node's secp256k1 public key. + /// The node's private key. pub id: H256, /// The node's user agent, containing a client name, version, OS, and other metadata. @@ -143,9 +142,8 @@ pub struct PeerInfo { /// The peer's enode URL. pub enode: String, - // TODO: unify comment with the one in `NodeInfo` /// The peer's enode ID. - pub id: H256, + pub id: String, /// The peer's name. pub name: String, From cce36a66ccbd171a6c9345116b9c6ab25655ba00 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sat, 26 Nov 2022 01:51:17 -0500 Subject: [PATCH 40/54] fix enode id type in admin_peers provider test --- ethers-providers/src/provider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 12249ce07..9086f54cf 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -2277,7 +2277,7 @@ mod tests { assert_eq!(peers.len(), 1); let peer = peers.get(0).unwrap(); - assert_eq!(peer.id, second_info.id); + assert_eq!(H256::from_str(&peer.id).unwrap(), second_info.id); // remove directories std::fs::remove_dir_all(dir1).unwrap(); From 20557f0f8bc5c87a1baf505939ed9209a6b7a4fa Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 28 Nov 2022 16:33:15 -0500 Subject: [PATCH 41/54] fix admin typo in Cargo.toml Co-authored-by: Georgios Konstantopoulos --- ethers-providers/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml index 52ae45c0a..f2231e951 100644 --- a/ethers-providers/Cargo.toml +++ b/ethers-providers/Cargo.toml @@ -35,7 +35,7 @@ futures-timer = { version = "3.0.2", default-features = false } futures-channel = { version = "0.3.16", default-features = false, optional = true } pin-project = { version = "1.0.11", default-features = false } -# peer-related amin namespace +# peer-related admin namespace enr = { version = "0.7.0", default-features = false, features = ["k256", "serde"] } # tracing From 7697f6992e54279ffa9b5d83e1f6185ea30e3d23 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 28 Nov 2022 16:27:09 -0500 Subject: [PATCH 42/54] add method to wait on a gethinstance adding a peer --- ethers-core/src/utils/geth.rs | 69 ++++++++++++++++++++++---- ethers-providers/src/provider.rs | 85 ++++++++++++++++++++++---------- 2 files changed, 118 insertions(+), 36 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 7c719134b..f370a0d79 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -1,22 +1,39 @@ use super::{unused_port, Genesis}; +use crate::types::H256; use std::{ env::temp_dir, fs::File, io::{BufRead, BufReader}, path::PathBuf, - process::{Child, ChildStderr, Command}, + process::{Child, Command}, time::{Duration, Instant}, }; /// How long we will wait for geth to indicate that it is ready. const GETH_STARTUP_TIMEOUT_MILLIS: u64 = 10_000; +/// Timeout for waiting for geth's dial loop to start. +const GETH_DIAL_LOOP_TIMEOUT: Duration = Duration::new(20, 0); + /// The exposed APIs const API: &str = "eth,net,web3,txpool,admin"; /// The geth command const GETH: &str = "geth"; +/// Errors that can occur when working with the [`GethInstance`]. +#[derive(Debug)] +pub enum GethInstanceError { + /// Timed out waiting for a message from geth's stderr. + Timeout(String), + + /// A line could not be read from the geth stderr. + ReadLineError(std::io::Error), + + /// The child geth process's stderr was not captured. + NoStderr, +} + /// A geth instance. Will close the instance when dropped. /// /// Construct this using [`Geth`](crate::utils::Geth) @@ -59,9 +76,25 @@ impl GethInstance { &self.data_dir } - /// Return a `BufReader` for the stderr output of this instance - pub fn stderr(&mut self) -> BufReader { - BufReader::new(self.pid.stderr.take().unwrap()) + /// Blocks until geth adds the specified peer, using [`GETH_DIAL_LOOP_TIMEOUT`] as the timeout. + pub fn wait_to_add_peer(&mut self, id: H256) -> Result<(), GethInstanceError> { + let mut stderr = self.pid.stderr.as_mut().ok_or(GethInstanceError::NoStderr)?; + let mut err_reader = BufReader::new(&mut stderr); + let mut line = String::new(); + let start = Instant::now(); + + while start.elapsed() < GETH_DIAL_LOOP_TIMEOUT { + line.clear(); + err_reader.read_line(&mut line).map_err(GethInstanceError::ReadLineError)?; + + // geth ids are trunated + let truncated_id = hex::encode(&id.0[..8]); + if line.contains("Adding p2p peer") && line.contains(&truncated_id) { + println!("Found peer in log: {}", line); + return Ok(()) + } + } + Err(GethInstanceError::Timeout("Timed out waiting for dial loop to start".into())) } } @@ -133,6 +166,7 @@ impl Default for PrivateNetOptions { #[derive(Clone, Default)] pub struct Geth { port: Option, + authrpc_port: Option, ipc_path: Option, data_dir: Option, chain_id: Option, @@ -231,6 +265,13 @@ impl Geth { self } + /// Sets the port for authenticated RPC connections. + #[must_use] + pub fn authrpc_port(mut self, port: u16) -> Self { + self.authrpc_port = Some(port); + self + } + /// Consumes the builder and spawns `geth` with stdout redirected /// to /dev/null. pub fn spawn(self) -> GethInstance { @@ -249,6 +290,11 @@ impl Geth { cmd.arg("--ws.port").arg(port.to_string()); cmd.arg("--ws.api").arg(API); + // Set the port for authenticated APIs + if let Some(authrpc_port) = self.authrpc_port { + cmd.arg("--authrpc.port").arg(authrpc_port.to_string()); + } + // use geth init to initialize the datadir if the genesis exists if let Some(genesis) = self.genesis { // create a temp dir to store the genesis file @@ -304,8 +350,8 @@ impl Geth { } } - // verbosity 5 - cmd.arg("--verbosity").arg("5"); + // debug verbosity is needed to check when peers are added + cmd.arg("--verbosity").arg("4"); if let Some(ref ipc) = self.ipc_path { cmd.arg("--ipcpath").arg(ipc); @@ -318,7 +364,10 @@ impl Geth { let start = Instant::now(); let mut reader = BufReader::new(stdout); + // we shouldn't need to wait for p2p to start if geth is in dev mode - p2p is disabled in + // dev mode let mut p2p_started = matches!(self.mode, GethMode::Dev(_)); + let mut http_started = false; loop { if start + Duration::from_millis(GETH_STARTUP_TIMEOUT_MILLIS) <= Instant::now() { @@ -333,9 +382,11 @@ impl Geth { } // geth 1.9.23 uses "server started" while 1.9.18 uses "endpoint opened" - if p2p_started && line.contains("HTTP endpoint opened") || - line.contains("HTTP server started") - { + if line.contains("HTTP endpoint opened") || line.contains("HTTP server started") { + http_started = true; + } + + if p2p_started && http_started { break } } diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 9086f54cf..b3af1ffe9 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -1806,7 +1806,6 @@ mod tests { utils::{Anvil, Genesis, Geth, GethInstance}, }; use futures_util::StreamExt; - use tokio::time::sleep; #[test] fn convert_h256_u256_quantity() { @@ -2175,10 +2174,17 @@ mod tests { // we can't use the test provider because infura does not expose admin endpoints let port = 8545u16; let p2p_listener_port = 13337u16; + let authrpc_port = 8551u16; let network = 1337u64; - let (geth, provider) = - spawn_geth_and_create_provider(network, port, p2p_listener_port, None, None); + let (geth, provider) = spawn_geth_and_create_provider( + network, + port, + p2p_listener_port, + authrpc_port, + None, + None, + ); let info = provider.node_info().await.unwrap(); drop(geth); @@ -2200,11 +2206,16 @@ mod tests { chain_id: u64, rpc_port: u16, p2p_port: u16, + authrpc_port: u16, datadir: Option, genesis: Option, ) -> (GethInstance, Provider) { - let geth = - Geth::new().port(rpc_port).p2p_port(p2p_port).chain_id(chain_id).disable_discovery(); + let geth = Geth::new() + .port(rpc_port) + .p2p_port(p2p_port) + .authrpc_port(authrpc_port) + .chain_id(chain_id) + .disable_discovery(); let geth = match genesis { Some(genesis) => geth.genesis(genesis), @@ -2217,11 +2228,44 @@ mod tests { } .spawn(); - let url = format!("http://localhost:{}", rpc_port); + let url = format!("http://127.0.0.1:{}", rpc_port); let provider = Provider::try_from(url).unwrap(); (geth, provider) } + /// Spawn a set of [`GethInstance`]s with the list of given data directories and [`Provider`]s + /// for those [`GethInstance`]s without discovery, setting sequential ports for their p2p, rpc, + /// and authrpc ports. + fn spawn_geth_instances( + datadirs: Vec, + chain_id: u64, + genesis: Option, + ) -> Vec<(GethInstance, Provider)> { + let mut geths = Vec::new(); + let mut p2p_port = 30303; + let mut rpc_port = 8545; + let mut authrpc_port = 8551; + + for dir in datadirs { + let (geth, provider) = spawn_geth_and_create_provider( + chain_id, + rpc_port, + p2p_port, + authrpc_port, + Some(dir), + genesis.clone(), + ); + + geths.push((geth, provider)); + + p2p_port += 1; + rpc_port += 1; + authrpc_port += 1; + } + + geths + } + #[tokio::test] async fn add_second_geth_peer() { // init each geth directory @@ -2231,28 +2275,15 @@ mod tests { // use the default genesis let genesis = utils::Genesis::default(); - // record p2p ports so they can be used to construct enodes - let first_port = 30304u16; - let second_port = 30305u16; - - let (first_geth, first_peer) = spawn_geth_and_create_provider( - 1337, - 8546, - first_port, - Some(dir1.clone()), - Some(genesis.clone()), - ); - let (second_geth, second_peer) = spawn_geth_and_create_provider( - 1337, - 8547, - second_port, - Some(dir2.clone()), - Some(genesis.clone()), - ); + // spawn the geths + let mut geths = spawn_geth_instances(vec![dir1.clone(), dir2.clone()], 1337, Some(genesis)); + let (mut first_geth, first_peer) = geths.pop().unwrap(); + let (second_geth, second_peer) = geths.pop().unwrap(); // get nodeinfo for each geth instance let first_info = first_peer.node_info().await.unwrap(); let second_info = second_peer.node_info().await.unwrap(); + let first_port = first_info.ports.listener; // replace the ip in the enode by putting let first_prefix = first_info.enode.split('@').collect::>(); @@ -2260,12 +2291,12 @@ mod tests { // create enodes for each geth instance using each id and port let first_enode = format!("{}@localhost:{}", first_prefix.first().unwrap(), first_port); - // add the second geth as a peer for the first + // add the first geth as a peer for the second let res = second_peer.add_peer(first_enode).await.unwrap(); assert!(res); - // wait for the geth dial loop to start and connect the static nodes - sleep(Duration::from_secs(20)).await; + // wait on the listening peer for an incoming connection + first_geth.wait_to_add_peer(second_info.id).unwrap(); // check that second_geth exists in the first_geth peer list let peers = first_peer.peers().await.unwrap(); From e92dbf601762ea997b024e049cacb6c69ac31b55 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 28 Nov 2022 16:43:34 -0500 Subject: [PATCH 43/54] fix dial loop and wait_to_add_peer doc comments --- ethers-core/src/utils/geth.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index f370a0d79..baa464047 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -12,7 +12,7 @@ use std::{ /// How long we will wait for geth to indicate that it is ready. const GETH_STARTUP_TIMEOUT_MILLIS: u64 = 10_000; -/// Timeout for waiting for geth's dial loop to start. +/// Timeout for waiting for geth to add a peer. const GETH_DIAL_LOOP_TIMEOUT: Duration = Duration::new(20, 0); /// The exposed APIs @@ -76,7 +76,7 @@ impl GethInstance { &self.data_dir } - /// Blocks until geth adds the specified peer, using [`GETH_DIAL_LOOP_TIMEOUT`] as the timeout. + /// Blocks until geth adds the specified peer, using 20s as the timeout. pub fn wait_to_add_peer(&mut self, id: H256) -> Result<(), GethInstanceError> { let mut stderr = self.pid.stderr.as_mut().ok_or(GethInstanceError::NoStderr)?; let mut err_reader = BufReader::new(&mut stderr); @@ -94,7 +94,7 @@ impl GethInstance { return Ok(()) } } - Err(GethInstanceError::Timeout("Timed out waiting for dial loop to start".into())) + Err(GethInstanceError::Timeout("Timed out waiting for geth to add a peer".into())) } } From 50ccfce212778e7372da64293d9edbb0f2efe4af Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 28 Nov 2022 17:00:11 -0500 Subject: [PATCH 44/54] update geth * the build can be updated by changing the GETH_BUILD env var --- .github/workflows/ci.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14281f96c..bcfaa4ba9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ env: ETHERSCAN_API_KEY_ETHEREUM: I5BXNZYP5GEDWFINGVEZKYIVU2695NPQZB ETHERSCAN_API_KEY_CELO: B13XSMUT6Q3Q4WZ5DNQR8RXDBA2KNTMT4M GOERLI_PRIVATE_KEY: "fa4a1a79e869a96fcb42727f75e3232d6865a82ea675bb95de967a7fe6a773b2" + GETH_BUILD: 1.10.26-e5eb32ac jobs: tests: @@ -36,9 +37,9 @@ jobs: - name: Install geth run: | mkdir -p "$HOME/bin" - wget -q https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.23-8c2f2715.tar.gz - tar -xvf geth-linux-amd64-1.9.23-8c2f2715.tar.gz - mv geth-linux-amd64-1.9.23-8c2f2715/geth $HOME/bin/geth + wget -q https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-$GETH_BUILD.tar.gz + tar -xvf geth-linux-amd64-$GETH_BUILD.tar.gz + mv geth-linux-amd64-$GETH_BUILD/geth $HOME/bin/geth chmod u+x "$HOME/bin/geth" export PATH=$HOME/bin:$PATH geth version @@ -79,9 +80,9 @@ jobs: - name: Install geth run: | mkdir -p "$HOME/bin" - wget -q https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.23-8c2f2715.tar.gz - tar -xvf geth-linux-amd64-1.9.23-8c2f2715.tar.gz - mv geth-linux-amd64-1.9.23-8c2f2715/geth $HOME/bin/geth + wget -q https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-$GETH_BUILD.tar.gz + tar -xvf geth-linux-amd64-$GETH_BUILD.tar.gz + mv geth-linux-amd64-$GETH_BUILD/geth $HOME/bin/geth chmod u+x "$HOME/bin/geth" export PATH=$HOME/bin:$PATH geth version @@ -201,9 +202,9 @@ jobs: - name: Install geth (for state overrides example) run: | mkdir -p "$HOME/bin" - wget -q https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.23-8c2f2715.tar.gz - tar -xvf geth-linux-amd64-1.9.23-8c2f2715.tar.gz - mv geth-linux-amd64-1.9.23-8c2f2715/geth $HOME/bin/geth + wget -q https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-$GETH_BUILD.tar.gz + tar -xvf geth-linux-amd64-$GETH_BUILD.tar.gz + mv geth-linux-amd64-$GETH_BUILD/geth $HOME/bin/geth chmod u+x "$HOME/bin/geth" export PATH=$HOME/bin:$PATH geth version From af47a3160cfeec3ef312198cb05fbd33feefd4fd Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 28 Nov 2022 18:18:34 -0500 Subject: [PATCH 45/54] wait for geth to exit on drop --- ethers-core/src/utils/geth.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index baa464047..e22a350bd 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -5,7 +5,7 @@ use std::{ fs::File, io::{BufRead, BufReader}, path::PathBuf, - process::{Child, Command}, + process::{Child, Command, Stdio}, time::{Duration, Instant}, }; @@ -101,6 +101,9 @@ impl GethInstance { impl Drop for GethInstance { fn drop(&mut self) { self.pid.kill().expect("could not kill geth"); + // wait for the process to exit - we don't need to use the Result because the process could + // have already exited + _ = self.pid.wait(); } } @@ -312,12 +315,15 @@ impl Geth { init_cmd.arg("--datadir").arg(data_dir); } + // set the stderr to null so we don't pollute the test output + init_cmd.stderr(Stdio::null()); + init_cmd.arg("init").arg(temp_genesis_path); init_cmd .spawn() .expect("failed to spawn geth init") .wait() - .expect("failed to wait for geth init"); + .expect("failed to wait for geth init to exit"); } if let Some(ref data_dir) = self.data_dir { @@ -359,10 +365,10 @@ impl Geth { let mut child = cmd.spawn().expect("couldnt start geth"); - let stdout = child.stderr.expect("Unable to get stderr for geth child process"); + let stderr = child.stderr.expect("Unable to get stderr for geth child process"); let start = Instant::now(); - let mut reader = BufReader::new(stdout); + let mut reader = BufReader::new(stderr); // we shouldn't need to wait for p2p to start if geth is in dev mode - p2p is disabled in // dev mode From f6188d35a1b04e34d29a7de1db25c3a29d2dcc1c Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 28 Nov 2022 18:27:15 -0500 Subject: [PATCH 46/54] remove unnecessary wait --- ethers-core/src/utils/geth.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index e22a350bd..70bf56bc9 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -101,9 +101,6 @@ impl GethInstance { impl Drop for GethInstance { fn drop(&mut self) { self.pid.kill().expect("could not kill geth"); - // wait for the process to exit - we don't need to use the Result because the process could - // have already exited - _ = self.pid.wait(); } } From 548b360d886819481f0d6b489ac64037da56c415 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 28 Nov 2022 20:26:14 -0500 Subject: [PATCH 47/54] fix mid-handshake PeerInfo deserialization --- ethers-providers/src/admin.rs | 80 +++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/admin.rs index eefc430c8..3de1ed4ec 100644 --- a/ethers-providers/src/admin.rs +++ b/ethers-providers/src/admin.rs @@ -101,13 +101,26 @@ pub struct PeerProtocolInfo { pub snap: Option, } +/// Can contain either eth protocol info or a string "handshake", which geth uses if the peer is +/// still completing the handshake for the protocol. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum EthPeerInfo { + /// The `eth` sub-protocol metadata known about the host peer. + Info(Box), + + /// The string "handshake" if the peer is still completing the handshake for the protocol. + #[serde(deserialize_with = "deser_handshake", serialize_with = "ser_handshake")] + Handshake, +} + /// Represents a short summary of the `eth` sub-protocol metadata known about a connected peer /// /// See [geth's `ethPeerInfo` /// struct](https://github.com/ethereum/go-ethereum/blob/53d1ae096ac0515173e17f0f81a553e5f39027f7/eth/peer.go#L28) /// for how these fields are determined. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct EthPeerInfo { +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct EthInfo { /// The negotiated eth version. pub version: u64, @@ -119,13 +132,26 @@ pub struct EthPeerInfo { pub head: H256, } +/// Can contain either snap protocol info or a string "handshake", which geth uses if the peer is +/// still completing the handshake for the protocol. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum SnapPeerInfo { + /// The `snap` sub-protocol metadata known about the host peer. + Info(SnapInfo), + + /// The string "handshake" if the peer is still completing the handshake for the protocol. + #[serde(deserialize_with = "deser_handshake", serialize_with = "ser_handshake")] + Handshake, +} + /// Represents a short summary of the `snap` sub-protocol metadata known about a connected peer. /// /// See [geth's `snapPeerInfo` /// struct](https://github.com/ethereum/go-ethereum/blob/53d1ae096ac0515173e17f0f81a553e5f39027f7/eth/peer.go#L53) /// for how these fields are determined. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SnapPeerInfo { +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct SnapInfo { /// The negotiated snap version. pub version: u64, } @@ -180,6 +206,27 @@ pub struct PeerNetworkInfo { pub static_node: bool, } +fn deser_handshake<'de, D>(deserializer: D) -> Result<(), D::Error> +where + D: serde::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + if s == "handshake" { + Ok(()) + } else { + Err(serde::de::Error::custom( + "expected \"handshake\" if protocol info did not appear in the response", + )) + } +} + +fn ser_handshake(serializer: S) -> Result +where + S: serde::Serializer, +{ + serializer.serialize_str("handshake") +} + #[cfg(test)] mod tests { use super::*; @@ -327,4 +374,29 @@ mod tests { let _: NodeInfo = serde_json::from_str(actual_response).unwrap(); } + + #[test] + fn deserialize_peer_info_handshake() { + let response = r#"{ + "enode": "enode://a997fde0023537ad01e536ebf2eeeb4b4b3d5286707586727b704f32e8e2b4959e08b6db5b27eb6b7e9f6efcbb53657f4e2bd16900aa77a89426dc3382c29ce0@[::1]:60948", + "id": "df6f8bc331005962c2ef1f5236486a753bc6b2ddb5ef04370757999d1ca832d4", + "name": "Geth/v1.10.26-stable-e5eb32ac/linux-amd64/go1.18.5", + "caps": ["eth/66","eth/67","snap/1"], + "network":{ + "localAddress":"[::1]:30304", + "remoteAddress":"[::1]:60948", + "inbound":true, + "trusted":false, + "static":false + }, + "protocols":{ + "eth":"handshake", + "snap":"handshake" + } + }"#; + + let info: PeerInfo = serde_json::from_str(response).unwrap(); + assert_eq!(info.protocols.eth, Some(EthPeerInfo::Handshake)); + assert_eq!(info.protocols.snap, Some(SnapPeerInfo::Handshake)); + } } From dbb53a9788d1ae91605c359ae497e4ddd3a98d1b Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 28 Nov 2022 20:27:24 -0500 Subject: [PATCH 48/54] remove println --- ethers-core/src/utils/geth.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 70bf56bc9..56d586c45 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -90,7 +90,6 @@ impl GethInstance { // geth ids are trunated let truncated_id = hex::encode(&id.0[..8]); if line.contains("Adding p2p peer") && line.contains(&truncated_id) { - println!("Found peer in log: {}", line); return Ok(()) } } From 96c897c3dfd0a269067ea9a24ab4b8279cd9086f Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:48:06 -0500 Subject: [PATCH 49/54] make tests less flaky --- ethers-core/src/utils/geth.rs | 10 ++++++++-- ethers-providers/src/provider.rs | 7 ++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 56d586c45..807538f63 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -2,7 +2,7 @@ use super::{unused_port, Genesis}; use crate::types::H256; use std::{ env::temp_dir, - fs::File, + fs::{create_dir, File}, io::{BufRead, BufReader}, path::PathBuf, process::{Child, Command, Stdio}, @@ -324,6 +324,11 @@ impl Geth { if let Some(ref data_dir) = self.data_dir { cmd.arg("--datadir").arg(data_dir); + + // create the directory if it doesn't exist + if !data_dir.exists() { + create_dir(data_dir).expect("could not create data dir"); + } } // Dev mode with custom block time @@ -384,7 +389,8 @@ impl Geth { } // geth 1.9.23 uses "server started" while 1.9.18 uses "endpoint opened" - if line.contains("HTTP endpoint opened") || line.contains("HTTP server started") { + // the unauthenticated api is used for regular non-engine API requests + if line.contains("HTTP endpoint opened") || (line.contains("HTTP server started") && !line.contains("auth=true")) { http_started = true; } diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index b3af1ffe9..846742d28 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -2172,17 +2172,18 @@ mod tests { #[tokio::test] async fn geth_admin_nodeinfo() { // we can't use the test provider because infura does not expose admin endpoints - let port = 8545u16; + let port = 8546u16; let p2p_listener_port = 13337u16; - let authrpc_port = 8551u16; + let authrpc_port = 8552u16; let network = 1337u64; + let temp_dir = tempfile::tempdir().unwrap().into_path(); let (geth, provider) = spawn_geth_and_create_provider( network, port, p2p_listener_port, authrpc_port, - None, + Some(temp_dir), None, ); From 7c445e318f22a61e8b1dcffafc579d9962f3bc98 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:40:08 -0500 Subject: [PATCH 50/54] handle discovery with the rest of the non dev opts --- ethers-core/src/utils/geth.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 807538f63..5baab50c4 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -339,10 +339,15 @@ impl Geth { cmd.arg("--dev.period").arg(block_time.to_string()); } } - GethMode::NonDev(PrivateNetOptions { p2p_port, .. }) => { + GethMode::NonDev(PrivateNetOptions { p2p_port, discovery }) => { if let Some(p2p_port) = p2p_port { cmd.arg("--port").arg(p2p_port.to_string()); } + + // disable discovery if the flag is set + if !discovery { + cmd.arg("--nodiscover"); + } } } @@ -350,13 +355,6 @@ impl Geth { cmd.arg("--networkid").arg(chain_id.to_string()); } - // disable discovery if the flag is set - if let GethMode::NonDev(PrivateNetOptions { discovery, .. }) = self.mode { - if !discovery { - cmd.arg("--nodiscover"); - } - } - // debug verbosity is needed to check when peers are added cmd.arg("--verbosity").arg("4"); @@ -390,7 +388,9 @@ impl Geth { // geth 1.9.23 uses "server started" while 1.9.18 uses "endpoint opened" // the unauthenticated api is used for regular non-engine API requests - if line.contains("HTTP endpoint opened") || (line.contains("HTTP server started") && !line.contains("auth=true")) { + if line.contains("HTTP endpoint opened") || + (line.contains("HTTP server started") && !line.contains("auth=true")) + { http_started = true; } From a66548d7a09188f277595dd604e2b78e1c780266 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:47:50 -0500 Subject: [PATCH 51/54] dump geth stderr to debug failing ci test * add method which dumps the unread stderr of the geth instance into a string --- ethers-core/src/utils/geth.rs | 14 ++++++++++++++ ethers-providers/src/call_raw.rs | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 5baab50c4..3ac611a1a 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -95,6 +95,20 @@ impl GethInstance { } Err(GethInstanceError::Timeout("Timed out waiting for geth to add a peer".into())) } + + /// Dumps the unread stderr of the geth instance to a string. + pub fn dump_stderr(&mut self) -> Result { + let mut stderr = self.pid.stderr.as_mut().ok_or(GethInstanceError::NoStderr)?; + let mut err_reader = BufReader::new(&mut stderr); + let mut line = String::new(); + let mut output = String::new(); + + while err_reader.read_line(&mut line).map_err(GethInstanceError::ReadLineError)? > 0 { + output.push_str(&line); + line.clear(); + } + Ok(output) + } } impl Drop for GethInstance { diff --git a/ethers-providers/src/call_raw.rs b/ethers-providers/src/call_raw.rs index 24e19b763..a06df8647 100644 --- a/ethers-providers/src/call_raw.rs +++ b/ethers-providers/src/call_raw.rs @@ -564,7 +564,7 @@ mod tests { #[tokio::test] async fn test_state_overrides() { - let geth = Geth::new().spawn(); + let mut geth = Geth::new().spawn(); let provider = Provider::::try_from(geth.endpoint()).unwrap(); let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap(); @@ -574,6 +574,9 @@ mod tests { // Not enough ether to pay for the transaction let tx = TransactionRequest::pay(adr2, pay_amt).from(adr1).into(); + // dump the geth logs TODO REMOVE + println!("geth logs: {}", geth.dump_stderr().unwrap()); + // assert that overriding the sender's balance works let state = spoof::balance(adr1, pay_amt * 2); provider.call_raw(&tx).state(&state).await.expect("eth_call success"); From b71c71c6ce0aefa7a616c100b6879e9e6bb7db1d Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 29 Nov 2022 16:56:45 -0500 Subject: [PATCH 52/54] remove call_raw debug println * bug is due to authrpc endpoint being in use --- ethers-providers/src/call_raw.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ethers-providers/src/call_raw.rs b/ethers-providers/src/call_raw.rs index a06df8647..24e19b763 100644 --- a/ethers-providers/src/call_raw.rs +++ b/ethers-providers/src/call_raw.rs @@ -564,7 +564,7 @@ mod tests { #[tokio::test] async fn test_state_overrides() { - let mut geth = Geth::new().spawn(); + let geth = Geth::new().spawn(); let provider = Provider::::try_from(geth.endpoint()).unwrap(); let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap(); @@ -574,9 +574,6 @@ mod tests { // Not enough ether to pay for the transaction let tx = TransactionRequest::pay(adr2, pay_amt).from(adr1).into(); - // dump the geth logs TODO REMOVE - println!("geth logs: {}", geth.dump_stderr().unwrap()); - // assert that overriding the sender's balance works let state = spoof::balance(adr1, pay_amt * 2); provider.call_raw(&tx).state(&state).await.expect("eth_call success"); From 9d7e23e199347b122e34b509d2df7ba4a7bebb49 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 29 Nov 2022 17:02:23 -0500 Subject: [PATCH 53/54] use unused port when authrpc port is not specified --- ethers-core/src/utils/geth.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 3ac611a1a..2fb3dc9bb 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -292,6 +292,7 @@ impl Geth { // geth uses stderr for its logs cmd.stderr(std::process::Stdio::piped()); let port = if let Some(port) = self.port { port } else { unused_port() }; + let authrpc_port = if let Some(port) = self.authrpc_port { port } else { unused_port() }; // Open the HTTP API cmd.arg("--http"); @@ -304,9 +305,7 @@ impl Geth { cmd.arg("--ws.api").arg(API); // Set the port for authenticated APIs - if let Some(authrpc_port) = self.authrpc_port { - cmd.arg("--authrpc.port").arg(authrpc_port.to_string()); - } + cmd.arg("--authrpc.port").arg(authrpc_port.to_string()); // use geth init to initialize the datadir if the genesis exists if let Some(genesis) = self.genesis { From 8ca864c309455e89a3d972a8f36b4daf000dddbb Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 29 Nov 2022 17:28:49 -0500 Subject: [PATCH 54/54] remove dump_stderr from GethInstance * did not work properly anyways --- ethers-core/src/utils/geth.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 2fb3dc9bb..657b8da34 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -95,20 +95,6 @@ impl GethInstance { } Err(GethInstanceError::Timeout("Timed out waiting for geth to add a peer".into())) } - - /// Dumps the unread stderr of the geth instance to a string. - pub fn dump_stderr(&mut self) -> Result { - let mut stderr = self.pid.stderr.as_mut().ok_or(GethInstanceError::NoStderr)?; - let mut err_reader = BufReader::new(&mut stderr); - let mut line = String::new(); - let mut output = String::new(); - - while err_reader.read_line(&mut line).map_err(GethInstanceError::ReadLineError)? > 0 { - output.push_str(&line); - line.clear(); - } - Ok(output) - } } impl Drop for GethInstance {