mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-25 10:28:00 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70395f580a | |||
| 6f4aa731ab |
@@ -76,7 +76,7 @@ pub struct Arguments {
|
||||
/// This argument controls which private keys the nodes should have access to and be added to
|
||||
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
|
||||
/// of the node.
|
||||
#[arg(short, long = "private-keys-count", default_value_t = 30)]
|
||||
#[arg(long = "private-keys-count", default_value_t = 30)]
|
||||
pub private_keys_to_add: usize,
|
||||
|
||||
/// The differential testing leader node implementation.
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::marker::PhantomData;
|
||||
|
||||
use alloy::json_abi::JsonAbi;
|
||||
use alloy::network::{Ethereum, TransactionBuilder};
|
||||
use alloy::primitives::Bytes;
|
||||
use alloy::rpc::types::TransactionReceipt;
|
||||
use alloy::rpc::types::trace::geth::{
|
||||
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace,
|
||||
@@ -442,9 +441,8 @@ where
|
||||
// Additionally, what happens if the compiler filter doesn't match? Do we consider that the
|
||||
// transaction should succeed? Do we just ignore the expectation?
|
||||
|
||||
let error_span =
|
||||
tracing::error_span!("Exception failed", ?tracing_result, ?execution_receipt,);
|
||||
let _guard = error_span.enter();
|
||||
let deployed_contracts = self.deployed_contracts.entry(case_idx).or_default();
|
||||
let chain_state_provider = node;
|
||||
|
||||
// Handling the receipt state assertion.
|
||||
let expected = !expectation.exception;
|
||||
@@ -458,17 +456,16 @@ where
|
||||
|
||||
// Handling the calldata assertion
|
||||
if let Some(ref expected_calldata) = expectation.return_data {
|
||||
let expected = expected_calldata
|
||||
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)
|
||||
.map(Bytes::from)?;
|
||||
let actual = tracing_result.output.clone().unwrap_or_default();
|
||||
if !expected.starts_with(&actual) {
|
||||
let expected = expected_calldata;
|
||||
let actual = &tracing_result.output.as_ref().unwrap_or_default();
|
||||
if !expected.is_equivalent(actual, deployed_contracts, chain_state_provider)? {
|
||||
tracing::error!(
|
||||
%expected,
|
||||
?execution_receipt,
|
||||
?expected,
|
||||
%actual,
|
||||
"Calldata assertion failed"
|
||||
);
|
||||
anyhow::bail!("Calldata assertion failed - Expected {expected} but got {actual}",);
|
||||
anyhow::bail!("Calldata assertion failed - Expected {expected:?} but got {actual}",);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,17 +502,24 @@ where
|
||||
}
|
||||
|
||||
// Handling the topics assertion.
|
||||
for (expected_topic, actual_topic) in expected_event
|
||||
for (expected, actual) in expected_event
|
||||
.topics
|
||||
.as_slice()
|
||||
.iter()
|
||||
.zip(actual_event.topics())
|
||||
{
|
||||
let expected = Calldata::Compound(vec![expected_topic.clone()])
|
||||
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)?;
|
||||
let actual = actual_topic.to_vec();
|
||||
if actual != expected {
|
||||
tracing::error!(?expected, ?actual, "Event topics assertion failed",);
|
||||
let expected = Calldata::Compound(vec![expected.clone()]);
|
||||
if !expected.is_equivalent(
|
||||
&actual.0,
|
||||
deployed_contracts,
|
||||
chain_state_provider,
|
||||
)? {
|
||||
tracing::error!(
|
||||
?execution_receipt,
|
||||
?expected,
|
||||
?actual,
|
||||
"Event topics assertion failed",
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Event topics assertion failed - Expected {expected:?} but got {actual:?}",
|
||||
);
|
||||
@@ -523,13 +527,15 @@ where
|
||||
}
|
||||
|
||||
// Handling the values assertion.
|
||||
let expected = &expected_event
|
||||
.values
|
||||
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)
|
||||
.map(Bytes::from)?;
|
||||
let expected = &expected_event.values;
|
||||
let actual = &actual_event.data().data;
|
||||
if !expected.starts_with(actual) {
|
||||
tracing::error!(?expected, ?actual, "Event value assertion failed",);
|
||||
if !expected.is_equivalent(&actual.0, deployed_contracts, chain_state_provider)? {
|
||||
tracing::error!(
|
||||
?execution_receipt,
|
||||
?expected,
|
||||
?actual,
|
||||
"Event value assertion failed",
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Event value assertion failed - Expected {expected:?} but got {actual:?}",
|
||||
);
|
||||
|
||||
@@ -57,7 +57,7 @@ pub struct Event {
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum Calldata {
|
||||
Single(String),
|
||||
Single(Bytes),
|
||||
Compound(Vec<String>),
|
||||
}
|
||||
|
||||
@@ -168,8 +168,8 @@ impl Calldata {
|
||||
chain_state_provider: &impl EthereumNode,
|
||||
) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Calldata::Single(string) => {
|
||||
alloy::hex::decode_to_slice(string, buffer)?;
|
||||
Calldata::Single(bytes) => {
|
||||
buffer.extend_from_slice(bytes);
|
||||
}
|
||||
Calldata::Compound(items) => {
|
||||
for (arg_idx, arg) in items.iter().enumerate() {
|
||||
@@ -190,10 +190,48 @@ impl Calldata {
|
||||
|
||||
pub fn size_requirement(&self) -> usize {
|
||||
match self {
|
||||
Calldata::Single(single) => (single.len() - 2) / 2,
|
||||
Calldata::Single(single) => single.len(),
|
||||
Calldata::Compound(items) => items.len() * 32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if this [`Calldata`] is equivalent to the passed calldata bytes.
|
||||
pub fn is_equivalent(
|
||||
&self,
|
||||
other: &[u8],
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
chain_state_provider: &impl EthereumNode,
|
||||
) -> anyhow::Result<bool> {
|
||||
match self {
|
||||
Calldata::Single(calldata) => Ok(calldata == other),
|
||||
Calldata::Compound(items) => {
|
||||
// Chunking the "other" calldata into 32 byte chunks since each
|
||||
// one of the items in the compound calldata represents 32 bytes
|
||||
for (this, other) in items.iter().zip(other.chunks(32)) {
|
||||
// The matterlabs format supports wildcards and therefore we
|
||||
// also need to support them.
|
||||
if this == "*" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let other = if other.len() < 32 {
|
||||
let mut vec = other.to_vec();
|
||||
vec.resize(32, 0);
|
||||
std::borrow::Cow::Owned(vec)
|
||||
} else {
|
||||
std::borrow::Cow::Borrowed(other)
|
||||
};
|
||||
|
||||
let this = resolve_argument(this, deployed_contracts, chain_state_provider)?;
|
||||
let other = U256::from_be_slice(&other);
|
||||
if this != other {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/// This constant defines how much Wei accounts are pre-seeded with in genesis.
|
||||
///
|
||||
/// We use [`u128::MAX`] here which means that accounts will be given 2^128 - 1 WEI which is
|
||||
/// (2^128 - 1) / 10^18 ETH.
|
||||
pub const INITIAL_BALANCE: u128 = u128::MAX;
|
||||
+38
-48
@@ -29,7 +29,7 @@ use revive_dt_config::Arguments;
|
||||
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
|
||||
use tracing::Level;
|
||||
|
||||
use crate::{Node, common::FallbackGasFiller};
|
||||
use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
|
||||
|
||||
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
@@ -74,6 +74,8 @@ impl Instance {
|
||||
const GETH_STDOUT_LOG_FILE_NAME: &str = "node_stdout.log";
|
||||
const GETH_STDERR_LOG_FILE_NAME: &str = "node_stderr.log";
|
||||
|
||||
const TRANSACTION_INDEXING_ERROR: &str = "transaction indexing is in progress";
|
||||
|
||||
/// Create the node directory and call `geth init` to configure the genesis.
|
||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||
fn init(&mut self, genesis: String) -> anyhow::Result<&mut Self> {
|
||||
@@ -84,10 +86,10 @@ impl Instance {
|
||||
for signer_address in
|
||||
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet)
|
||||
{
|
||||
genesis.alloc.entry(signer_address).or_insert(
|
||||
GenesisAccount::default()
|
||||
.with_balance(10000000000000000000000u128.try_into().unwrap()),
|
||||
);
|
||||
genesis
|
||||
.alloc
|
||||
.entry(signer_address)
|
||||
.or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE)));
|
||||
}
|
||||
let genesis_path = self.base_directory.join(Self::GENESIS_JSON_FILE);
|
||||
serde_json::to_writer(File::create(&genesis_path)?, &genesis)?;
|
||||
@@ -265,57 +267,45 @@ impl EthereumNode for Instance {
|
||||
// it eventually works, but we only do that if the error we get back is the "transaction
|
||||
// indexing is in progress" error or if the receipt is None.
|
||||
//
|
||||
// At the moment we do not allow for the 60 seconds to be modified and we take it as
|
||||
// being an implementation detail that's invisible to anything outside of this module.
|
||||
//
|
||||
// We allow a total of 60 retries for getting the receipt with one second between each
|
||||
// retry and the next which means that we allow for a total of 60 seconds of waiting
|
||||
// before we consider that we're unable to get the transaction receipt.
|
||||
// Getting the transaction indexed and taking a receipt can take a long time especially
|
||||
// when a lot of transactions are being submitted to the node. Thus, while initially we
|
||||
// only allowed for 60 seconds of waiting with a 1 second delay in polling, we need to
|
||||
// allow for a larger wait time. Therefore, in here we allow for 5 minutes of waiting
|
||||
// with exponential backoff each time we attempt to get the receipt and find that it's
|
||||
// not available.
|
||||
let mut retries = 0;
|
||||
let mut total_wait_duration = Duration::from_secs(0);
|
||||
let max_allowed_wait_duration = Duration::from_secs(5 * 60);
|
||||
loop {
|
||||
if total_wait_duration >= max_allowed_wait_duration {
|
||||
tracing::error!(
|
||||
?total_wait_duration,
|
||||
?max_allowed_wait_duration,
|
||||
retry_count = retries,
|
||||
"Failed to get receipt after polling for it"
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Polled for receipt for {total_wait_duration:?} but failed to get it"
|
||||
);
|
||||
}
|
||||
|
||||
match provider.get_transaction_receipt(*transaction_hash).await {
|
||||
Ok(Some(receipt)) => {
|
||||
tracing::info!("Obtained the transaction receipt");
|
||||
break Ok(receipt);
|
||||
}
|
||||
Ok(None) => {
|
||||
if retries == 60 {
|
||||
tracing::error!(
|
||||
"Polled for transaction receipt for 60 seconds but failed to get it"
|
||||
);
|
||||
break Err(anyhow::anyhow!("Failed to get the transaction receipt"));
|
||||
} else {
|
||||
tracing::trace!(
|
||||
retries,
|
||||
"Sleeping for 1 second and trying to get the receipt again"
|
||||
);
|
||||
retries += 1;
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Ok(Some(receipt)) => break Ok(receipt),
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
let error_string = error.to_string();
|
||||
if error_string.contains("transaction indexing is in progress") {
|
||||
if retries == 60 {
|
||||
tracing::error!(
|
||||
"Polled for transaction receipt for 60 seconds but failed to get it"
|
||||
);
|
||||
break Err(error.into());
|
||||
} else {
|
||||
tracing::trace!(
|
||||
retries,
|
||||
"Sleeping for 1 second and trying to get the receipt again"
|
||||
);
|
||||
retries += 1;
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if !error_string.contains(Self::TRANSACTION_INDEXING_ERROR) {
|
||||
break Err(error.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let next_wait_duration = Duration::from_secs(2u64.pow(retries))
|
||||
.min(max_allowed_wait_duration - total_wait_duration);
|
||||
total_wait_duration += next_wait_duration;
|
||||
retries += 1;
|
||||
|
||||
tokio::time::sleep(next_wait_duration).await;
|
||||
}
|
||||
})?
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ use tracing::Level;
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
|
||||
|
||||
use crate::{Node, common::FallbackGasFiller};
|
||||
use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
|
||||
|
||||
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
@@ -131,10 +131,10 @@ impl KitchensinkNode {
|
||||
for signer_address in
|
||||
<EthereumWallet as NetworkWallet<Ethereum>>::signer_addresses(&self.wallet)
|
||||
{
|
||||
genesis.alloc.entry(signer_address).or_insert(
|
||||
GenesisAccount::default()
|
||||
.with_balance(10000000000000000000000u128.try_into().unwrap()),
|
||||
);
|
||||
genesis
|
||||
.alloc
|
||||
.entry(signer_address)
|
||||
.or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE)));
|
||||
}
|
||||
self.extract_balance_from_genesis_file(&genesis)?
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ use revive_dt_config::Arguments;
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
|
||||
pub mod common;
|
||||
pub mod constants;
|
||||
pub mod geth;
|
||||
pub mod kitchensink;
|
||||
pub mod pool;
|
||||
|
||||
Reference in New Issue
Block a user