mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-22 20:47:58 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d04cdf337f | |||
| 11b568a442 | |||
| 74b5e34260 | |||
| 4c55bba53d | |||
| 222b5d4f86 | |||
| 762288bf04 | |||
| cac2220188 | |||
| 4bf22f2d2b | |||
| 5c64de7e67 | |||
| fa4bf95091 | |||
| 2537a132e6 | |||
| 20da99784e | |||
| 6d7cd67931 | |||
| 68bda92465 | |||
| ddd775d703 | |||
| 02547b62ee | |||
| 61540741e1 | |||
| fa4bbbb987 | |||
| 8f80b1da8a | |||
| c6d63255ec | |||
| a4f5c4c8af | |||
| 7d48d1600e | |||
| e7e00a50dd | |||
| 27a0a0de0b | |||
| 83c20b1be3 | |||
| 331705134a | |||
| 075c8235a7 | |||
| 5f86ade1e0 | |||
| 43064022e8 | |||
| 57bb015fa3 | |||
| 43e0d0e592 | |||
| 332012754d | |||
| eb6c64c17a | |||
| 2373872230 | |||
| e3723e780a | |||
| 4d4398f83e | |||
| 76c85f191c | |||
| 7664e9735e | |||
| 4bab457114 | |||
| f6374ad52a | |||
| abba0cee08 |
Generated
+1
@@ -3984,6 +3984,7 @@ dependencies = [
|
|||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-sol-types",
|
"alloy-sol-types",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"revive-dt-node-interaction",
|
||||||
"semver 1.0.26",
|
"semver 1.0.26",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
+387
-42
@@ -1,7 +1,7 @@
|
|||||||
//! The test driver handles the compilation and execution of the test cases.
|
//! The test driver handles the compilation and execution of the test cases.
|
||||||
|
|
||||||
use alloy::json_abi::JsonAbi;
|
use alloy::json_abi::JsonAbi;
|
||||||
use alloy::network::TransactionBuilder;
|
use alloy::network::{Ethereum, TransactionBuilder};
|
||||||
use alloy::rpc::types::TransactionReceipt;
|
use alloy::rpc::types::TransactionReceipt;
|
||||||
use alloy::rpc::types::trace::geth::GethTrace;
|
use alloy::rpc::types::trace::geth::GethTrace;
|
||||||
use alloy::{
|
use alloy::{
|
||||||
@@ -19,7 +19,7 @@ use revive_dt_report::reporter::{CompilationTask, Report, Span};
|
|||||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap as StdHashMap;
|
use std::collections::HashMap as StdHashMap;
|
||||||
use tracing::Level;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::Platform;
|
use crate::Platform;
|
||||||
|
|
||||||
@@ -134,8 +134,12 @@ where
|
|||||||
std::any::type_name::<T>()
|
std::any::type_name::<T>()
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx =
|
let tx = match input.legacy_transaction(
|
||||||
match input.legacy_transaction(nonce, &self.deployed_contracts, &self.deployed_abis) {
|
nonce,
|
||||||
|
&self.deployed_contracts,
|
||||||
|
&self.deployed_abis,
|
||||||
|
node,
|
||||||
|
) {
|
||||||
Ok(tx) => {
|
Ok(tx) => {
|
||||||
tracing::debug!("Legacy transaction data: {tx:#?}");
|
tracing::debug!("Legacy transaction data: {tx:#?}");
|
||||||
tx
|
tx
|
||||||
@@ -253,10 +257,12 @@ where
|
|||||||
return Err(error.into());
|
return Err(error.into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let tx = {
|
||||||
let tx = TransactionRequest::default()
|
let tx = TransactionRequest::default()
|
||||||
.nonce(nonce)
|
.nonce(nonce)
|
||||||
.from(input.caller)
|
.from(input.caller);
|
||||||
.with_deploy_code(code);
|
TransactionBuilder::<Ethereum>::with_deploy_code(tx, code)
|
||||||
|
};
|
||||||
|
|
||||||
let receipt = match node.execute_transaction(tx) {
|
let receipt = match node.execute_transaction(tx) {
|
||||||
Ok(receipt) => receipt,
|
Ok(receipt) => receipt,
|
||||||
@@ -398,67 +404,178 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(&mut self, span: Span) -> anyhow::Result<()> {
|
// A note on this function and the choice of how we handle errors that happen here. This is not
|
||||||
|
// a doc comment since it's a comment for the maintainers of this code and not for the users of
|
||||||
|
// this code.
|
||||||
|
//
|
||||||
|
// This function does a few things: it builds the contracts for the various SOLC modes needed.
|
||||||
|
// It deploys the contracts to the chain, and it executes the various inputs that are specified
|
||||||
|
// for the test cases.
|
||||||
|
//
|
||||||
|
// In most functions in the codebase, it's fine to just say "If we encounter an error just
|
||||||
|
// bubble it up to the caller", but this isn't a good idea to do here and we need an elaborate
|
||||||
|
// way to report errors all while being graceful and continuing execution where we can. For
|
||||||
|
// example, if one of the inputs of one of the cases fail to execute, then we should not just
|
||||||
|
// bubble that error up immediately. Instead, we should note it down and continue to the next
|
||||||
|
// case as the next case might succeed.
|
||||||
|
//
|
||||||
|
// Therefore, this method returns an `ExecutionResult` object, and not just a normal `Result`.
|
||||||
|
// This object is fully typed to contain information about what exactly in the execution was a
|
||||||
|
// success and what failed.
|
||||||
|
//
|
||||||
|
// The above then allows us to have better logging and better information in the caller of this
|
||||||
|
// function as we have a more detailed view of what worked and what didn't.
|
||||||
|
pub fn execute(&mut self, span: Span) -> ExecutionResult {
|
||||||
|
// This is the execution result object that all of the execution information will be
|
||||||
|
// collected into and returned at the end of the execution.
|
||||||
|
let mut execution_result = ExecutionResult::default();
|
||||||
|
|
||||||
|
let tracing_span = tracing::info_span!("Handling metadata file");
|
||||||
|
let _guard = tracing_span.enter();
|
||||||
|
|
||||||
for mode in self.metadata.solc_modes() {
|
for mode in self.metadata.solc_modes() {
|
||||||
|
let tracing_span = tracing::info_span!("With solc mode", solc_mode = ?mode);
|
||||||
|
let _guard = tracing_span.enter();
|
||||||
|
|
||||||
let mut leader_state = State::<L>::new(self.config, span);
|
let mut leader_state = State::<L>::new(self.config, span);
|
||||||
leader_state.build_contracts(&mode, self.metadata)?;
|
|
||||||
|
|
||||||
let mut follower_state = State::<F>::new(self.config, span);
|
let mut follower_state = State::<F>::new(self.config, span);
|
||||||
follower_state.build_contracts(&mode, self.metadata)?;
|
|
||||||
|
|
||||||
for (case_idx, case) in self.metadata.cases.iter().enumerate() {
|
// We build the contracts. If building the contracts for the metadata file fails then we
|
||||||
// Creating a tracing span to know which case within the metadata is being executed
|
// have no other option but to keep note of this error and move on to the next solc mode
|
||||||
// and which one we're getting logs for.
|
// and NOT just bail out of the execution as a whole.
|
||||||
let tracing_span = tracing::span!(
|
let build_result = tracing::info_span!("Building contracts").in_scope(|| {
|
||||||
Level::INFO,
|
match leader_state.build_contracts(&mode, self.metadata) {
|
||||||
"Executing case",
|
Ok(_) => {
|
||||||
case = case.name,
|
tracing::debug!(target = ?Target::Leader, "Contract building succeeded");
|
||||||
|
execution_result.add_successful_build(Target::Leader, mode.clone());
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(target = ?Target::Leader, ?error, "Contract building failed");
|
||||||
|
execution_result.add_failed_build(Target::Leader, mode.clone(), error);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match follower_state.build_contracts(&mode, self.metadata) {
|
||||||
|
Ok(_) => {
|
||||||
|
tracing::debug!(target = ?Target::Follower, "Contract building succeeded");
|
||||||
|
execution_result.add_successful_build(Target::Follower, mode.clone());
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(target = ?Target::Follower, ?error, "Contract building failed");
|
||||||
|
execution_result.add_failed_build(Target::Follower, mode.clone(), error);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
if build_result.is_err() {
|
||||||
|
// Note: We skip to the next solc mode as there's nothing that we can do at this
|
||||||
|
// point, the building has failed. We do NOT bail out of the execution as a whole.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For cases if one of the inputs fail then we move on to the next case and we do NOT
|
||||||
|
// bail out of the whole thing.
|
||||||
|
'case_loop: for (case_idx, case) in self.metadata.cases.iter().enumerate() {
|
||||||
|
let tracing_span = tracing::info_span!(
|
||||||
|
"Handling case",
|
||||||
|
case_name = case.name,
|
||||||
case_idx = case_idx
|
case_idx = case_idx
|
||||||
);
|
);
|
||||||
let _guard = tracing_span.enter();
|
let _guard = tracing_span.enter();
|
||||||
|
|
||||||
for input in &case.inputs {
|
// For inputs if one of the inputs fail we move on to the next case (we do not move
|
||||||
tracing::debug!("Starting deploying contract {}", &input.instance);
|
// on to the next input as it doesn't make sense. It depends on the previous one).
|
||||||
if let Err(err) = leader_state.deploy_contracts(input, self.leader_node) {
|
for (input_idx, input) in case.inputs.iter().enumerate() {
|
||||||
tracing::error!("Leader deployment failed for {}: {err}", input.instance);
|
let tracing_span = tracing::info_span!("Handling input", input_idx);
|
||||||
continue;
|
let _guard = tracing_span.enter();
|
||||||
} else {
|
|
||||||
tracing::debug!("Leader deployment succeeded for {}", &input.instance);
|
// TODO: verify if this is correct, I doubt that we need to do contract redeploy
|
||||||
|
// for each input. It doesn't quite look to be correct but we need to cross
|
||||||
|
// check with the matterlabs implementation. This matches our implementation but
|
||||||
|
// I have doubts around its correctness.
|
||||||
|
let deployment_result = tracing::info_span!(
|
||||||
|
"Deploying contracts",
|
||||||
|
contract_name = input.instance
|
||||||
|
)
|
||||||
|
.in_scope(|| {
|
||||||
|
if let Err(error) = leader_state.deploy_contracts(input, self.leader_node) {
|
||||||
|
tracing::error!(target = ?Target::Leader, ?error, "Contract deployment failed");
|
||||||
|
execution_result.add_failed_case(
|
||||||
|
Target::Leader,
|
||||||
|
mode.clone(),
|
||||||
|
case.name.clone().unwrap_or("no case name".to_owned()),
|
||||||
|
case_idx,
|
||||||
|
input_idx,
|
||||||
|
anyhow::Error::msg(
|
||||||
|
format!("Failed to deploy contracts, {error}")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Err(error)
|
||||||
|
};
|
||||||
|
if let Err(error) =
|
||||||
|
follower_state.deploy_contracts(input, self.follower_node)
|
||||||
|
{
|
||||||
|
tracing::error!(target = ?Target::Follower, ?error, "Contract deployment failed");
|
||||||
|
execution_result.add_failed_case(
|
||||||
|
Target::Follower,
|
||||||
|
mode.clone(),
|
||||||
|
case.name.clone().unwrap_or("no case name".to_owned()),
|
||||||
|
case_idx,
|
||||||
|
input_idx,
|
||||||
|
anyhow::Error::msg(
|
||||||
|
format!("Failed to deploy contracts, {error}")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Err(error)
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
if deployment_result.is_err() {
|
||||||
|
// Noting it again here: if something in the input fails we do not move on
|
||||||
|
// to the next input, we move to the next case completely.
|
||||||
|
continue 'case_loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = follower_state.deploy_contracts(input, self.follower_node) {
|
let execution_result =
|
||||||
tracing::error!("Follower deployment failed for {}: {err}", input.instance);
|
tracing::info_span!("Executing input", contract_name = input.instance)
|
||||||
continue;
|
.in_scope(|| {
|
||||||
} else {
|
|
||||||
tracing::debug!("Follower deployment succeeded for {}", &input.instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::debug!("Starting executing contract {}", &input.instance);
|
|
||||||
|
|
||||||
let (leader_receipt, _, leader_diff) =
|
let (leader_receipt, _, leader_diff) =
|
||||||
match leader_state.execute_input(input, self.leader_node) {
|
match leader_state.execute_input(input, self.leader_node) {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(err) => {
|
Err(error) => {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"Leader execution failed for {}: {err}",
|
target = ?Target::Leader,
|
||||||
input.instance
|
?error,
|
||||||
|
"Contract execution failed"
|
||||||
);
|
);
|
||||||
continue;
|
return Err(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (follower_receipt, _, follower_diff) =
|
let (follower_receipt, _, follower_diff) =
|
||||||
match follower_state.execute_input(input, self.follower_node) {
|
match follower_state.execute_input(input, self.follower_node) {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(err) => {
|
Err(error) => {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"Follower execution failed for {}: {err}",
|
target = ?Target::Follower,
|
||||||
input.instance
|
?error,
|
||||||
|
"Contract execution failed"
|
||||||
);
|
);
|
||||||
continue;
|
return Err(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Ok((leader_receipt, leader_diff, follower_receipt, follower_diff))
|
||||||
|
});
|
||||||
|
let Ok((leader_receipt, leader_diff, follower_receipt, follower_diff)) =
|
||||||
|
execution_result
|
||||||
|
else {
|
||||||
|
// Noting it again here: if something in the input fails we do not move on
|
||||||
|
// to the next input, we move to the next case completely.
|
||||||
|
continue 'case_loop;
|
||||||
|
};
|
||||||
|
|
||||||
if leader_diff == follower_diff {
|
if leader_diff == follower_diff {
|
||||||
tracing::debug!("State diffs match between leader and follower.");
|
tracing::debug!("State diffs match between leader and follower.");
|
||||||
} else {
|
} else {
|
||||||
@@ -481,10 +598,238 @@ where
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: Only consider the case as having been successful after we have processed
|
||||||
|
// all of the inputs and completed the entire loop over the input.
|
||||||
|
execution_result.add_successful_case(
|
||||||
|
Target::Leader,
|
||||||
|
mode.clone(),
|
||||||
|
case.name.clone().unwrap_or("no case name".to_owned()),
|
||||||
|
case_idx,
|
||||||
|
);
|
||||||
|
execution_result.add_successful_case(
|
||||||
|
Target::Follower,
|
||||||
|
mode.clone(),
|
||||||
|
case.name.clone().unwrap_or("no case name".to_owned()),
|
||||||
|
case_idx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
execution_result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ExecutionResult {
|
||||||
|
pub results: Vec<Box<dyn ExecutionResultItem>>,
|
||||||
|
pub successful_cases_count: usize,
|
||||||
|
pub failed_cases_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecutionResult {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
results: Default::default(),
|
||||||
|
successful_cases_count: Default::default(),
|
||||||
|
failed_cases_count: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_successful_build(&mut self, target: Target, solc_mode: SolcMode) {
|
||||||
|
self.results
|
||||||
|
.push(Box::new(BuildResult::Success { target, solc_mode }));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_failed_build(&mut self, target: Target, solc_mode: SolcMode, error: anyhow::Error) {
|
||||||
|
self.results.push(Box::new(BuildResult::Failure {
|
||||||
|
target,
|
||||||
|
solc_mode,
|
||||||
|
error,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_successful_case(
|
||||||
|
&mut self,
|
||||||
|
target: Target,
|
||||||
|
solc_mode: SolcMode,
|
||||||
|
case_name: String,
|
||||||
|
case_idx: usize,
|
||||||
|
) {
|
||||||
|
self.successful_cases_count += 1;
|
||||||
|
self.results.push(Box::new(CaseResult::Success {
|
||||||
|
target,
|
||||||
|
solc_mode,
|
||||||
|
case_name,
|
||||||
|
case_idx,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_failed_case(
|
||||||
|
&mut self,
|
||||||
|
target: Target,
|
||||||
|
solc_mode: SolcMode,
|
||||||
|
case_name: String,
|
||||||
|
case_idx: usize,
|
||||||
|
input_idx: usize,
|
||||||
|
error: anyhow::Error,
|
||||||
|
) {
|
||||||
|
self.failed_cases_count += 1;
|
||||||
|
self.results.push(Box::new(CaseResult::Failure {
|
||||||
|
target,
|
||||||
|
solc_mode,
|
||||||
|
case_name,
|
||||||
|
case_idx,
|
||||||
|
error,
|
||||||
|
input_idx,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ExecutionResultItem: Debug {
|
||||||
|
/// Converts this result item into an [`anyhow::Result`].
|
||||||
|
fn as_result(&self) -> Result<(), &anyhow::Error>;
|
||||||
|
|
||||||
|
/// Provides information on whether the provided result item is of a success or failure.
|
||||||
|
fn is_success(&self) -> bool;
|
||||||
|
|
||||||
|
/// Provides information of the target that this result is for.
|
||||||
|
fn target(&self) -> &Target;
|
||||||
|
|
||||||
|
/// Provides information on the [`SolcMode`] mode that we being used for this result item.
|
||||||
|
fn solc_mode(&self) -> &SolcMode;
|
||||||
|
|
||||||
|
/// Provides information on the case name and number that this result item pertains to. This is
|
||||||
|
/// [`None`] if the error doesn't belong to any case (e.g., if it's a build error outside of any
|
||||||
|
/// of the cases.).
|
||||||
|
fn case_name_and_index(&self) -> Option<(&str, usize)>;
|
||||||
|
|
||||||
|
/// Provides information on the input number that this result item pertains to. This is [`None`]
|
||||||
|
/// if the error doesn't belong to any input (e.g., if it's a build error outside of any of the
|
||||||
|
/// inputs.).
|
||||||
|
fn input_index(&self) -> Option<usize>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum Target {
|
||||||
|
Leader,
|
||||||
|
Follower,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BuildResult {
|
||||||
|
Success {
|
||||||
|
target: Target,
|
||||||
|
solc_mode: SolcMode,
|
||||||
|
},
|
||||||
|
Failure {
|
||||||
|
target: Target,
|
||||||
|
solc_mode: SolcMode,
|
||||||
|
error: anyhow::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecutionResultItem for BuildResult {
|
||||||
|
fn as_result(&self) -> Result<(), &anyhow::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Success { .. } => Ok(()),
|
||||||
|
Self::Failure { error, .. } => Err(error)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_success(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Success { .. } => true,
|
||||||
|
Self::Failure { .. } => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target(&self) -> &Target {
|
||||||
|
match self {
|
||||||
|
Self::Success { target, .. } | Self::Failure { target, .. } => target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solc_mode(&self) -> &SolcMode {
|
||||||
|
match self {
|
||||||
|
Self::Success { solc_mode, .. } | Self::Failure { solc_mode, .. } => solc_mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn case_name_and_index(&self) -> Option<(&str, usize)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_index(&self) -> Option<usize> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CaseResult {
|
||||||
|
Success {
|
||||||
|
target: Target,
|
||||||
|
solc_mode: SolcMode,
|
||||||
|
case_name: String,
|
||||||
|
case_idx: usize,
|
||||||
|
},
|
||||||
|
Failure {
|
||||||
|
target: Target,
|
||||||
|
solc_mode: SolcMode,
|
||||||
|
case_name: String,
|
||||||
|
case_idx: usize,
|
||||||
|
input_idx: usize,
|
||||||
|
error: anyhow::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecutionResultItem for CaseResult {
|
||||||
|
fn as_result(&self) -> Result<(), &anyhow::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Success { .. } => Ok(()),
|
||||||
|
Self::Failure { error, .. } => Err(error)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_success(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Success { .. } => true,
|
||||||
|
Self::Failure { .. } => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target(&self) -> &Target {
|
||||||
|
match self {
|
||||||
|
Self::Success { target, .. } | Self::Failure { target, .. } => target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solc_mode(&self) -> &SolcMode {
|
||||||
|
match self {
|
||||||
|
Self::Success { solc_mode, .. } | Self::Failure { solc_mode, .. } => solc_mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn case_name_and_index(&self) -> Option<(&str, usize)> {
|
||||||
|
match self {
|
||||||
|
Self::Success {
|
||||||
|
case_name,
|
||||||
|
case_idx,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Self::Failure {
|
||||||
|
case_name,
|
||||||
|
case_idx,
|
||||||
|
..
|
||||||
|
} => Some((case_name, *case_idx)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_index(&self) -> Option<usize> {
|
||||||
|
match self {
|
||||||
|
CaseResult::Success { .. } => None,
|
||||||
|
CaseResult::Failure { input_idx, .. } => Some(*input_idx),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-11
@@ -13,7 +13,7 @@ use revive_dt_node::pool::NodePool;
|
|||||||
use revive_dt_report::reporter::{Report, Span};
|
use revive_dt_report::reporter::{Report, Span};
|
||||||
use temp_dir::TempDir;
|
use temp_dir::TempDir;
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
use tracing_subscriber::{EnvFilter, FmtSubscriber, fmt::format::FmtSpan};
|
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||||
|
|
||||||
static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
|
static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ fn init_cli() -> anyhow::Result<Arguments> {
|
|||||||
.with_thread_ids(true)
|
.with_thread_ids(true)
|
||||||
.with_thread_names(true)
|
.with_thread_names(true)
|
||||||
.with_env_filter(EnvFilter::from_default_env())
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
.with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE)
|
.with_ansi(false)
|
||||||
.pretty()
|
.pretty()
|
||||||
.finish();
|
.finish();
|
||||||
tracing::subscriber::set_global_default(subscriber)?;
|
tracing::subscriber::set_global_default(subscriber)?;
|
||||||
@@ -116,19 +116,24 @@ where
|
|||||||
follower_nodes.round_robbin(),
|
follower_nodes.round_robbin(),
|
||||||
);
|
);
|
||||||
|
|
||||||
match driver.execute(span) {
|
let execution_result = driver.execute(span);
|
||||||
Ok(_) => {
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"metadata {} success",
|
case_success_count = execution_result.successful_cases_count,
|
||||||
metadata.directory().as_ref().unwrap().display()
|
case_failure_count = execution_result.failed_cases_count,
|
||||||
|
"Execution completed"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut error_count = 0;
|
||||||
|
for result in execution_result.results.iter() {
|
||||||
|
if !result.is_success() {
|
||||||
|
tracing::error!(execution_error = ?result, "Encountered an error");
|
||||||
|
error_count += 1;
|
||||||
}
|
}
|
||||||
Err(error) => {
|
|
||||||
tracing::warn!(
|
|
||||||
"metadata {} failure: {error:?}",
|
|
||||||
metadata.file_path.as_ref().unwrap().display()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
if error_count == 0 {
|
||||||
|
tracing::info!("Execution succeeded");
|
||||||
|
} else {
|
||||||
|
tracing::info!("Execution failed");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ repository.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
revive-dt-node-interaction = { workspace = true }
|
||||||
|
|
||||||
alloy = { workspace = true }
|
alloy = { workspace = true }
|
||||||
alloy-primitives = { workspace = true }
|
alloy-primitives = { workspace = true }
|
||||||
alloy-sol-types = { workspace = true }
|
alloy-sol-types = { workspace = true }
|
||||||
|
|||||||
+230
-24
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
|
eips::BlockNumberOrTag,
|
||||||
json_abi::JsonAbi,
|
json_abi::JsonAbi,
|
||||||
network::TransactionBuilder,
|
network::TransactionBuilder,
|
||||||
primitives::{Address, Bytes, U256},
|
primitives::{Address, Bytes, U256},
|
||||||
@@ -10,6 +11,8 @@ use semver::VersionReq;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
#[serde(default = "default_caller")]
|
#[serde(default = "default_caller")]
|
||||||
@@ -84,6 +87,7 @@ impl Input {
|
|||||||
&self,
|
&self,
|
||||||
deployed_abis: &HashMap<String, JsonAbi>,
|
deployed_abis: &HashMap<String, JsonAbi>,
|
||||||
deployed_contracts: &HashMap<String, Address>,
|
deployed_contracts: &HashMap<String, Address>,
|
||||||
|
chain_state_provider: &impl EthereumNode,
|
||||||
) -> anyhow::Result<Bytes> {
|
) -> anyhow::Result<Bytes> {
|
||||||
let Method::FunctionName(ref function_name) = self.method else {
|
let Method::FunctionName(ref function_name) = self.method else {
|
||||||
return Ok(Bytes::default()); // fallback or deployer — no input
|
return Ok(Bytes::default()); // fallback or deployer — no input
|
||||||
@@ -145,7 +149,7 @@ impl Input {
|
|||||||
calldata.extend(function.selector().0);
|
calldata.extend(function.selector().0);
|
||||||
|
|
||||||
for (arg_idx, arg) in calldata_args.iter().enumerate() {
|
for (arg_idx, arg) in calldata_args.iter().enumerate() {
|
||||||
match resolve_argument(arg, deployed_contracts) {
|
match resolve_argument(arg, deployed_contracts, chain_state_provider) {
|
||||||
Ok(resolved) => {
|
Ok(resolved) => {
|
||||||
calldata.extend(resolved.to_be_bytes::<32>());
|
calldata.extend(resolved.to_be_bytes::<32>());
|
||||||
}
|
}
|
||||||
@@ -165,8 +169,10 @@ impl Input {
|
|||||||
nonce: u64,
|
nonce: u64,
|
||||||
deployed_contracts: &HashMap<String, Address>,
|
deployed_contracts: &HashMap<String, Address>,
|
||||||
deployed_abis: &HashMap<String, JsonAbi>,
|
deployed_abis: &HashMap<String, JsonAbi>,
|
||||||
|
chain_state_provider: &impl EthereumNode,
|
||||||
) -> anyhow::Result<TransactionRequest> {
|
) -> anyhow::Result<TransactionRequest> {
|
||||||
let input_data = self.encoded_input(deployed_abis, deployed_contracts)?;
|
let input_data =
|
||||||
|
self.encoded_input(deployed_abis, deployed_contracts, chain_state_provider)?;
|
||||||
let transaction_request = TransactionRequest::default().nonce(nonce);
|
let transaction_request = TransactionRequest::default().nonce(nonce);
|
||||||
match self.method {
|
match self.method {
|
||||||
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
|
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
|
||||||
@@ -196,6 +202,7 @@ fn default_caller() -> Address {
|
|||||||
fn resolve_argument(
|
fn resolve_argument(
|
||||||
value: &str,
|
value: &str,
|
||||||
deployed_contracts: &HashMap<String, Address>,
|
deployed_contracts: &HashMap<String, Address>,
|
||||||
|
chain_state_provider: &impl EthereumNode,
|
||||||
) -> anyhow::Result<U256> {
|
) -> anyhow::Result<U256> {
|
||||||
if let Some(instance) = value.strip_suffix(".address") {
|
if let Some(instance) = value.strip_suffix(".address") {
|
||||||
Ok(U256::from_be_slice(
|
Ok(U256::from_be_slice(
|
||||||
@@ -217,32 +224,42 @@ fn resolve_argument(
|
|||||||
} else if let Some(value) = value.strip_prefix("0x") {
|
} else if let Some(value) = value.strip_prefix("0x") {
|
||||||
Ok(U256::from_str_radix(value, 16)
|
Ok(U256::from_str_radix(value, 16)
|
||||||
.map_err(|error| anyhow::anyhow!("Invalid hexadecimal literal: {}", error))?)
|
.map_err(|error| anyhow::anyhow!("Invalid hexadecimal literal: {}", error))?)
|
||||||
} else {
|
} else if value == "$CHAIN_ID" {
|
||||||
// TODO: This is a set of "variables" that we need to be able to resolve to be fully in
|
let chain_id = chain_state_provider.chain_id()?;
|
||||||
// compliance with the matter labs tester but we currently do not resolve them. We need to
|
Ok(U256::from(chain_id))
|
||||||
// add logic that does their resolution in the future, perhaps through some kind of system
|
} else if value == "$GAS_LIMIT" {
|
||||||
// context API that we pass down to the resolution function that allows it to make calls to
|
let gas_limit = chain_state_provider.block_gas_limit(BlockNumberOrTag::Latest)?;
|
||||||
// the node to perform these resolutions.
|
Ok(U256::from(gas_limit))
|
||||||
let is_unsupported = [
|
} else if value == "$COINBASE" {
|
||||||
"$CHAIN_ID",
|
let coinbase = chain_state_provider.block_coinbase(BlockNumberOrTag::Latest)?;
|
||||||
"$GAS_LIMIT",
|
Ok(U256::from_be_slice(coinbase.as_ref()))
|
||||||
"$COINBASE",
|
} else if value == "$DIFFICULTY" {
|
||||||
"$DIFFICULTY",
|
let block_difficulty = chain_state_provider.block_difficulty(BlockNumberOrTag::Latest)?;
|
||||||
"$BLOCK_HASH",
|
Ok(block_difficulty)
|
||||||
"$BLOCK_TIMESTAMP",
|
} else if value.starts_with("$BLOCK_HASH") {
|
||||||
]
|
let offset: u64 = value
|
||||||
.iter()
|
.split(':')
|
||||||
.any(|var| value.starts_with(var));
|
.next_back()
|
||||||
|
.and_then(|value| value.parse().ok())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
if is_unsupported {
|
let current_block_number = chain_state_provider.last_block_number()?;
|
||||||
tracing::error!(value, "Unsupported variable used");
|
let desired_block_number = current_block_number - offset;
|
||||||
anyhow::bail!("Encountered {value} which is currently unsupported by the framework");
|
|
||||||
|
let block_hash = chain_state_provider.block_hash(desired_block_number.into())?;
|
||||||
|
|
||||||
|
Ok(U256::from_be_bytes(block_hash.0))
|
||||||
|
} else if value == "$BLOCK_NUMBER" {
|
||||||
|
let current_block_number = chain_state_provider.last_block_number()?;
|
||||||
|
Ok(U256::from(current_block_number))
|
||||||
|
} else if value == "$BLOCK_TIMESTAMP" {
|
||||||
|
let timestamp = chain_state_provider.block_timestamp(BlockNumberOrTag::Latest)?;
|
||||||
|
Ok(U256::from(timestamp))
|
||||||
} else {
|
} else {
|
||||||
Ok(U256::from_str_radix(value, 10)
|
Ok(U256::from_str_radix(value, 10)
|
||||||
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?)
|
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@@ -253,6 +270,69 @@ mod tests {
|
|||||||
use alloy_sol_types::SolValue;
|
use alloy_sol_types::SolValue;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
struct DummyEthereumNode;
|
||||||
|
|
||||||
|
impl EthereumNode for DummyEthereumNode {
|
||||||
|
fn execute_transaction(
|
||||||
|
&self,
|
||||||
|
_: TransactionRequest,
|
||||||
|
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trace_transaction(
|
||||||
|
&self,
|
||||||
|
_: alloy::rpc::types::TransactionReceipt,
|
||||||
|
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state_diff(
|
||||||
|
&self,
|
||||||
|
_: alloy::rpc::types::TransactionReceipt,
|
||||||
|
) -> anyhow::Result<alloy::rpc::types::trace::geth::DiffMode> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_add_nonce(&self, _: Address) -> anyhow::Result<u64> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chain_id(&self) -> anyhow::Result<alloy_primitives::ChainId> {
|
||||||
|
Ok(0x123)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_gas_limit(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<u128> {
|
||||||
|
Ok(0x1234)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_coinbase(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<Address> {
|
||||||
|
Ok(Address::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_difficulty(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<U256> {
|
||||||
|
Ok(U256::from(0x12345u128))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_hash(
|
||||||
|
&self,
|
||||||
|
_: alloy::eips::BlockNumberOrTag,
|
||||||
|
) -> anyhow::Result<alloy_primitives::BlockHash> {
|
||||||
|
Ok([0xEE; 32].into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_timestamp(
|
||||||
|
&self,
|
||||||
|
_: alloy::eips::BlockNumberOrTag,
|
||||||
|
) -> anyhow::Result<alloy_primitives::BlockTimestamp> {
|
||||||
|
Ok(0x123456)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_block_number(&self) -> anyhow::Result<alloy_primitives::BlockNumber> {
|
||||||
|
Ok(0x1234567)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encoded_input_uint256() {
|
fn test_encoded_input_uint256() {
|
||||||
let raw_metadata = r#"
|
let raw_metadata = r#"
|
||||||
@@ -288,7 +368,7 @@ mod tests {
|
|||||||
let deployed_contracts = HashMap::new();
|
let deployed_contracts = HashMap::new();
|
||||||
|
|
||||||
let encoded = input
|
let encoded = input
|
||||||
.encoded_input(&deployed_abis, &deployed_contracts)
|
.encoded_input(&deployed_abis, &deployed_contracts, &DummyEthereumNode)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(encoded.0.starts_with(&selector));
|
assert!(encoded.0.starts_with(&selector));
|
||||||
|
|
||||||
@@ -331,7 +411,9 @@ mod tests {
|
|||||||
abis.insert("Contract".to_string(), parsed_abi);
|
abis.insert("Contract".to_string(), parsed_abi);
|
||||||
let contracts = HashMap::new();
|
let contracts = HashMap::new();
|
||||||
|
|
||||||
let encoded = input.encoded_input(&abis, &contracts).unwrap();
|
let encoded = input
|
||||||
|
.encoded_input(&abis, &contracts, &DummyEthereumNode)
|
||||||
|
.unwrap();
|
||||||
assert!(encoded.0.starts_with(&selector));
|
assert!(encoded.0.starts_with(&selector));
|
||||||
|
|
||||||
type T = (alloy_primitives::Address,);
|
type T = (alloy_primitives::Address,);
|
||||||
@@ -341,4 +423,128 @@ mod tests {
|
|||||||
address!("0x1000000000000000000000000000000000000001")
|
address!("0x1000000000000000000000000000000000000001")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolver_can_resolve_chain_id_variable() {
|
||||||
|
// Arrange
|
||||||
|
let input = "$CHAIN_ID";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let resolved = resolved.expect("Failed to resolve argument");
|
||||||
|
assert_eq!(resolved, U256::from(DummyEthereumNode.chain_id().unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolver_can_resolve_gas_limit_variable() {
|
||||||
|
// Arrange
|
||||||
|
let input = "$GAS_LIMIT";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let resolved = resolved.expect("Failed to resolve argument");
|
||||||
|
assert_eq!(
|
||||||
|
resolved,
|
||||||
|
U256::from(
|
||||||
|
DummyEthereumNode
|
||||||
|
.block_gas_limit(Default::default())
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolver_can_resolve_coinbase_variable() {
|
||||||
|
// Arrange
|
||||||
|
let input = "$COINBASE";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let resolved = resolved.expect("Failed to resolve argument");
|
||||||
|
assert_eq!(
|
||||||
|
resolved,
|
||||||
|
U256::from_be_slice(
|
||||||
|
DummyEthereumNode
|
||||||
|
.block_coinbase(Default::default())
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolver_can_resolve_block_difficulty_variable() {
|
||||||
|
// Arrange
|
||||||
|
let input = "$DIFFICULTY";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let resolved = resolved.expect("Failed to resolve argument");
|
||||||
|
assert_eq!(
|
||||||
|
resolved,
|
||||||
|
DummyEthereumNode
|
||||||
|
.block_difficulty(Default::default())
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolver_can_resolve_block_hash_variable() {
|
||||||
|
// Arrange
|
||||||
|
let input = "$BLOCK_HASH";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let resolved = resolved.expect("Failed to resolve argument");
|
||||||
|
assert_eq!(
|
||||||
|
resolved,
|
||||||
|
U256::from_be_bytes(DummyEthereumNode.block_hash(Default::default()).unwrap().0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolver_can_resolve_block_number_variable() {
|
||||||
|
// Arrange
|
||||||
|
let input = "$BLOCK_NUMBER";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let resolved = resolved.expect("Failed to resolve argument");
|
||||||
|
assert_eq!(
|
||||||
|
resolved,
|
||||||
|
U256::from(DummyEthereumNode.last_block_number().unwrap())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolver_can_resolve_block_timestamp_variable() {
|
||||||
|
// Arrange
|
||||||
|
let input = "$BLOCK_TIMESTAMP";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let resolved = resolved.expect("Failed to resolve argument");
|
||||||
|
assert_eq!(
|
||||||
|
resolved,
|
||||||
|
U256::from(
|
||||||
|
DummyEthereumNode
|
||||||
|
.block_timestamp(Default::default())
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,6 @@ impl BlockingExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the state of the async runtime. This runtime is designed to be a singleton runtime
|
/// Represents the state of the async runtime. This runtime is designed to be a singleton runtime
|
||||||
/// which means that in the current running program there's just a single thread that has an async
|
/// which means that in the current running program there's just a single thread that has an async
|
||||||
/// runtime.
|
/// runtime.
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
//! This crate implements all node interactions.
|
//! This crate implements all node interactions.
|
||||||
|
|
||||||
use alloy::primitives::Address;
|
use alloy::eips::BlockNumberOrTag;
|
||||||
|
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
|
||||||
use alloy::rpc::types::trace::geth::{DiffMode, GethTrace};
|
use alloy::rpc::types::trace::geth::{DiffMode, GethTrace};
|
||||||
use alloy::rpc::types::{TransactionReceipt, TransactionRequest};
|
use alloy::rpc::types::{TransactionReceipt, TransactionRequest};
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
mod blocking_executor;
|
mod blocking_executor;
|
||||||
pub use blocking_executor::*;
|
pub use blocking_executor::*;
|
||||||
@@ -10,17 +12,37 @@ pub use blocking_executor::*;
|
|||||||
/// An interface for all interactions with Ethereum compatible nodes.
|
/// An interface for all interactions with Ethereum compatible nodes.
|
||||||
pub trait EthereumNode {
|
pub trait EthereumNode {
|
||||||
/// Execute the [TransactionRequest] and return a [TransactionReceipt].
|
/// Execute the [TransactionRequest] and return a [TransactionReceipt].
|
||||||
fn execute_transaction(
|
fn execute_transaction(&self, transaction: TransactionRequest) -> Result<TransactionReceipt>;
|
||||||
&self,
|
|
||||||
transaction: TransactionRequest,
|
|
||||||
) -> anyhow::Result<TransactionReceipt>;
|
|
||||||
|
|
||||||
/// Trace the transaction in the [TransactionReceipt] and return a [GethTrace].
|
/// Trace the transaction in the [TransactionReceipt] and return a [GethTrace].
|
||||||
fn trace_transaction(&self, transaction: TransactionReceipt) -> anyhow::Result<GethTrace>;
|
fn trace_transaction(&self, transaction: TransactionReceipt) -> Result<GethTrace>;
|
||||||
|
|
||||||
/// Returns the state diff of the transaction hash in the [TransactionReceipt].
|
/// Returns the state diff of the transaction hash in the [TransactionReceipt].
|
||||||
fn state_diff(&self, transaction: TransactionReceipt) -> anyhow::Result<DiffMode>;
|
fn state_diff(&self, transaction: TransactionReceipt) -> Result<DiffMode>;
|
||||||
|
|
||||||
/// Returns the next available nonce for the given [Address].
|
/// Returns the next available nonce for the given [Address].
|
||||||
fn fetch_add_nonce(&self, address: Address) -> anyhow::Result<u64>;
|
fn fetch_add_nonce(&self, address: Address) -> Result<u64>;
|
||||||
|
|
||||||
|
/// Returns the ID of the chain that the node is on.
|
||||||
|
fn chain_id(&self) -> Result<ChainId>;
|
||||||
|
|
||||||
|
// TODO: This is currently a u128 due to Kitchensink needing more than 64 bits for its gas limit
|
||||||
|
// when we implement the changes to the gas we need to adjust this to be a u64.
|
||||||
|
/// Returns the gas limit of the specified block.
|
||||||
|
fn block_gas_limit(&self, number: BlockNumberOrTag) -> Result<u128>;
|
||||||
|
|
||||||
|
/// Returns the coinbase of the specified block.
|
||||||
|
fn block_coinbase(&self, number: BlockNumberOrTag) -> Result<Address>;
|
||||||
|
|
||||||
|
/// Returns the difficulty of the specified block.
|
||||||
|
fn block_difficulty(&self, number: BlockNumberOrTag) -> Result<U256>;
|
||||||
|
|
||||||
|
/// Returns the hash of the specified block.
|
||||||
|
fn block_hash(&self, number: BlockNumberOrTag) -> Result<BlockHash>;
|
||||||
|
|
||||||
|
/// Returns the timestamp of the specified block,
|
||||||
|
fn block_timestamp(&self, number: BlockNumberOrTag) -> Result<BlockTimestamp>;
|
||||||
|
|
||||||
|
/// Returns the number of the last block.
|
||||||
|
fn last_block_number(&self) -> Result<BlockNumber>;
|
||||||
}
|
}
|
||||||
|
|||||||
+213
-22
@@ -14,9 +14,14 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
network::EthereumWallet,
|
eips::BlockNumberOrTag,
|
||||||
primitives::Address,
|
network::{Ethereum, EthereumWallet},
|
||||||
providers::{Provider, ProviderBuilder, ext::DebugApi},
|
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, U256},
|
||||||
|
providers::{
|
||||||
|
Provider, ProviderBuilder,
|
||||||
|
ext::DebugApi,
|
||||||
|
fillers::{FillProvider, TxFiller},
|
||||||
|
},
|
||||||
rpc::types::{
|
rpc::types::{
|
||||||
TransactionReceipt, TransactionRequest,
|
TransactionReceipt, TransactionRequest,
|
||||||
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
||||||
@@ -191,6 +196,24 @@ impl Instance {
|
|||||||
fn geth_stderr_log_file_path(&self) -> PathBuf {
|
fn geth_stderr_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory.join(Self::GETH_STDERR_LOG_FILE_NAME)
|
self.logs_directory.join(Self::GETH_STDERR_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn provider(
|
||||||
|
&self,
|
||||||
|
) -> impl Future<
|
||||||
|
Output = anyhow::Result<
|
||||||
|
FillProvider<impl TxFiller<Ethereum>, impl Provider<Ethereum>, Ethereum>,
|
||||||
|
>,
|
||||||
|
> + 'static {
|
||||||
|
let connection_string = self.connection_string();
|
||||||
|
let wallet = self.wallet.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
ProviderBuilder::new()
|
||||||
|
.wallet(wallet)
|
||||||
|
.connect(&connection_string)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EthereumNode for Instance {
|
impl EthereumNode for Instance {
|
||||||
@@ -199,17 +222,12 @@ impl EthereumNode for Instance {
|
|||||||
&self,
|
&self,
|
||||||
transaction: TransactionRequest,
|
transaction: TransactionRequest,
|
||||||
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
||||||
let connection_string = self.connection_string();
|
let provider = self.provider();
|
||||||
let wallet = self.wallet.clone();
|
|
||||||
|
|
||||||
BlockingExecutor::execute(async move {
|
BlockingExecutor::execute(async move {
|
||||||
let outer_span = tracing::debug_span!("Submitting transaction", ?transaction,);
|
let outer_span = tracing::debug_span!("Submitting transaction", ?transaction,);
|
||||||
let _outer_guard = outer_span.enter();
|
let _outer_guard = outer_span.enter();
|
||||||
|
|
||||||
let provider = ProviderBuilder::new()
|
let provider = provider.await?;
|
||||||
.wallet(wallet)
|
|
||||||
.connect(&connection_string)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let pending_transaction = provider.send_transaction(transaction).await?;
|
let pending_transaction = provider.send_transaction(transaction).await?;
|
||||||
let transaction_hash = pending_transaction.tx_hash();
|
let transaction_hash = pending_transaction.tx_hash();
|
||||||
@@ -289,18 +307,15 @@ impl EthereumNode for Instance {
|
|||||||
&self,
|
&self,
|
||||||
transaction: TransactionReceipt,
|
transaction: TransactionReceipt,
|
||||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
||||||
let connection_string = self.connection_string();
|
|
||||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||||
diff_mode: Some(true),
|
diff_mode: Some(true),
|
||||||
disable_code: None,
|
disable_code: None,
|
||||||
disable_storage: None,
|
disable_storage: None,
|
||||||
});
|
});
|
||||||
let wallet = self.wallet.clone();
|
let provider = self.provider();
|
||||||
|
|
||||||
BlockingExecutor::execute(async move {
|
BlockingExecutor::execute(async move {
|
||||||
Ok(ProviderBuilder::new()
|
Ok(provider
|
||||||
.wallet(wallet)
|
|
||||||
.connect(&connection_string)
|
|
||||||
.await?
|
.await?
|
||||||
.debug_trace_transaction(transaction.transaction_hash, trace_options)
|
.debug_trace_transaction(transaction.transaction_hash, trace_options)
|
||||||
.await?)
|
.await?)
|
||||||
@@ -323,13 +338,9 @@ impl EthereumNode for Instance {
|
|||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
fn fetch_add_nonce(&self, address: Address) -> anyhow::Result<u64> {
|
fn fetch_add_nonce(&self, address: Address) -> anyhow::Result<u64> {
|
||||||
let connection_string = self.connection_string.clone();
|
let provider = self.provider();
|
||||||
let wallet = self.wallet.clone();
|
|
||||||
|
|
||||||
let onchain_nonce = BlockingExecutor::execute::<anyhow::Result<_>>(async move {
|
let onchain_nonce = BlockingExecutor::execute::<anyhow::Result<_>>(async move {
|
||||||
ProviderBuilder::new()
|
provider
|
||||||
.wallet(wallet)
|
|
||||||
.connect(&connection_string)
|
|
||||||
.await?
|
.await?
|
||||||
.get_transaction_count(address)
|
.get_transaction_count(address)
|
||||||
.await
|
.await
|
||||||
@@ -342,6 +353,87 @@ impl EthereumNode for Instance {
|
|||||||
*current += 1;
|
*current += 1;
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider.await?.get_chain_id().await.map_err(Into::into)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.gas_limit as _)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.beneficiary)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.difficulty)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.hash)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.timestamp)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider.await?.get_block_number().await.map_err(Into::into)
|
||||||
|
})?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node for Instance {
|
impl Node for Instance {
|
||||||
@@ -429,7 +521,7 @@ mod tests {
|
|||||||
|
|
||||||
use crate::{GENESIS_JSON, Node};
|
use crate::{GENESIS_JSON, Node};
|
||||||
|
|
||||||
use super::Instance;
|
use super::*;
|
||||||
|
|
||||||
fn test_config() -> (Arguments, TempDir) {
|
fn test_config() -> (Arguments, TempDir) {
|
||||||
let mut config = Arguments::default();
|
let mut config = Arguments::default();
|
||||||
@@ -439,6 +531,16 @@ mod tests {
|
|||||||
(config, temp_dir)
|
(config, temp_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_node() -> (Instance, TempDir) {
|
||||||
|
let (args, temp_dir) = test_config();
|
||||||
|
let mut node = Instance::new(&args);
|
||||||
|
node.init(GENESIS_JSON.to_owned())
|
||||||
|
.expect("Failed to initialize the node")
|
||||||
|
.spawn_process()
|
||||||
|
.expect("Failed to spawn the node process");
|
||||||
|
(node, temp_dir)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init_works() {
|
fn init_works() {
|
||||||
Instance::new(&test_config().0)
|
Instance::new(&test_config().0)
|
||||||
@@ -461,4 +563,93 @@ mod tests {
|
|||||||
"expected version string, got: '{version}'"
|
"expected version string, got: '{version}'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_chain_id_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let (node, _temp_dir) = new_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let chain_id = node.chain_id();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let chain_id = chain_id.expect("Failed to get the chain id");
|
||||||
|
assert_eq!(chain_id, 420_420_420);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_gas_limit_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let (node, _temp_dir) = new_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let gas_limit = node.block_gas_limit(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let gas_limit = gas_limit.expect("Failed to get the gas limit");
|
||||||
|
assert_eq!(gas_limit, u32::MAX as u128)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_coinbase_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let (node, _temp_dir) = new_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let coinbase = node.block_coinbase(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let coinbase = coinbase.expect("Failed to get the coinbase");
|
||||||
|
assert_eq!(coinbase, Address::new([0xFF; 20]))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_block_difficulty_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let (node, _temp_dir) = new_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let block_difficulty = node.block_difficulty(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let block_difficulty = block_difficulty.expect("Failed to get the block difficulty");
|
||||||
|
assert_eq!(block_difficulty, U256::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_block_hash_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let (node, _temp_dir) = new_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let block_hash = node.block_hash(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let _ = block_hash.expect("Failed to get the block hash");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_block_timestamp_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let (node, _temp_dir) = new_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let block_timestamp = node.block_timestamp(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let _ = block_timestamp.expect("Failed to get the block timestamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_block_number_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let (node, _temp_dir) = new_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let block_number = node.last_block_number();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let block_number = block_number.expect("Failed to get the block number");
|
||||||
|
assert_eq!(block_number, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+247
-35
@@ -13,13 +13,18 @@ use std::{
|
|||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
consensus::{BlockHeader, TxEnvelope},
|
consensus::{BlockHeader, TxEnvelope},
|
||||||
|
eips::BlockNumberOrTag,
|
||||||
hex,
|
hex,
|
||||||
network::{
|
network::{
|
||||||
Ethereum, EthereumWallet, Network, TransactionBuilder, TransactionBuilderError,
|
Ethereum, EthereumWallet, Network, TransactionBuilder, TransactionBuilderError,
|
||||||
UnbuiltTransactionError,
|
UnbuiltTransactionError,
|
||||||
},
|
},
|
||||||
primitives::{Address, B64, B256, BlockNumber, Bloom, Bytes, U256},
|
primitives::{Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, U256},
|
||||||
providers::{Provider, ProviderBuilder, ext::DebugApi},
|
providers::{
|
||||||
|
Provider, ProviderBuilder,
|
||||||
|
ext::DebugApi,
|
||||||
|
fillers::{FillProvider, TxFiller},
|
||||||
|
},
|
||||||
rpc::types::{
|
rpc::types::{
|
||||||
TransactionReceipt,
|
TransactionReceipt,
|
||||||
eth::{Block, Header, Transaction},
|
eth::{Block, Header, Transaction},
|
||||||
@@ -232,6 +237,7 @@ impl KitchensinkNode {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
||||||
fn extract_balance_from_genesis_file(
|
fn extract_balance_from_genesis_file(
|
||||||
&self,
|
&self,
|
||||||
@@ -330,6 +336,29 @@ impl KitchensinkNode {
|
|||||||
fn proxy_stderr_log_file_path(&self) -> PathBuf {
|
fn proxy_stderr_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory.join(Self::PROXY_STDERR_LOG_FILE_NAME)
|
self.logs_directory.join(Self::PROXY_STDERR_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn provider(
|
||||||
|
&self,
|
||||||
|
) -> impl Future<
|
||||||
|
Output = anyhow::Result<
|
||||||
|
FillProvider<
|
||||||
|
impl TxFiller<KitchenSinkNetwork>,
|
||||||
|
impl Provider<KitchenSinkNetwork>,
|
||||||
|
KitchenSinkNetwork,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
> + 'static {
|
||||||
|
let connection_string = self.connection_string();
|
||||||
|
let wallet = self.wallet.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
ProviderBuilder::new()
|
||||||
|
.network::<KitchenSinkNetwork>()
|
||||||
|
.wallet(wallet)
|
||||||
|
.connect(&connection_string)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EthereumNode for KitchensinkNode {
|
impl EthereumNode for KitchensinkNode {
|
||||||
@@ -338,17 +367,10 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
&self,
|
&self,
|
||||||
transaction: alloy::rpc::types::TransactionRequest,
|
transaction: alloy::rpc::types::TransactionRequest,
|
||||||
) -> anyhow::Result<TransactionReceipt> {
|
) -> anyhow::Result<TransactionReceipt> {
|
||||||
let url = self.rpc_url.clone();
|
tracing::debug!(?transaction, "Submitting transaction");
|
||||||
let wallet = self.wallet.clone();
|
let provider = self.provider();
|
||||||
|
|
||||||
tracing::debug!("Submitting transaction: {transaction:#?}");
|
|
||||||
|
|
||||||
tracing::info!("Submitting tx to kitchensink");
|
|
||||||
let receipt = BlockingExecutor::execute(async move {
|
let receipt = BlockingExecutor::execute(async move {
|
||||||
Ok(ProviderBuilder::new()
|
Ok(provider
|
||||||
.network::<KitchenSinkNetwork>()
|
|
||||||
.wallet(wallet)
|
|
||||||
.connect(&url)
|
|
||||||
.await?
|
.await?
|
||||||
.send_transaction(transaction)
|
.send_transaction(transaction)
|
||||||
.await?
|
.await?
|
||||||
@@ -364,20 +386,15 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
&self,
|
&self,
|
||||||
transaction: TransactionReceipt,
|
transaction: TransactionReceipt,
|
||||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
||||||
let url = self.rpc_url.clone();
|
|
||||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||||
diff_mode: Some(true),
|
diff_mode: Some(true),
|
||||||
disable_code: None,
|
disable_code: None,
|
||||||
disable_storage: None,
|
disable_storage: None,
|
||||||
});
|
});
|
||||||
|
let provider = self.provider();
|
||||||
let wallet = self.wallet.clone();
|
|
||||||
|
|
||||||
BlockingExecutor::execute(async move {
|
BlockingExecutor::execute(async move {
|
||||||
Ok(ProviderBuilder::new()
|
Ok(provider
|
||||||
.network::<KitchenSinkNetwork>()
|
|
||||||
.wallet(wallet)
|
|
||||||
.connect(&url)
|
|
||||||
.await?
|
.await?
|
||||||
.debug_trace_transaction(transaction.transaction_hash, trace_options)
|
.debug_trace_transaction(transaction.transaction_hash, trace_options)
|
||||||
.await?)
|
.await?)
|
||||||
@@ -397,13 +414,9 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
||||||
fn fetch_add_nonce(&self, address: Address) -> anyhow::Result<u64> {
|
fn fetch_add_nonce(&self, address: Address) -> anyhow::Result<u64> {
|
||||||
let url = self.rpc_url.clone();
|
let provider = self.provider();
|
||||||
let wallet = self.wallet.clone();
|
|
||||||
|
|
||||||
let onchain_nonce = BlockingExecutor::execute::<anyhow::Result<_>>(async move {
|
let onchain_nonce = BlockingExecutor::execute::<anyhow::Result<_>>(async move {
|
||||||
ProviderBuilder::new()
|
provider
|
||||||
.wallet(wallet)
|
|
||||||
.connect(&url)
|
|
||||||
.await?
|
.await?
|
||||||
.get_transaction_count(address)
|
.get_transaction_count(address)
|
||||||
.await
|
.await
|
||||||
@@ -416,6 +429,87 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
*current += 1;
|
*current += 1;
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider.await?.get_chain_id().await.map_err(Into::into)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.gas_limit)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.beneficiary)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.difficulty)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.hash)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider
|
||||||
|
.await?
|
||||||
|
.get_block_by_number(number)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::Error::msg("Blockchain has no blocks"))
|
||||||
|
.map(|block| block.header.timestamp)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
||||||
|
let provider = self.provider();
|
||||||
|
BlockingExecutor::execute(async move {
|
||||||
|
provider.await?.get_block_number().await.map_err(Into::into)
|
||||||
|
})?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node for KitchensinkNode {
|
impl Node for KitchensinkNode {
|
||||||
@@ -926,6 +1020,7 @@ mod tests {
|
|||||||
use alloy::rpc::types::TransactionRequest;
|
use alloy::rpc::types::TransactionRequest;
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use temp_dir::TempDir;
|
use temp_dir::TempDir;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -945,20 +1040,49 @@ mod tests {
|
|||||||
(config, temp_dir)
|
(config, temp_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_node() -> (KitchensinkNode, Arguments, TempDir) {
|
||||||
|
// Note: When we run the tests in the CI we found that if they're all
|
||||||
|
// run in parallel then the CI is unable to start all of the nodes in
|
||||||
|
// time and their start up times-out. Therefore, we want all of the
|
||||||
|
// nodes to be started in series and not in parallel. To do this, we use
|
||||||
|
// a dummy mutex here such that there can only be a single node being
|
||||||
|
// started up at any point of time. This will make our tests run slower
|
||||||
|
// but it will allow the node startup to not timeout.
|
||||||
|
//
|
||||||
|
// Note: an alternative to starting all of the nodes in series and not
|
||||||
|
// in parallel would be for us to reuse the same node between tests
|
||||||
|
// which is not the best thing to do in my opinion as it removes all
|
||||||
|
// of the isolation between tests and makes them depend on what other
|
||||||
|
// tests do. For example, if one test checks what the block number is
|
||||||
|
// and another test submits a transaction then the tx test would have
|
||||||
|
// side effects that affect the block number test.
|
||||||
|
static NODE_START_MUTEX: Mutex<()> = Mutex::new(());
|
||||||
|
let _guard = NODE_START_MUTEX.lock().unwrap();
|
||||||
|
|
||||||
|
let (args, temp_dir) = test_config();
|
||||||
|
let mut node = KitchensinkNode::new(&args);
|
||||||
|
node.init(GENESIS_JSON)
|
||||||
|
.expect("Failed to initialize the node")
|
||||||
|
.spawn_process()
|
||||||
|
.expect("Failed to spawn the node process");
|
||||||
|
(node, args, temp_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shared node that multiple tests can use. It starts up once.
|
||||||
|
fn shared_node() -> &'static KitchensinkNode {
|
||||||
|
static NODE: LazyLock<(KitchensinkNode, TempDir)> = LazyLock::new(|| {
|
||||||
|
let (node, _, temp_dir) = new_node();
|
||||||
|
(node, temp_dir)
|
||||||
|
});
|
||||||
|
&NODE.0
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn node_mines_simple_transfer_transaction_and_returns_receipt() {
|
async fn node_mines_simple_transfer_transaction_and_returns_receipt() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let (args, _temp_dir) = test_config();
|
let (node, args, _temp_dir) = new_node();
|
||||||
let mut node = KitchensinkNode::new(&args);
|
|
||||||
node.spawn(GENESIS_JSON.to_owned())
|
|
||||||
.expect("Failed to spawn the node");
|
|
||||||
|
|
||||||
let provider = ProviderBuilder::new()
|
let provider = node.provider().await.expect("Failed to create provider");
|
||||||
.network::<KitchenSinkNetwork>()
|
|
||||||
.wallet(args.wallet())
|
|
||||||
.connect(&node.rpc_url)
|
|
||||||
.await
|
|
||||||
.expect("Failed to create provider");
|
|
||||||
|
|
||||||
let account_address = args.wallet().default_signer().address();
|
let account_address = args.wallet().default_signer().address();
|
||||||
let transaction = TransactionRequest::default()
|
let transaction = TransactionRequest::default()
|
||||||
@@ -1137,4 +1261,92 @@ mod tests {
|
|||||||
"Expected eth-rpc version string, got: {version}"
|
"Expected eth-rpc version string, got: {version}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_chain_id_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let node = shared_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let chain_id = node.chain_id();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let chain_id = chain_id.expect("Failed to get the chain id");
|
||||||
|
assert_eq!(chain_id, 420_420_420);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_gas_limit_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let node = shared_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let gas_limit = node.block_gas_limit(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let _ = gas_limit.expect("Failed to get the gas limit");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_coinbase_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let node = shared_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let coinbase = node.block_coinbase(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let coinbase = coinbase.expect("Failed to get the coinbase");
|
||||||
|
assert_eq!(coinbase, Address::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_block_difficulty_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let node = shared_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let block_difficulty = node.block_difficulty(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let block_difficulty = block_difficulty.expect("Failed to get the block difficulty");
|
||||||
|
assert_eq!(block_difficulty, U256::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_block_hash_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let node = shared_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let block_hash = node.block_hash(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let _ = block_hash.expect("Failed to get the block hash");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_block_timestamp_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let node = shared_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let block_timestamp = node.block_timestamp(BlockNumberOrTag::Latest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let _ = block_timestamp.expect("Failed to get the block timestamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_block_number_from_node() {
|
||||||
|
// Arrange
|
||||||
|
let node = shared_node();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let block_number = node.last_block_number();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let block_number = block_number.expect("Failed to get the block number");
|
||||||
|
assert_eq!(block_number, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user