make node interactions generic

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
Cyrill Leutwiler
2025-03-31 09:50:55 +02:00
parent 3edd72850f
commit ea17166448
10 changed files with 112 additions and 111 deletions
-2
View File
@@ -6,8 +6,6 @@ use std::{
process::{Command, Stdio}, process::{Command, Stdio},
}; };
use semver::Version;
use crate::{CompilerInput, CompilerOutput, SolidityCompiler}; use crate::{CompilerInput, CompilerOutput, SolidityCompiler};
pub struct Solc { pub struct Solc {
+2 -5
View File
@@ -1,11 +1,8 @@
//! The global configuration used accross all revive differential testing crates. //! The global configuration used accross all revive differential testing crates.
use std::{ use std::path::{Path, PathBuf};
env,
path::{Path, PathBuf},
};
use clap::{Arg, Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use semver::Version; use semver::Version;
use temp_dir::TempDir; use temp_dir::TempDir;
+5 -8
View File
@@ -6,11 +6,7 @@ use alloy::{
}; };
use revive_dt_compiler::{Compiler, CompilerInput, SolidityCompiler}; use revive_dt_compiler::{Compiler, CompilerInput, SolidityCompiler};
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use revive_dt_format::{ use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode};
input::Input,
metadata::Metadata,
mode::{Mode, SolcMode},
};
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use revive_dt_solc_binaries::download_solc; use revive_dt_solc_binaries::download_solc;
use revive_solc_json_interface::SolcStandardJsonOutput; use revive_solc_json_interface::SolcStandardJsonOutput;
@@ -74,7 +70,8 @@ where
&self.deployed_contracts, &self.deployed_contracts,
)?)?; )?)?;
dbg!(&receipt); dbg!(&receipt);
node.trace_transaction(receipt) //node.trace_transaction(receipt)
todo!()
} }
} }
@@ -114,8 +111,8 @@ where
for case in &self.metadata.cases { for case in &self.metadata.cases {
for input in &case.inputs { for input in &case.inputs {
let expected = leader_state.execute_input(input, self.leader_node)?; let _ = leader_state.execute_input(input, self.leader_node)?;
let received = follower_state.execute_input(input, self.follower_node)?; let _ = follower_state.execute_input(input, self.follower_node)?;
} }
} }
} }
+1 -1
View File
@@ -5,7 +5,7 @@ use rayon::{ThreadPoolBuilder, prelude::*};
use revive_dt_config::*; use revive_dt_config::*;
use revive_dt_core::{ use revive_dt_core::{
Geth, Kitchensink, Geth,
driver::{Driver, State}, driver::{Driver, State},
}; };
use revive_dt_format::{corpus::Corpus, metadata::Metadata}; use revive_dt_format::{corpus::Corpus, metadata::Metadata};
+1 -1
View File
@@ -135,7 +135,7 @@ impl Input {
}; };
Ok(TransactionRequest::default() Ok(TransactionRequest::default()
.with_from(self.caller.clone()) .with_from(self.caller)
.with_to(to) .with_to(to)
.with_nonce(nonce) .with_nonce(nonce)
.with_chain_id(chain_id) .with_chain_id(chain_id)
+22 -19
View File
@@ -1,12 +1,13 @@
//! The alloy crate is convenient but requires a tokio runtime. //! The alloy crate __requires__ a tokio runtime.
//! We contain any async rust right here. //! We contain any async rust right here.
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::pin::Pin;
use std::sync::Mutex; use std::sync::Mutex;
use std::thread; use std::thread;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tokio::spawn; use tokio::spawn;
use tokio::sync::mpsc; use tokio::sync::{mpsc, oneshot};
use tokio::task::JoinError; use tokio::task::JoinError;
use crate::trace::Trace; use crate::trace::Trace;
@@ -15,15 +16,18 @@ use crate::transaction::Transaction;
pub(crate) static TO_TOKIO: Lazy<Mutex<TokioRuntime>> = pub(crate) static TO_TOKIO: Lazy<Mutex<TokioRuntime>> =
Lazy::new(|| Mutex::new(TokioRuntime::spawn())); Lazy::new(|| Mutex::new(TokioRuntime::spawn()));
// Common interface for executing async node interactions from a non-async context. /// Common interface for executing async node interactions from a non-async context.
#[allow(clippy::type_complexity)]
pub(crate) trait AsyncNodeInteraction: Send + 'static { pub(crate) trait AsyncNodeInteraction: Send + 'static {
type Output: Send + 'static; type Output: Send;
/// Any async calls the task needs to perform go here. //// Returns the task and the output sender.
fn execute_async(self) -> impl std::future::Future<Output = Self::Output> + Send; fn split(
self,
/// Returns the interactions output sender. ) -> (
fn output_sender(&self) -> mpsc::Sender<Self::Output>; Pin<Box<dyn Future<Output = Self::Output> + Send>>,
oneshot::Sender<Self::Output>,
);
} }
pub(crate) struct TokioRuntime { pub(crate) struct TokioRuntime {
@@ -58,18 +62,17 @@ impl TokioRuntime {
} }
} }
async fn interaction<T: AsyncNodeInteraction>( async fn interaction<T>(mut receiver: mpsc::Receiver<T>) -> Result<(), JoinError>
mut receiver: mpsc::Receiver<T>, where
) -> Result<(), JoinError> { T: AsyncNodeInteraction,
{
while let Some(task) = receiver.recv().await { while let Some(task) = receiver.recv().await {
spawn(async move { spawn(async move {
let sender = task.output_sender(); let (task, sender) = task.split();
let result = task.execute_async().await; sender
if let Err(error) = sender.send(result).await { .send(task.await)
log::error!("failed to send task output: {error}"); .unwrap_or_else(|_| panic!("failed to send task output"));
} });
})
.await?;
} }
Ok(()) Ok(())
+24 -38
View File
@@ -1,57 +1,43 @@
//! Trace transactions in a sync context. //! Trace transactions in a sync context.
use alloy::primitives::TxHash; use std::pin::Pin;
use alloy::providers::ProviderBuilder;
use alloy::providers::ext::DebugApi; use alloy::rpc::types::trace::geth::GethTrace;
use alloy::rpc::types::TransactionReceipt; use tokio::sync::oneshot;
use alloy::rpc::types::trace::geth::{GethDebugTracingOptions, GethTrace};
use tokio::sync::mpsc;
use crate::TO_TOKIO; use crate::TO_TOKIO;
use crate::tokio_runtime::AsyncNodeInteraction; use crate::tokio_runtime::AsyncNodeInteraction;
pub type Task = Pin<Box<dyn Future<Output = anyhow::Result<GethTrace>> + Send>>;
pub(crate) struct Trace { pub(crate) struct Trace {
transaction_hash: TxHash, sender: oneshot::Sender<anyhow::Result<GethTrace>>,
options: GethDebugTracingOptions, task: Task,
geth_trace_sender: mpsc::Sender<anyhow::Result<GethTrace>>,
connection_string: String,
} }
impl AsyncNodeInteraction for Trace { impl AsyncNodeInteraction for Trace {
type Output = anyhow::Result<GethTrace>; type Output = anyhow::Result<GethTrace>;
async fn execute_async(self) -> Self::Output { fn split(
let provider = ProviderBuilder::new() self,
.connect(&self.connection_string) ) -> (
.await?; std::pin::Pin<Box<dyn Future<Output = Self::Output> + Send>>,
Ok(provider oneshot::Sender<Self::Output>,
.debug_trace_transaction(self.transaction_hash, self.options) ) {
.await?) (self.task, self.sender)
}
fn output_sender(&self) -> mpsc::Sender<Self::Output> {
self.geth_trace_sender.clone()
} }
} }
/// Trace the transaction in [TransactionReceipt] against the `node`, /// Execute some [Task] that return a [GethTrace] result.
/// using the provided [GethDebugTracingOptions]. pub fn trace_transaction(task: Task) -> anyhow::Result<GethTrace> {
pub fn trace_transaction( let task_sender = TO_TOKIO.lock().unwrap().trace_sender.clone();
transaction_receipt: TransactionReceipt, let (sender, receiver) = oneshot::channel();
options: GethDebugTracingOptions,
connection_string: String,
) -> anyhow::Result<GethTrace> {
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 { task_sender
transaction_hash: transaction_receipt.transaction_hash, .blocking_send(Trace { task, sender })
options, .expect("we are not calling this from an async context");
geth_trace_sender,
connection_string,
})?;
geth_trace_receiver receiver
.blocking_recv() .blocking_recv()
.unwrap_or_else(|| anyhow::bail!("no receipt received")) .unwrap_or_else(|error| anyhow::bail!("no trace received: {error}"))
} }
+25 -31
View File
@@ -1,52 +1,46 @@
//! Execute transactions in a sync context. //! Execute transactions in a sync context.
use alloy::providers::{Provider, ProviderBuilder}; use std::pin::Pin;
use alloy::rpc::types::{TransactionReceipt, TransactionRequest};
use tokio::sync::mpsc; use alloy::rpc::types::TransactionReceipt;
use tokio::sync::oneshot;
use crate::TO_TOKIO; use crate::TO_TOKIO;
use crate::tokio_runtime::AsyncNodeInteraction; use crate::tokio_runtime::AsyncNodeInteraction;
pub type Task = Pin<Box<dyn Future<Output = anyhow::Result<TransactionReceipt>> + Send>>;
pub(crate) struct Transaction { pub(crate) struct Transaction {
transaction_request: TransactionRequest, receipt_sender: oneshot::Sender<anyhow::Result<TransactionReceipt>>,
receipt_sender: mpsc::Sender<anyhow::Result<TransactionReceipt>>, task: Task,
connection_string: String,
} }
impl AsyncNodeInteraction for Transaction { impl AsyncNodeInteraction for Transaction {
type Output = anyhow::Result<TransactionReceipt>; type Output = anyhow::Result<TransactionReceipt>;
async fn execute_async(self) -> Self::Output { fn split(
let provider = ProviderBuilder::new() self,
.connect(&self.connection_string) ) -> (
.await?; Pin<Box<dyn Future<Output = Self::Output> + Send>>,
Ok(provider oneshot::Sender<Self::Output>,
.send_transaction(self.transaction_request) ) {
.await? (self.task, self.receipt_sender)
.get_receipt()
.await?)
}
fn output_sender(&self) -> mpsc::Sender<Self::Output> {
self.receipt_sender.clone()
} }
} }
/// Execute the [TransactionRequest] against the `node`. /// Execute some [Task] that returns a [TransactionReceipt].
pub fn execute_transaction( pub fn execute_transaction(task: Task) -> anyhow::Result<TransactionReceipt> {
transaction_request: TransactionRequest,
connection_string: String,
) -> anyhow::Result<TransactionReceipt> {
let request_sender = TO_TOKIO.lock().unwrap().transaction_sender.clone(); let request_sender = TO_TOKIO.lock().unwrap().transaction_sender.clone();
let (receipt_sender, mut receipt_receiver) = mpsc::channel(1); let (receipt_sender, receipt_receiver) = oneshot::channel();
request_sender.blocking_send(Transaction { request_sender
transaction_request, .blocking_send(Transaction {
receipt_sender, receipt_sender,
connection_string, task,
})?; })
.expect("we are not calling this from an async context");
receipt_receiver receipt_receiver
.blocking_recv() .blocking_recv()
.unwrap_or_else(|| anyhow::bail!("no receipt received")) .unwrap_or_else(|error| anyhow::bail!("no receipt received: {error}"))
} }
+31 -5
View File
@@ -10,9 +10,12 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use alloy::rpc::types::{ use alloy::{
TransactionReceipt, TransactionRequest, providers::{Provider, ProviderBuilder, ext::DebugApi},
trace::geth::{DiffMode, PreStateFrame}, rpc::types::{
TransactionReceipt, TransactionRequest,
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
},
}; };
use revive_dt_config::Arguments; use revive_dt_config::Arguments;
use revive_dt_node_interaction::{ use revive_dt_node_interaction::{
@@ -144,14 +147,37 @@ impl EthereumNode for Instance {
&self, &self,
transaction: TransactionRequest, transaction: TransactionRequest,
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> { ) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
execute_transaction(transaction, self.connection_string()) let connection_string = self.connection_string();
execute_transaction(Box::pin(async move {
Ok(ProviderBuilder::new()
.connect(&connection_string)
.await?
.send_transaction(transaction)
.await?
.get_receipt()
.await?)
}))
} }
fn trace_transaction( fn trace_transaction(
&self, &self,
transaction: TransactionReceipt, transaction: TransactionReceipt,
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> { ) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
trace_transaction(transaction, Default::default(), self.connection_string()) let connection_string = self.connection_string();
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
diff_mode: Some(true),
disable_code: None,
disable_storage: None,
});
trace_transaction(Box::pin(async move {
Ok(ProviderBuilder::new()
.connect(&connection_string)
.await?
.debug_trace_transaction(transaction.transaction_hash, trace_options)
.await?)
}))
} }
} }
+1 -1
View File
@@ -61,7 +61,7 @@ where
} }
fn spawn_node<T: Node + Send>(args: &Arguments, genesis: String) -> anyhow::Result<T> { fn spawn_node<T: Node + Send>(args: &Arguments, genesis: String) -> anyhow::Result<T> {
let mut node = T::new(&args); let mut node = T::new(args);
log::info!("starting node: {}", node.connection_string()); log::info!("starting node: {}", node.connection_string());
node.spawn(genesis)?; node.spawn(genesis)?;
Ok(node) Ok(node)