Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve Reth bindings and fix debug_traceCallMany and trace_callMany methods #1076

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3b90d48
should skip serializing block_override if none
zerosnacks Jul 19, 2024
8c45751
feat: Add call_many (#1085)
DoTheBestToGetTheBest Jul 22, 2024
3ecfc3d
Merge branch 'main' into zerosnacks/fix-all-many-methods-rpc
zerosnacks Jul 22, 2024
a3ca720
merge in main
zerosnacks Sep 9, 2024
b860cbb
add tempdir utility
zerosnacks Sep 9, 2024
c684b70
clean up tests
zerosnacks Sep 9, 2024
d03af40
add debug_trace_call_many test
zerosnacks Sep 9, 2024
a9f4e76
fix tests
zerosnacks Sep 9, 2024
d877ce7
add trace_call
zerosnacks Sep 9, 2024
5783ab1
seemingly correct api results in incorrect api response from Reth
zerosnacks Sep 9, 2024
fb8a29d
simplify types, fix request formatting
zerosnacks Sep 11, 2024
8a0901e
revert type changes to keep compatibility with RpcWithBlock optional …
zerosnacks Sep 11, 2024
96062a2
format output
zerosnacks Sep 11, 2024
03fd742
clean up, add random_instance for reduced flakiness
zerosnacks Sep 11, 2024
bbc5ebc
fix clippy
zerosnacks Sep 11, 2024
157860c
add back utils namespace
zerosnacks Sep 11, 2024
c83dc57
merge in main
zerosnacks Sep 11, 2024
c83cba8
Merge branch 'main' into zerosnacks/fix-all-many-methods-rpc
zerosnacks Sep 11, 2024
072cc26
Update crates/rpc-types-eth/src/call.rs
zerosnacks Sep 11, 2024
4f31750
re-export TraceCallList
zerosnacks Sep 11, 2024
8c896d2
Merge branch 'zerosnacks/fix-all-many-methods-rpc' of github.com:allo…
zerosnacks Sep 11, 2024
0dc8adc
add back `init_tracing`
zerosnacks Sep 11, 2024
f3f6340
undo unrelated test_* prefix changes in anvil tests
zerosnacks Sep 11, 2024
7f76e24
clean up
zerosnacks Sep 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions crates/node-bindings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ workspace = true
alloy-primitives = { workspace = true, features = ["std", "k256", "serde"] }
alloy-genesis.workspace = true
k256.workspace = true
rand.workspace = true
serde_json = { workspace = true, features = ["std"] }
tempfile.workspace = true
thiserror.workspace = true
tracing.workspace = true
url.workspace = true

[dev-dependencies]
rand.workspace = true
3 changes: 1 addition & 2 deletions crates/node-bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ pub use nodes::{
mod node;
pub use node::*;

mod utils;
use utils::*;
pub mod utils;

/// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei
pub const WEI_IN_ETHER: U256 = U256::from_limbs([0x0de0b6b3a7640000, 0x0, 0x0, 0x0]);
Expand Down
46 changes: 23 additions & 23 deletions crates/node-bindings/src/node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Node-related types and constants.

use alloy_primitives::hex;
use std::time::Duration;
use thiserror::Error;

Expand All @@ -9,31 +10,9 @@ pub const NODE_STARTUP_TIMEOUT: Duration = Duration::from_secs(10);
/// Timeout for waiting for the node to add a peer.
pub const NODE_DIAL_LOOP_TIMEOUT: Duration = Duration::from_secs(20);

/// Errors that can occur when working with a node instance.
#[derive(Debug)]
pub enum NodeInstanceError {
/// Timed out waiting for a message from node's stderr.
Timeout(String),

/// A line could not be read from the node's stderr.
ReadLineError(std::io::Error),

/// The child node process's stderr was not captured.
NoStderr,

/// The child node process's stdout was not captured.
NoStdout,
}

/// Errors that can occur when working with the node.
/// Errors that can occur when working with the node instance.
#[derive(Debug, Error)]
pub enum NodeError {
/// The chain id was not set.
#[error("the chain ID was not set")]
ChainIdNotSet,
/// Could not create the data directory.
#[error("could not create directory: {0}")]
CreateDirError(std::io::Error),
/// No stderr was captured from the child process.
#[error("no stderr was captured from the process")]
NoStderr,
Expand All @@ -49,6 +28,14 @@ pub enum NodeError {
/// A line could not be read from the node stderr.
#[error("could not read line from node stderr: {0}")]
ReadLineError(std::io::Error),

/// The chain id was not set.
#[error("the chain ID was not set")]
ChainIdNotSet,
/// Could not create the data directory.
#[error("could not create directory: {0}")]
CreateDirError(std::io::Error),

/// Genesis error
#[error("genesis error occurred: {0}")]
GenesisError(String),
Expand All @@ -65,4 +52,17 @@ pub enum NodeError {
/// Clique private key error
#[error("clique address error: {0}")]
CliqueAddressError(String),

/// The private key could not be parsed.
#[error("could not parse private key")]
ParsePrivateKeyError,
/// An error occurred while deserializing a private key.
#[error("could not deserialize private key from bytes")]
DeserializePrivateKeyError,
/// An error occurred while parsing a hex string.
#[error(transparent)]
FromHexError(#[from] hex::FromHexError),
/// No keys available this node instance.
#[error("no keys available in this node instance")]
NoKeysAvailable,
}
62 changes: 13 additions & 49 deletions crates/node-bindings/src/nodes/anvil.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Utilities for launching an Anvil instance.

use crate::NodeError;
use alloy_primitives::{hex, Address, ChainId};
use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
use k256::{ecdsa::SigningKey, SecretKey};
use std::{
io::{BufRead, BufReader},
net::SocketAddr,
Expand All @@ -10,7 +11,6 @@ use std::{
str::FromStr,
time::{Duration, Instant},
};
use thiserror::Error;
use url::Url;

/// How long we will wait for anvil to indicate that it is ready.
Expand All @@ -22,7 +22,7 @@ const ANVIL_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
#[derive(Debug)]
pub struct AnvilInstance {
child: Child,
private_keys: Vec<K256SecretKey>,
private_keys: Vec<SecretKey>,
addresses: Vec<Address>,
port: u16,
chain_id: Option<ChainId>,
Expand All @@ -40,7 +40,7 @@ impl AnvilInstance {
}

/// Returns the private keys used to instantiate this instance
pub fn keys(&self) -> &[K256SecretKey] {
pub fn keys(&self) -> &[SecretKey] {
&self.private_keys
}

Expand Down Expand Up @@ -89,42 +89,6 @@ impl Drop for AnvilInstance {
}
}

/// Errors that can occur when working with the [`Anvil`].
#[derive(Debug, Error)]
pub enum AnvilError {
/// Spawning the anvil process failed.
#[error("could not start anvil: {0}")]
SpawnError(std::io::Error),

/// Timed out waiting for a message from anvil's stderr.
#[error("timed out waiting for anvil to spawn; is anvil installed?")]
Timeout,

/// A line could not be read from the geth stderr.
#[error("could not read line from anvil stderr: {0}")]
ReadLineError(std::io::Error),

/// The child anvil process's stderr was not captured.
#[error("could not get stderr for anvil child process")]
NoStderr,

/// The private key could not be parsed.
#[error("could not parse private key")]
ParsePrivateKeyError,

/// An error occurred while deserializing a private key.
#[error("could not deserialize private key from bytes")]
DeserializePrivateKeyError,

/// An error occurred while parsing a hex string.
#[error(transparent)]
FromHexError(#[from] hex::FromHexError),

/// No keys available in anvil instance.
#[error("no keys available in anvil instance")]
NoKeysAvailable,
}

/// Builder for launching `anvil`.
///
/// # Panics
Expand Down Expand Up @@ -288,7 +252,7 @@ impl Anvil {
}

/// Consumes the builder and spawns `anvil`. If spawning fails, returns an error.
pub fn try_spawn(self) -> Result<AnvilInstance, AnvilError> {
pub fn try_spawn(self) -> Result<AnvilInstance, NodeError> {
let mut cmd = self.program.as_ref().map_or_else(|| Command::new("anvil"), Command::new);
cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
let mut port = self.port.unwrap_or_default();
Expand Down Expand Up @@ -316,9 +280,9 @@ impl Anvil {

cmd.args(self.args);

let mut child = cmd.spawn().map_err(AnvilError::SpawnError)?;
let mut child = cmd.spawn().map_err(NodeError::SpawnError)?;

let stdout = child.stdout.take().ok_or(AnvilError::NoStderr)?;
let stdout = child.stdout.take().ok_or(NodeError::NoStderr)?;

let start = Instant::now();
let mut reader = BufReader::new(stdout);
Expand All @@ -331,11 +295,11 @@ impl Anvil {
if start + Duration::from_millis(self.timeout.unwrap_or(ANVIL_STARTUP_TIMEOUT_MILLIS))
<= Instant::now()
{
return Err(AnvilError::Timeout);
return Err(NodeError::Timeout);
}

let mut line = String::new();
reader.read_line(&mut line).map_err(AnvilError::ReadLineError)?;
reader.read_line(&mut line).map_err(NodeError::ReadLineError)?;
trace!(target: "anvil", line);
if let Some(addr) = line.strip_prefix("Listening on") {
// <Listening on 127.0.0.1:8545>
Expand All @@ -352,10 +316,10 @@ impl Anvil {

if is_private_key && line.starts_with('(') {
let key_str =
line.split("0x").last().ok_or(AnvilError::ParsePrivateKeyError)?.trim();
let key_hex = hex::decode(key_str).map_err(AnvilError::FromHexError)?;
let key = K256SecretKey::from_bytes((&key_hex[..]).into())
.map_err(|_| AnvilError::DeserializePrivateKeyError)?;
line.split("0x").last().ok_or(NodeError::ParsePrivateKeyError)?.trim();
let key_hex = hex::decode(key_str).map_err(NodeError::FromHexError)?;
let key = SecretKey::from_bytes((&key_hex[..]).into())
.map_err(|_| NodeError::DeserializePrivateKeyError)?;
addresses.push(Address::from_public_key(SigningKey::from(&key).verifying_key()));
private_keys.push(key);
}
Expand Down
43 changes: 15 additions & 28 deletions crates/node-bindings/src/nodes/geth.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Utilities for launching a Geth dev-mode instance.

use crate::{
extract_endpoint, extract_value, unused_port, NodeError, NodeInstanceError,
NODE_DIAL_LOOP_TIMEOUT, NODE_STARTUP_TIMEOUT,
utils::{extract_endpoint, extract_value, unused_port},
NodeError, NODE_DIAL_LOOP_TIMEOUT, NODE_STARTUP_TIMEOUT,
};
use alloy_genesis::{CliqueConfig, Genesis};
use alloy_primitives::Address;
Expand Down Expand Up @@ -139,30 +139,30 @@ impl GethInstance {
///
/// This leaves a `None` in its place, so calling methods that require a stderr to be present
/// will fail if called after this.
pub fn stderr(&mut self) -> Result<ChildStderr, NodeInstanceError> {
self.pid.stderr.take().ok_or(NodeInstanceError::NoStderr)
pub fn stderr(&mut self) -> Result<ChildStderr, NodeError> {
self.pid.stderr.take().ok_or(NodeError::NoStderr)
}

/// Blocks until geth adds the specified peer, using 20s as the timeout.
///
/// Requires the stderr to be present in the `GethInstance`.
pub fn wait_to_add_peer(&mut self, id: &str) -> Result<(), NodeInstanceError> {
let mut stderr = self.pid.stderr.as_mut().ok_or(NodeInstanceError::NoStderr)?;
pub fn wait_to_add_peer(&mut self, id: &str) -> Result<(), NodeError> {
let mut stderr = self.pid.stderr.as_mut().ok_or(NodeError::NoStderr)?;
let mut err_reader = BufReader::new(&mut stderr);
let mut line = String::new();
let start = Instant::now();

while start.elapsed() < NODE_DIAL_LOOP_TIMEOUT {
line.clear();
err_reader.read_line(&mut line).map_err(NodeInstanceError::ReadLineError)?;
err_reader.read_line(&mut line).map_err(NodeError::ReadLineError)?;

// geth ids are truncated
let truncated_id = if id.len() > 16 { &id[..16] } else { id };
if line.contains("Adding p2p peer") && line.contains(truncated_id) {
return Ok(());
}
}
Err(NodeInstanceError::Timeout("Timed out waiting for geth to add a peer".into()))
Err(NodeError::Timeout)
}
}

Expand Down Expand Up @@ -622,33 +622,20 @@ impl Geth {
// These tests should use a different datadir for each `geth` spawned.
#[cfg(test)]
mod tests {

use super::*;
use std::path::Path;
use crate::utils::run_with_tempdir_sync;

#[test]
fn port_0() {
run_with_tempdir(|_| {
run_with_tempdir_sync("geth-test-", |_| {
let _geth = Geth::new().disable_discovery().port(0u16).spawn();
});
}

/// Allows running tests with a temporary directory, which is cleaned up after the function is
/// called.
///
/// Helps with tests that spawn a helper instance, which has to be dropped before the temporary
/// directory is cleaned up.
#[track_caller]
fn run_with_tempdir(f: impl Fn(&Path)) {
let temp_dir = tempfile::tempdir().unwrap();
let temp_dir_path = temp_dir.path();
f(temp_dir_path);
#[cfg(not(windows))]
temp_dir.close().unwrap();
}

#[test]
fn p2p_port() {
run_with_tempdir(|temp_dir_path| {
run_with_tempdir_sync("geth-test-", |temp_dir_path| {
let geth = Geth::new().disable_discovery().data_dir(temp_dir_path).spawn();
let p2p_port = geth.p2p_port();
assert!(p2p_port.is_some());
Expand All @@ -657,7 +644,7 @@ mod tests {

#[test]
fn explicit_p2p_port() {
run_with_tempdir(|temp_dir_path| {
run_with_tempdir_sync("geth-test-", |temp_dir_path| {
// if a p2p port is explicitly set, it should be used
let geth = Geth::new().p2p_port(1234).data_dir(temp_dir_path).spawn();
let p2p_port = geth.p2p_port();
Expand All @@ -667,7 +654,7 @@ mod tests {

#[test]
fn dev_mode() {
run_with_tempdir(|temp_dir_path| {
run_with_tempdir_sync("geth-test-", |temp_dir_path| {
// dev mode should not have a p2p port, and dev should be the default
let geth = Geth::new().data_dir(temp_dir_path).spawn();
let p2p_port = geth.p2p_port();
Expand All @@ -679,7 +666,7 @@ mod tests {
#[ignore = "fails on geth >=1.14"]
#[allow(deprecated)]
fn clique_correctly_configured() {
run_with_tempdir(|temp_dir_path| {
run_with_tempdir_sync("geth-test-", |temp_dir_path| {
let private_key = SigningKey::random(&mut rand::thread_rng());
let geth = Geth::new()
.set_clique_private_key(private_key)
Expand Down
Loading