Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
114 changes: 100 additions & 14 deletions crates/core/src/driver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,29 @@
Ok(output) => {
task.json_output = Some(output.output.clone());
task.error = output.error;
self.contracts.insert(output.input, output.output);
self.contracts.insert(output.input, output.output.clone());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the added .clone() necessary, I don't see the output being accessed further down?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it's not necessary.


if let Some(last_output) = self.contracts.values().last() {
if let Some(contracts) = &last_output.contracts {
for (file, contracts_map) in contracts {
for (contract_name, _) in contracts_map {

Check failure on line 91 in crates/core/src/driver/mod.rs

View workflow job for this annotation

GitHub Actions / CI on macos-14

you seem to want to iterate on a map's keys
log::debug!(
"Compiled contract: {} from file: {}",
contract_name,
file
);
}
}
} else {
log::warn!("Compiled contracts field is None");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the only point of this heavily nested code here is to log a warning if there were no contracts compiled? This makes sense but could be simplified a lot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it is useful to log that the contract was succesfully compiled from a file and also when we don't have any contract to compile.

}
}

Report::compilation(span, T::config_id(), task);
Ok(())
}
Err(error) => {
log::error!("Failed to compile contract: {:?}", error.to_string());
task.error = Some(error.to_string());
Err(error)
}
Expand All @@ -99,13 +117,40 @@
input: &Input,
node: &T::Blockchain,
) -> anyhow::Result<(GethTrace, DiffMode)> {
let receipt = node.execute_transaction(input.legacy_transaction(
self.config.network_id,
0,
&self.deployed_contracts,
)?)?;
log::trace!("Calling execute_input for input: {:?}", input);

let nonce = node.fetch_add_nonce(input.caller)?;

log::debug!(
"Nonce calculated on the execute contract, calculated nonce {}, for contract {}, having address {} on node: {}",
&nonce,
&input.instance,
&input.caller,
std::any::type_name::<T>()
);

let tx =
match input.legacy_transaction(self.config.network_id, nonce, &self.deployed_contracts)
{
Ok(tx) => tx,
Err(err) => {
log::error!("Failed to construct legacy transaction: {:?}", err);
return Err(err.into());

Check failure on line 138 in crates/core/src/driver/mod.rs

View workflow job for this annotation

GitHub Actions / CI on macos-14

useless conversion to the same type: `anyhow::Error`
}
};

log::trace!("Executing transaction for input: {:?}", input);

let receipt = match node.execute_transaction(tx) {
Ok(receipt) => receipt,
Err(err) => {
log::error!("Failed to execute transaction: {:?}", err);
return Err(err.into());

Check failure on line 148 in crates/core/src/driver/mod.rs

View workflow job for this annotation

GitHub Actions / CI on macos-14

useless conversion to the same type: `anyhow::Error`
}
};

log::trace!("Transaction receipt: {:?}", receipt);

let trace = node.trace_transaction(receipt.clone())?;
log::trace!("Trace result: {:?}", trace);

Expand All @@ -115,14 +160,28 @@
}

pub fn deploy_contracts(&mut self, input: &Input, node: &T::Blockchain) -> anyhow::Result<()> {
log::debug!(
"Deploying contracts {}, having address {} on node: {}",
&input.instance,
&input.caller,
std::any::type_name::<T>()
);
for output in self.contracts.values() {
let Some(contract_map) = &output.contracts else {
log::debug!("No contracts in output — skipping deployment for this input.");
log::debug!(
"No contracts in output — skipping deployment for this input {}",
&input.instance
);
continue;
};

for contracts in contract_map.values() {
for (contract_name, contract) in contracts {
log::debug!(
"Contract name is: {:?} and the input name is: {:?}",
&contract_name,
&input.instance
);
if contract_name != &input.instance {
continue;
}
Expand All @@ -134,24 +193,47 @@
.map(|b| b.object.clone());

let Some(code) = bytecode else {
anyhow::bail!("no bytecode for contract `{}`", contract_name);
log::error!("no bytecode for contract {}", contract_name);
continue;
};

let nonce = node.fetch_add_nonce(input.caller)?;

log::debug!(
"Calculated nonce {}, for contract {}, having address {} on node: {}",
&nonce,
&input.instance,
&input.caller,
std::any::type_name::<T>()
);

let tx = TransactionRequest::default()
.with_from(input.caller)
.with_to(Address::ZERO)
.with_input(Bytes::from(code.clone()))
.with_gas_price(20_000_000_000)
.with_gas_limit(20_000_000_000)
.with_gas_price(5_000_000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you need to change this? IIRC it was arbitrary but if it has to be a specific value it would be good to have the reason as comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed it because I have received this message:

error code -32000: tx fee (400.00 ether) exceeds the configured cap (1.00 ether)

And this is because:

Total Fee = gas_price * gas_limit
= 20_000_000_000 * 20_000_000
= 400_000_000_000_000_000_000 (wei)
= 400 ether

And the new value will be:

0.000025 ether

.with_gas_limit(5_000_000)
.with_chain_id(self.config.network_id)
.with_nonce(0);
.with_nonce(nonce);

let receipt = match node.execute_transaction(tx) {
Ok(receipt) => receipt,
Err(err) => {
log::error!(
"Failed to execute transaction when deploying the contract: {:?}, {:?}",
&contract_name,
err
);
return Err(err.into());

Check failure on line 227 in crates/core/src/driver/mod.rs

View workflow job for this annotation

GitHub Actions / CI on macos-14

useless conversion to the same type: `anyhow::Error`
}
};

let receipt = node.execute_transaction(tx)?;
let Some(address) = receipt.contract_address else {
anyhow::bail!(
"contract `{}` deployment did not return an address",
log::error!(
"contract {} deployment did not return an address",
contract_name
);
continue;
};

self.deployed_contracts
Expand All @@ -161,6 +243,8 @@
}
}

log::debug!("Available contracts: {:?}", self.deployed_contracts.keys());

Ok(())
}
}
Expand Down Expand Up @@ -227,9 +311,11 @@

for case in &self.metadata.cases {
for input in &case.inputs {
log::debug!("Starting deploying contract {}", &input.instance);
leader_state.deploy_contracts(input, self.leader_node)?;
follower_state.deploy_contracts(input, self.follower_node)?;

log::debug!("Starting executing contract {}", &input.instance);
let (_, leader_diff) = leader_state.execute_input(input, self.leader_node)?;
let (_, follower_diff) =
follower_state.execute_input(input, self.follower_node)?;
Expand Down
4 changes: 2 additions & 2 deletions crates/format/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ impl Input {
.with_to(to)
.with_nonce(nonce)
.with_chain_id(chain_id)
.with_gas_price(20_000_000_000)
.with_gas_limit(20_000_000_000))
.with_gas_price(5_000_000)
.with_gas_limit(5_000_000))
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/node-interaction/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! This crate implements all node interactions.

use alloy::primitives::Address;
use alloy::rpc::types::trace::geth::{DiffMode, GethTrace};
use alloy::rpc::types::{TransactionReceipt, TransactionRequest};
use tokio_runtime::TO_TOKIO;

pub mod nonce;
mod tokio_runtime;
pub mod trace;
pub mod transaction;
Expand All @@ -21,4 +23,6 @@ pub trait EthereumNode {

/// Returns the state diff of the transaction hash in the [TransactionReceipt].
fn state_diff(&self, transaction: TransactionReceipt) -> anyhow::Result<DiffMode>;

fn fetch_add_nonce(&self, address: Address) -> anyhow::Result<u64>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a doc comment explaining what the trait method is supposed to do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the new revision

}
55 changes: 55 additions & 0 deletions crates/node-interaction/src/nonce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::pin::Pin;

use alloy::{
primitives::Address,
providers::{Provider, ProviderBuilder},
};
use tokio::sync::oneshot;

use crate::{TO_TOKIO, tokio_runtime::AsyncNodeInteraction};

pub type Task = Pin<Box<dyn Future<Output = anyhow::Result<u64>> + Send>>;

pub(crate) struct Nonce {
sender: oneshot::Sender<anyhow::Result<u64>>,
task: Task,
}

impl AsyncNodeInteraction for Nonce {
type Output = anyhow::Result<u64>;

fn split(
self,
) -> (
std::pin::Pin<Box<dyn Future<Output = Self::Output> + Send>>,
oneshot::Sender<Self::Output>,
) {
(self.task, self.sender)
}
}

/// This is like `trace_transaction`, just for nonces.
pub fn fetch_onchain_nonce(
connection: String,
wallet: alloy::network::EthereumWallet,
address: Address,
) -> anyhow::Result<u64> {
let sender = TO_TOKIO.lock().unwrap().nonce_sender.clone();

let (tx, rx) = oneshot::channel();
let task: Task = Box::pin(async move {
let provider = ProviderBuilder::new()
.wallet(wallet)
.connect(&connection)
.await?;
let onchain = provider.get_transaction_count(address).await?;
Ok(onchain)
});

sender
.blocking_send(Nonce { task, sender: tx })
.expect("not in async context");

rx.blocking_recv()
.unwrap_or_else(|err| anyhow::bail!("nonce fetch failed: {err}"))
}
8 changes: 8 additions & 0 deletions crates/node-interaction/src/tokio_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use tokio::spawn;
use tokio::sync::{mpsc, oneshot};
use tokio::task::JoinError;

use crate::nonce::Nonce;
use crate::trace::Trace;
use crate::transaction::Transaction;

Expand All @@ -33,31 +34,38 @@ pub(crate) trait AsyncNodeInteraction: Send + 'static {
pub(crate) struct TokioRuntime {
pub(crate) transaction_sender: mpsc::Sender<Transaction>,
pub(crate) trace_sender: mpsc::Sender<Trace>,
pub(crate) nonce_sender: mpsc::Sender<Nonce>,
}

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::<Transaction>(1024);
let (trace_sender, trace_receiver) = mpsc::channel::<Trace>(1024);
let (nonce_sender, nonce_receiver) = mpsc::channel::<Nonce>(1024);

thread::spawn(move || {
rt.block_on(async move {
let transaction_task = spawn(interaction::<Transaction>(transaction_receiver));
let trace_task = spawn(interaction::<Trace>(trace_receiver));
let nonce_task = spawn(interaction::<Nonce>(nonce_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}");
}
if let Err(error) = nonce_task.await {
log::error!("tokio nonce task failed: {error}");
}
});
});

Self {
transaction_sender,
trace_sender,
nonce_sender,
}
}
}
Expand Down
24 changes: 22 additions & 2 deletions crates/node/src/geth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ use std::{
io::{BufRead, BufReader, Read, Write},
path::PathBuf,
process::{Child, Command, Stdio},
sync::atomic::{AtomicU32, Ordering},
sync::{
Mutex,
atomic::{AtomicU32, Ordering},
},
thread,
time::{Duration, Instant},
};

use alloy::{
network::EthereumWallet,
primitives::{Address, map::HashMap},
providers::{Provider, ProviderBuilder, ext::DebugApi},
rpc::types::{
TransactionReceipt, TransactionRequest,
Expand All @@ -20,7 +24,8 @@ use alloy::{
};
use revive_dt_config::Arguments;
use revive_dt_node_interaction::{
EthereumNode, trace::trace_transaction, transaction::execute_transaction,
EthereumNode, nonce::fetch_onchain_nonce, trace::trace_transaction,
transaction::execute_transaction,
};

use crate::Node;
Expand All @@ -45,6 +50,7 @@ pub struct Instance {
network_id: u64,
start_timeout: u64,
wallet: EthereumWallet,
nonces: Mutex<HashMap<Address, u64>>,
}

impl Instance {
Expand Down Expand Up @@ -198,6 +204,19 @@ impl EthereumNode for Instance {
_ => anyhow::bail!("expected a diff mode trace"),
}
}

fn fetch_add_nonce(&self, address: Address) -> anyhow::Result<u64> {
let connection_string = self.connection_string.clone();
let wallet = self.wallet.clone();

let onchain_nonce = fetch_onchain_nonce(connection_string, wallet, address)?;

let mut nonces = self.nonces.lock().unwrap();
let current = nonces.entry(address).or_insert(onchain_nonce);
let value = *current;
*current += 1;
Ok(value)
}
}

impl Node for Instance {
Expand All @@ -216,6 +235,7 @@ impl Node for Instance {
network_id: config.network_id,
start_timeout: config.geth_start_timeout,
wallet: config.wallet(),
nonces: Mutex::new(HashMap::new()),
}
}

Expand Down
Loading
Loading