diff --git a/Cargo.lock b/Cargo.lock index bb68b73..d925722 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1441,7 +1441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2862,16 +2862,23 @@ dependencies = [ "serde_json", ] +[[package]] +name = "revive-dt-config" +version = "0.1.0" +dependencies = [ + "clap", +] + [[package]] name = "revive-dt-core" version = "0.1.0" dependencies = [ "anyhow", - "clap", "env_logger", "log", "rayon", "revive-dt-compiler", + "revive-dt-config", "revive-dt-format", "revive-solc-json-interface", "semver 1.0.26", @@ -2897,6 +2904,9 @@ version = "0.1.0" dependencies = [ "alloy", "anyhow", + "revive-dt-config", + "revive-dt-node-interaction", + "serde_json", ] [[package]] @@ -2908,7 +2918,6 @@ dependencies = [ "hex", "log", "once_cell", - "revive-dt-node", "serde_json", "tokio", ] @@ -3051,7 +3060,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3392,7 +3401,7 @@ dependencies = [ "cfg-if", "libc", "psm", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3526,7 +3535,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2e831c0..7f18cea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ rust-version = "1.85.0" [workspace.dependencies] revive-dt-compiler = { version = "0.1.0", path = "crates/compiler" } +revive-dt-config = { version = "0.1.0", path = "crates/config" } revive-dt-core = { version = "0.1.0", path = "crates/core" } revive-dt-format = { version = "0.1.0", path = "crates/format" } revive-dt-node = { version = "0.1.0", path = "crates/node" } @@ -43,13 +44,13 @@ revive-differential = { git = "https://github.com/paritytech/revive", rev = "497 version = "0.12.6" default-features = false features = [ - "providers", - "rpc-types", "json-abi", - "reqwest", - "std", "genesis", - "provider-debug-api" + "providers", + "provider-debug-api", + "reqwest", + "rpc-types", + "std", ] [profile.bench] diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml new file mode 100644 index 0000000..9251a3f --- /dev/null +++ b/crates/config/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "revive-dt-config" +description = "global configuration for the revive differential tester" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +clap = { workspace = true } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs new file mode 100644 index 0000000..8e613b8 --- /dev/null +++ b/crates/config/src/lib.rs @@ -0,0 +1,39 @@ +//! The global configuration used accross all revive differential testing crates. + +use std::{path::PathBuf, sync::OnceLock}; + +use clap::Parser; + +static ARGUMENTS: OnceLock = OnceLock::new(); + +/// Get the command line arguments. +pub fn get_args() -> &'static Arguments { + ARGUMENTS.get_or_init(Arguments::parse) +} + +#[derive(Debug, Parser, Clone)] +#[command( + name = "revive compiler differential tester utility", + arg_required_else_help = true +)] +pub struct Arguments { + /// The path to the `resolc` executable to be tested. + /// + /// By default it uses the `resolc` binary found in `$PATH`. + #[arg(long = "resolc", short, default_value = "resolc")] + pub resolc: PathBuf, + + /// A list of test corpus JSON files to be tested. + #[arg(long = "corpus", short)] + pub corpus: Vec, + + /// A place to store temporary artifacts during test execution. + #[arg(long = "workdir", short)] + pub working_directory: PathBuf, + + /// The path to the `geth` executable. + /// + /// By default it uses `geth` binary found in `$PATH`. + #[arg(short, long = "geth", default_value = "geth")] + pub geth: PathBuf, +} diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 3012d76..0f01e7f 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -16,9 +16,9 @@ path = "src/main.rs" revive-dt-compiler = { workspace = true } revive-dt-format = { workspace = true } revive-solc-json-interface = { workspace = true } +revive-dt-config = { workspace = true } anyhow = { workspace = true } -clap = { workspace = true } log = { workspace = true } env_logger = { workspace = true } rayon = { workspace = true } diff --git a/crates/core/src/arguments.rs b/crates/core/src/arguments.rs deleted file mode 100644 index db1893e..0000000 --- a/crates/core/src/arguments.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::path::PathBuf; - -use clap::Parser; - -#[derive(Debug, Parser)] -#[command(name = "The PolkaVM Solidity compiler", arg_required_else_help = true)] -pub struct Arguments { - /// The path where the `resolc` executable to be tested is found at. - /// - /// By default it uses the `resolc` found in `$PATH` - #[arg(long = "resolc")] - pub resolc: Option, - - /// A list of test corpus JSON files to be tested. - #[arg(long = "corpus")] - pub corpus: Vec, -} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index de222cb..22ca60d 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -3,5 +3,4 @@ //! This crate defines the testing configuration and //! provides a helper utilty to execute tests. -pub mod arguments; pub mod driver; diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 2d27598..6598ee6 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -1,17 +1,15 @@ use std::collections::BTreeSet; -use clap::Parser; - use rayon::prelude::*; -use revive_dt_core::{arguments::Arguments, driver::compiler::build_evm}; + +use revive_dt_config::*; +use revive_dt_core::driver::compiler::build_evm; use revive_dt_format::corpus::Corpus; fn main() -> anyhow::Result<()> { env_logger::init(); - let args = Arguments::try_parse()?; - - for path in args.corpus.iter().collect::>() { + for path in get_args().corpus.iter().collect::>() { log::trace!("attempting corpus {path:?}"); let corpus = Corpus::try_from_path(path)?; log::info!("found corpus: {corpus:?}"); diff --git a/crates/node-interaction/Cargo.toml b/crates/node-interaction/Cargo.toml index a341469..7029edc 100644 --- a/crates/node-interaction/Cargo.toml +++ b/crates/node-interaction/Cargo.toml @@ -16,5 +16,3 @@ log = { workspace = true } once_cell = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } - -revive-dt-node = { workspace = true } diff --git a/crates/node-interaction/src/lib.rs b/crates/node-interaction/src/lib.rs index c93a21e..d612178 100644 --- a/crates/node-interaction/src/lib.rs +++ b/crates/node-interaction/src/lib.rs @@ -2,15 +2,14 @@ use alloy::rpc::types::trace::geth::GethTrace; use alloy::rpc::types::{TransactionReceipt, TransactionRequest}; -use revive_dt_node::Node; use tokio_runtime::TO_TOKIO; mod tokio_runtime; pub mod trace; pub mod transaction; -/// An interface for all node interactions. -pub trait NodeInteraction: Node { +/// An interface for all interactions with Ethereum compatible nodes. +pub trait EthereumNode { /// Execute the [TransactionRequest] and return a [TransactionReceipt]. fn execute_transaction( &self, diff --git a/crates/node-interaction/src/trace.rs b/crates/node-interaction/src/trace.rs index 217fd06..0bbfab1 100644 --- a/crates/node-interaction/src/trace.rs +++ b/crates/node-interaction/src/trace.rs @@ -5,7 +5,6 @@ use alloy::providers::ProviderBuilder; use alloy::providers::ext::DebugApi; use alloy::rpc::types::TransactionReceipt; use alloy::rpc::types::trace::geth::{GethDebugTracingOptions, GethTrace}; -use revive_dt_node::Node; use tokio::sync::mpsc; use crate::TO_TOKIO; @@ -37,10 +36,10 @@ impl AsyncNodeInteraction for Trace { /// Trace the transaction in [TransactionReceipt] against the `node`, /// using the provided [GethDebugTracingOptions]. -pub fn trace_transaction( +pub fn trace_transaction( transaction_receipt: TransactionReceipt, options: GethDebugTracingOptions, - node: &T, + connection_string: String, ) -> anyhow::Result { let trace_sender = TO_TOKIO.lock().unwrap().trace_sender.clone(); let (geth_trace_sender, mut geth_trace_receiver) = mpsc::channel(1); @@ -49,7 +48,7 @@ pub fn trace_transaction( transaction_hash: transaction_receipt.transaction_hash, options, geth_trace_sender, - connection_string: node.connection_string(), + connection_string, })?; geth_trace_receiver diff --git a/crates/node-interaction/src/transaction.rs b/crates/node-interaction/src/transaction.rs index 49636d4..1e83f09 100644 --- a/crates/node-interaction/src/transaction.rs +++ b/crates/node-interaction/src/transaction.rs @@ -2,7 +2,6 @@ use alloy::providers::{Provider, ProviderBuilder}; use alloy::rpc::types::{TransactionReceipt, TransactionRequest}; -use revive_dt_node::Node; use tokio::sync::mpsc; use crate::TO_TOKIO; @@ -34,9 +33,9 @@ impl AsyncNodeInteraction for Transaction { } /// Execute the [TransactionRequest] against the `node`. -pub fn execute_transaction( +pub fn execute_transaction( transaction_request: TransactionRequest, - node: &T, + connection_string: String, ) -> anyhow::Result { let request_sender = TO_TOKIO.lock().unwrap().transaction_sender.clone(); let (receipt_sender, mut receipt_receiver) = mpsc::channel(1); @@ -44,7 +43,7 @@ pub fn execute_transaction( request_sender.blocking_send(Transaction { transaction_request, receipt_sender, - connection_string: node.connection_string(), + connection_string, })?; receipt_receiver diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 520ba87..67c509d 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -11,3 +11,7 @@ rust-version.workspace = true [dependencies] anyhow = { workspace = true } alloy = { workspace = true } +serde_json = { workspace = true } + +revive-dt-node-interaction = { workspace = true } +revive-dt-config = { workspace = true } diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs new file mode 100644 index 0000000..f771340 --- /dev/null +++ b/crates/node/src/geth.rs @@ -0,0 +1,125 @@ +//! The go-ethereum node implementation. + +use std::{ + fs::{File, create_dir, exists}, + path::PathBuf, + process::{Command, Stdio}, + sync::atomic::{AtomicU32, Ordering}, +}; + +use alloy::{ + genesis::Genesis, + rpc::types::trace::geth::{DiffMode, PreStateFrame}, +}; +use revive_dt_config::get_args; +use revive_dt_node_interaction::{ + EthereumNode, trace::trace_transaction, transaction::execute_transaction, +}; + +use crate::Node; + +static NODE_COUNT: AtomicU32 = AtomicU32::new(0); + +#[derive(Debug)] +pub struct Instance { + connection_string: String, + directory: PathBuf, + geth: PathBuf, + id: u32, +} + +impl Instance { + pub fn new() -> anyhow::Result { + let args = get_args(); + + let geth_directory = args.working_directory.join("geth"); + if !exists(&geth_directory)? { + create_dir(&geth_directory)?; + } + + let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst); + let directory = geth_directory.join(id.to_string()); + + let connection_string = directory.join("geth.ipc").display().to_string(); + + Ok(Self { + connection_string, + directory, + geth: args.geth.clone(), + id, + }) + } +} + +impl Instance { + /// Call `init` on the node to configure it's genesis. + fn init(&mut self, genesis: Genesis) -> anyhow::Result<()> { + let genesis_path = self.directory.join("genesis.json"); + + let mut file = File::create(&genesis_path)?; + serde_json::to_writer_pretty(&mut file, &genesis)?; + + if !Command::new(&self.geth) + .arg("--datadir") + .arg(self.directory.join("data")) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .spawn()? + .wait()? + .success() + { + anyhow::bail!("failed to initialize geth node {:?}", &self); + } + + Ok(()) + } +} + +impl EthereumNode for Instance { + fn execute_transaction( + &self, + transaction_request: alloy::rpc::types::TransactionRequest, + ) -> anyhow::Result { + execute_transaction(transaction_request, self.connection_string()) + } + + fn trace_transaction( + &self, + transaction_receipt: alloy::rpc::types::TransactionReceipt, + ) -> anyhow::Result { + trace_transaction( + transaction_receipt, + Default::default(), + self.connection_string(), + ) + } +} + +impl Node for Instance { + fn connection_string(&self) -> String { + self.connection_string.clone() + } + + fn shutdown(self) -> anyhow::Result<()> { + Ok(()) + } + + fn spawn(&mut self, genesis: Genesis) -> anyhow::Result<&mut Self> { + self.init(genesis)?; + + Ok(self) + } + + fn state_diff( + &self, + transaction: alloy::rpc::types::TransactionReceipt, + ) -> anyhow::Result { + match self + .trace_transaction(transaction)? + .try_into_pre_state_frame()? + { + PreStateFrame::Diff(diff) => Ok(diff), + _ => anyhow::bail!("expected a diff mode trace"), + } + } +} diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index 4fab692..6532546 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -1,29 +1,28 @@ -//! An abstract interface for testing nodes. +//! This crate implements the testing nodes. use alloy::{ genesis::Genesis, - rpc::types::{TransactionReceipt, TransactionRequest, trace::parity::StateDiff}, + rpc::types::{TransactionReceipt, trace::geth::DiffMode}, }; +use revive_dt_node_interaction::EthereumNode; -pub trait Node { - /// Configures the node with the given genesis configuration. - fn with_genesis(&mut self, genesis: &Genesis) -> anyhow::Result<&mut Self>; +pub mod geth; - /// Spawns the node, blocking until it's ready to accept transactions. - fn spawn(&mut self) -> anyhow::Result<&mut Self>; +/// An abstract interface for testing nodes. +pub trait Node: EthereumNode { + /// Spawns a node configured according to the [Genesis]. + /// + /// Blocking until it's ready to accept transactions. + fn spawn(&mut self, genesis: Genesis) -> anyhow::Result<&mut Self>; - /// Prune the node, blocking until it's completely stopped. + /// Prune the node instance and related data. + /// + /// Blocking until it's completely stopped. fn shutdown(self) -> anyhow::Result<()>; /// Returns the nodes connection string. fn connection_string(&self) -> String; - /// Execute the [TransactionRequest], blocking until the transaction is mined. - fn execute_transaction( - &self, - transaction: &TransactionRequest, - ) -> anyhow::Result; - /// Returns the state diff of the transaction hash in the [TransactionReceipt]. - fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result; + fn state_diff(&self, transaction: TransactionReceipt) -> anyhow::Result; }