From f9a0542d492d7008648a7a4598bb35a7a7502c18 Mon Sep 17 00:00:00 2001 From: xermicus Date: Sat, 22 Mar 2025 19:41:24 +0100 Subject: [PATCH] the node interaction interface Signed-off-by: xermicus --- Cargo.lock | 44 ++++++++++-- Cargo.toml | 17 ++++- crates/compiler/src/solc.rs | 1 - crates/core/src/driver/compiler.rs | 2 +- crates/core/src/main.rs | 2 +- crates/node-interaction/Cargo.toml | 4 +- crates/node-interaction/src/lib.rs | 58 +++++---------- crates/node-interaction/src/tokio_runtime.rs | 76 ++++++++++++++++++++ crates/node-interaction/src/trace.rs | 58 +++++++++++++++ crates/node-interaction/src/transaction.rs | 29 +++++--- crates/node/Cargo.toml | 13 ++++ crates/node/src/lib.rs | 29 ++++++++ 12 files changed, 276 insertions(+), 57 deletions(-) create mode 100644 crates/node-interaction/src/tokio_runtime.rs create mode 100644 crates/node-interaction/src/trace.rs create mode 100644 crates/node/Cargo.toml create mode 100644 crates/node/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index b6c2705..bb68b73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -305,7 +305,9 @@ dependencies = [ "alloy-network-primitives", "alloy-primitives", "alloy-rpc-client", + "alloy-rpc-types-debug", "alloy-rpc-types-eth", + "alloy-rpc-types-trace", "alloy-sol-types", "alloy-transport", "alloy-transport-http", @@ -383,6 +385,7 @@ checksum = "9157deaec6ba2ad7854f16146e4cd60280e76593eed79fdcb06e0fa8b6c60f77" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", + "alloy-rpc-types-trace", "alloy-serde", "serde", ] @@ -398,6 +401,16 @@ dependencies = [ "alloy-serde", ] +[[package]] +name = "alloy-rpc-types-debug" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08b113a0087d226291b9768ed331818fa0b0744cc1207ae7c150687cf3fde1bd" +dependencies = [ + "alloy-primitives", + "serde", +] + [[package]] name = "alloy-rpc-types-eth" version = "0.12.6" @@ -418,6 +431,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "alloy-rpc-types-trace" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4747763aee39c1b0f5face79bde9be8932be05b2db7d8bdcebb93490f32c889c" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "alloy-serde" version = "0.12.6" @@ -1414,7 +1441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2864,6 +2891,14 @@ dependencies = [ "serde_json", ] +[[package]] +name = "revive-dt-node" +version = "0.1.0" +dependencies = [ + "alloy", + "anyhow", +] + [[package]] name = "revive-dt-node-interaction" version = "0.1.0" @@ -2873,6 +2908,7 @@ dependencies = [ "hex", "log", "once_cell", + "revive-dt-node", "serde_json", "tokio", ] @@ -3015,7 +3051,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3356,7 +3392,7 @@ dependencies = [ "cfg-if", "libc", "psm", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3490,7 +3526,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 24e1b94..2e831c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,11 @@ rust-version = "1.85.0" revive-dt-compiler = { version = "0.1.0", path = "crates/compiler" } 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" } revive-dt-node-interaction = { version = "0.1.0", path = "crates/node-interaction" } +revive-dt-node-pool = { version = "0.1.0", path = "crates/node-pool" } anyhow = "1.0" -alloy = { version = "0.12.6", default-features = false, features = [ "providers", "rpc-types", "json-abi", "reqwest", "std" ] } clap = { version = "4", features = ["derive"] } env_logger = "0.11.7" hex = "0.4.3" @@ -36,6 +37,20 @@ tokio = { version = "1", default-features = false, features = ["rt-multi-thread" # revive compiler revive-solc-json-interface = { git = "https://github.com/paritytech/revive", rev = "497dae2494dabe12d1af32d6d687122903cb2ada" } revive-common = { git = "https://github.com/paritytech/revive", rev = "497dae2494dabe12d1af32d6d687122903cb2ada" } +revive-differential = { git = "https://github.com/paritytech/revive", rev = "497dae2494dabe12d1af32d6d687122903cb2ada" } + +[workspace.dependencies.alloy] +version = "0.12.6" +default-features = false +features = [ + "providers", + "rpc-types", + "json-abi", + "reqwest", + "std", + "genesis", + "provider-debug-api" +] [profile.bench] inherits = "release" diff --git a/crates/compiler/src/solc.rs b/crates/compiler/src/solc.rs index 3d41c66..60bd9e9 100644 --- a/crates/compiler/src/solc.rs +++ b/crates/compiler/src/solc.rs @@ -34,7 +34,6 @@ impl SolidityCompiler for Solc { serde_json::to_writer(stdin, input)?; let output = child.wait_with_output()?.stdout; - Ok(serde_json::from_slice(&output)?) } diff --git a/crates/core/src/driver/compiler.rs b/crates/core/src/driver/compiler.rs index 5b717aa..65c2927 100644 --- a/crates/core/src/driver/compiler.rs +++ b/crates/core/src/driver/compiler.rs @@ -38,7 +38,7 @@ pub fn build_evm( let mut result = HashMap::new(); for mode in modes { let mut compiler = Compiler::::new().base_path(directory.display().to_string()); - for (_, file) in &sources { + for file in sources.values() { compiler = compiler.with_source(file)?; } diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 257c915..2d27598 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -21,7 +21,7 @@ fn main() -> anyhow::Result<()> { tests .par_iter() - .for_each(|metadata| match build_evm(&metadata) { + .for_each(|metadata| match build_evm(metadata) { Ok(_) => log::info!( "metadata {} compilation success", metadata.path.as_ref().unwrap().display() diff --git a/crates/node-interaction/Cargo.toml b/crates/node-interaction/Cargo.toml index 1ac60d0..a341469 100644 --- a/crates/node-interaction/Cargo.toml +++ b/crates/node-interaction/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revive-dt-node-interaction" -description = "send and trace transactions to EVM and PVM nodes" +description = "send and trace transactions to nodes" version.workspace = true authors.workspace = true license.workspace = true @@ -16,3 +16,5 @@ 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 8a5fdb5..c93a21e 100644 --- a/crates/node-interaction/src/lib.rs +++ b/crates/node-interaction/src/lib.rs @@ -1,45 +1,25 @@ -//! Implements helpers for node interactions using transactions. -//! -//! The alloy crate is convenient but requires running in a tokio runtime. -//! We contain any async rust right here. +//! This crate implements all node interactions. -use once_cell::sync::Lazy; -use std::sync::Mutex; -use std::thread; -use tokio::runtime::Runtime; -use tokio::sync::mpsc; -use transaction::Transaction; +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; -pub(crate) static TO_TOKIO: Lazy> = - Lazy::new(|| Mutex::new(TokioRuntime::spawn())); +/// An interface for all node interactions. +pub trait NodeInteraction: Node { + /// Execute the [TransactionRequest] and return a [TransactionReceipt]. + fn execute_transaction( + &self, + transaction_request: TransactionRequest, + ) -> anyhow::Result; -pub struct TokioRuntime { - pub transaction_sender: mpsc::Sender, -} - -impl TokioRuntime { - pub fn spawn() -> Self { - let rt = Runtime::new().expect("should be able to create the tokio runtime"); - let (transaction_sender, mut transaction_receiver) = mpsc::channel::(1024); - - thread::spawn(move || { - rt.block_on(async move { - while let Some(transaction) = transaction_receiver.recv().await { - tokio::task::spawn(async move { - let sender = transaction.receipt_sender.clone(); - let result = transaction.execute().await; - if let Err(error) = sender.send(result).await { - log::error!("failed to send transaction receipt: {error}"); - } - }) - .await - .expect("should alaways be able to spawn the tokio tasks"); - } - }); - }); - - Self { transaction_sender } - } + /// Trace the transaction in the [TransactionReceipt] and return a [GethTrace]. + fn trace_transaction( + &self, + transaction_receipt: TransactionReceipt, + ) -> anyhow::Result; } diff --git a/crates/node-interaction/src/tokio_runtime.rs b/crates/node-interaction/src/tokio_runtime.rs new file mode 100644 index 0000000..942d427 --- /dev/null +++ b/crates/node-interaction/src/tokio_runtime.rs @@ -0,0 +1,76 @@ +//! The alloy crate is convenient but requires a tokio runtime. +//! We contain any async rust right here. + +use once_cell::sync::Lazy; +use std::sync::Mutex; +use std::thread; +use tokio::runtime::Runtime; +use tokio::spawn; +use tokio::sync::mpsc; +use tokio::task::JoinError; + +use crate::trace::Trace; +use crate::transaction::Transaction; + +pub(crate) static TO_TOKIO: Lazy> = + Lazy::new(|| Mutex::new(TokioRuntime::spawn())); + +// Common interface for executing async node interactions from a non-async context. +pub(crate) trait AsyncNodeInteraction: Send + 'static { + type Output: Send + 'static; + + /// Any async calls the task needs to perform go here. + fn execute_async(self) -> impl std::future::Future + Send; + + /// Returns the interactions output sender. + fn output_sender(&self) -> mpsc::Sender; +} + +pub(crate) struct TokioRuntime { + pub(crate) transaction_sender: mpsc::Sender, + pub(crate) trace_sender: mpsc::Sender, +} + +impl TokioRuntime { + fn spawn() -> Self { + let rt = Runtime::new().expect("should be able to create the tokio runtime"); + let (transaction_sender, transaction_receiver) = mpsc::channel::(1024); + let (trace_sender, trace_receiver) = mpsc::channel::(1024); + + thread::spawn(move || { + rt.block_on(async move { + let transaction_task = spawn(interaction::(transaction_receiver)); + let trace_task = spawn(interaction::(trace_receiver)); + + if let Err(error) = transaction_task.await { + log::error!("tokio transaction task failed: {error}"); + } + if let Err(error) = trace_task.await { + log::error!("tokio trace transaction task failed: {error}"); + } + }); + }); + + Self { + transaction_sender, + trace_sender, + } + } +} + +async fn interaction( + mut receiver: mpsc::Receiver, +) -> Result<(), JoinError> { + while let Some(task) = receiver.recv().await { + spawn(async move { + let sender = task.output_sender(); + let result = task.execute_async().await; + if let Err(error) = sender.send(result).await { + log::error!("failed to send task output: {error}"); + } + }) + .await?; + } + + Ok(()) +} diff --git a/crates/node-interaction/src/trace.rs b/crates/node-interaction/src/trace.rs new file mode 100644 index 0000000..217fd06 --- /dev/null +++ b/crates/node-interaction/src/trace.rs @@ -0,0 +1,58 @@ +//! Trace transactions in a sync context. + +use alloy::primitives::TxHash; +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; +use crate::tokio_runtime::AsyncNodeInteraction; + +pub(crate) struct Trace { + transaction_hash: TxHash, + options: GethDebugTracingOptions, + geth_trace_sender: mpsc::Sender>, + connection_string: String, +} + +impl AsyncNodeInteraction for Trace { + type Output = anyhow::Result; + + async fn execute_async(self) -> Self::Output { + let provider = ProviderBuilder::new() + .connect(&self.connection_string) + .await?; + Ok(provider + .debug_trace_transaction(self.transaction_hash, self.options) + .await?) + } + + fn output_sender(&self) -> mpsc::Sender { + self.geth_trace_sender.clone() + } +} + +/// Trace the transaction in [TransactionReceipt] against the `node`, +/// using the provided [GethDebugTracingOptions]. +pub fn trace_transaction( + transaction_receipt: TransactionReceipt, + options: GethDebugTracingOptions, + node: &T, +) -> anyhow::Result { + let trace_sender = TO_TOKIO.lock().unwrap().trace_sender.clone(); + let (geth_trace_sender, mut geth_trace_receiver) = mpsc::channel(1); + + trace_sender.blocking_send(Trace { + transaction_hash: transaction_receipt.transaction_hash, + options, + geth_trace_sender, + connection_string: node.connection_string(), + })?; + + geth_trace_receiver + .blocking_recv() + .unwrap_or_else(|| anyhow::bail!("no receipt received")) +} diff --git a/crates/node-interaction/src/transaction.rs b/crates/node-interaction/src/transaction.rs index 844165d..49636d4 100644 --- a/crates/node-interaction/src/transaction.rs +++ b/crates/node-interaction/src/transaction.rs @@ -1,17 +1,23 @@ +//! Execute transactions in a sync context. + use alloy::providers::{Provider, ProviderBuilder}; use alloy::rpc::types::{TransactionReceipt, TransactionRequest}; +use revive_dt_node::Node; use tokio::sync::mpsc; use crate::TO_TOKIO; +use crate::tokio_runtime::AsyncNodeInteraction; -pub struct Transaction { - pub transaction_request: TransactionRequest, - pub receipt_sender: mpsc::Sender>, - pub connection_string: String, +pub(crate) struct Transaction { + transaction_request: TransactionRequest, + receipt_sender: mpsc::Sender>, + connection_string: String, } -impl Transaction { - pub async fn execute(self) -> anyhow::Result { +impl AsyncNodeInteraction for Transaction { + type Output = anyhow::Result; + + async fn execute_async(self) -> Self::Output { let provider = ProviderBuilder::new() .connect(&self.connection_string) .await?; @@ -21,11 +27,16 @@ impl Transaction { .get_receipt() .await?) } + + fn output_sender(&self) -> mpsc::Sender { + self.receipt_sender.clone() + } } -pub fn execute_transaction( +/// Execute the [TransactionRequest] against the `node`. +pub fn execute_transaction( transaction_request: TransactionRequest, - connection_string: String, + node: &T, ) -> anyhow::Result { let request_sender = TO_TOKIO.lock().unwrap().transaction_sender.clone(); let (receipt_sender, mut receipt_receiver) = mpsc::channel(1); @@ -33,7 +44,7 @@ pub fn execute_transaction( request_sender.blocking_send(Transaction { transaction_request, receipt_sender, - connection_string, + connection_string: node.connection_string(), })?; receipt_receiver diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml new file mode 100644 index 0000000..520ba87 --- /dev/null +++ b/crates/node/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "revive-dt-node" +description = "abstraction over blockchain nodes" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +anyhow = { workspace = true } +alloy = { workspace = true } diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs new file mode 100644 index 0000000..4fab692 --- /dev/null +++ b/crates/node/src/lib.rs @@ -0,0 +1,29 @@ +//! An abstract interface for testing nodes. + +use alloy::{ + genesis::Genesis, + rpc::types::{TransactionReceipt, TransactionRequest, trace::parity::StateDiff}, +}; + +pub trait Node { + /// Configures the node with the given genesis configuration. + fn with_genesis(&mut self, genesis: &Genesis) -> anyhow::Result<&mut Self>; + + /// Spawns the node, blocking until it's ready to accept transactions. + fn spawn(&mut self) -> anyhow::Result<&mut Self>; + + /// Prune the node, 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; +}