mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-22 19:37:57 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 772c5f79fc | |||
| aabcd06254 | |||
| 90fb89adc0 | |||
| b03ad3027e | |||
| 972f3b6d5b | |||
| 6f4aa731ab |
Generated
+15
-5
@@ -3948,6 +3948,17 @@ dependencies = [
|
||||
"serde_stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revive-dt-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
"once_cell",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revive-dt-compiler"
|
||||
version = "0.1.0"
|
||||
@@ -3982,6 +3993,7 @@ dependencies = [
|
||||
"clap",
|
||||
"indexmap 2.10.0",
|
||||
"rayon",
|
||||
"revive-dt-common",
|
||||
"revive-dt-compiler",
|
||||
"revive-dt-config",
|
||||
"revive-dt-format",
|
||||
@@ -4003,7 +4015,7 @@ dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-sol-types",
|
||||
"anyhow",
|
||||
"revive-dt-node-interaction",
|
||||
"revive-dt-common",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4016,7 +4028,9 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"alloy",
|
||||
"anyhow",
|
||||
"revive-dt-common",
|
||||
"revive-dt-config",
|
||||
"revive-dt-format",
|
||||
"revive-dt-node-interaction",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4033,10 +4047,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"alloy",
|
||||
"anyhow",
|
||||
"futures",
|
||||
"once_cell",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -11,6 +11,7 @@ repository = "https://github.com/paritytech/revive-differential-testing.git"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
revive-dt-common = { version = "0.1.0", path = "crates/common" }
|
||||
revive-dt-compiler = { version = "0.1.0", path = "crates/compiler" }
|
||||
revive-dt-config = { version = "0.1.0", path = "crates/config" }
|
||||
revive-dt-core = { version = "0.1.0", path = "crates/core" }
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "revive-dt-common"
|
||||
description = "A library containing common concepts that other crates in the workspace can rely on"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
+13
-16
@@ -23,7 +23,7 @@ use tracing::Instrument;
|
||||
/// executor to drive an async computation:
|
||||
///
|
||||
/// ```rust
|
||||
/// use revive_dt_node_interaction::*;
|
||||
/// use revive_dt_common::concepts::*;
|
||||
///
|
||||
/// fn blocking_function() {
|
||||
/// let result = BlockingExecutor::execute(async move {
|
||||
@@ -134,22 +134,17 @@ impl BlockingExecutor {
|
||||
}
|
||||
};
|
||||
|
||||
match result.map(|result| {
|
||||
*result
|
||||
.downcast::<R>()
|
||||
.expect("Type mismatch in the downcast")
|
||||
}) {
|
||||
Ok(result) => Ok(result),
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
?error,
|
||||
"Failed to downcast the returned result into the expected type"
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Failed to downcast the returned result into the expected type: {error:?}"
|
||||
)
|
||||
tracing::error!(?error, "An error occurred when running the async task");
|
||||
anyhow::bail!("An error occurred when running the async task: {error:?}")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(*result
|
||||
.downcast::<R>()
|
||||
.expect("An error occurred when downcasting into R. This is a bug"))
|
||||
}
|
||||
}
|
||||
/// Represents the state of the async runtime. This runtime is designed to be a singleton runtime
|
||||
@@ -208,7 +203,9 @@ mod test {
|
||||
fn panics_in_futures_are_caught() {
|
||||
// Act
|
||||
let result = BlockingExecutor::execute(async move {
|
||||
panic!("This is a panic!");
|
||||
panic!(
|
||||
"If this panic causes, well, a panic, then this is an issue. If it's caught then all good!"
|
||||
);
|
||||
0xFFu8
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
mod blocking_executor;
|
||||
|
||||
pub use blocking_executor::*;
|
||||
@@ -0,0 +1,3 @@
|
||||
mod files_with_extension_iterator;
|
||||
|
||||
pub use files_with_extension_iterator::*;
|
||||
@@ -0,0 +1,6 @@
|
||||
//! This crate provides common concepts, functionality, types, macros, and more that other crates in
|
||||
//! the workspace can benefit from.
|
||||
|
||||
pub mod concepts;
|
||||
pub mod iterators;
|
||||
pub mod macros;
|
||||
@@ -12,11 +12,9 @@
|
||||
/// pub struct CaseId(usize);
|
||||
/// ```
|
||||
///
|
||||
/// And would also implement a number of methods on this type making it easier
|
||||
/// to use.
|
||||
/// And would also implement a number of methods on this type making it easier to use.
|
||||
///
|
||||
/// These wrapper types become very useful as they make the code a lot easier
|
||||
/// to read.
|
||||
/// These wrapper types become very useful as they make the code a lot easier to read.
|
||||
///
|
||||
/// Take the following as an example:
|
||||
///
|
||||
@@ -26,33 +24,31 @@
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// In the above code it's hard to understand what the various types refer to or
|
||||
/// what to expect them to contain.
|
||||
/// In the above code it's hard to understand what the various types refer to or what to expect them
|
||||
/// to contain.
|
||||
///
|
||||
/// With these wrapper types we're able to create code that's self-documenting
|
||||
/// in that the types tell us what the code is referring to. The above code is
|
||||
/// transformed into
|
||||
/// With these wrapper types we're able to create code that's self-documenting in that the types
|
||||
/// tell us what the code is referring to. The above code is transformed into
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// struct State {
|
||||
/// contracts: HashMap<CaseId, HashMap<ContractName, ContractByteCode>>
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note that we follow the same syntax for defining wrapper structs but we do not permit the use of
|
||||
/// generics.
|
||||
#[macro_export]
|
||||
macro_rules! define_wrapper_type {
|
||||
(
|
||||
$(#[$meta: meta])*
|
||||
$ident: ident($ty: ty) $(;)?
|
||||
$vis:vis struct $ident: ident($ty: ty);
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
pub struct $ident($ty);
|
||||
$vis struct $ident($ty);
|
||||
|
||||
impl $ident {
|
||||
pub fn new(value: $ty) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
pub fn new_from<T: Into<$ty>>(value: T) -> Self {
|
||||
pub fn new(value: impl Into<$ty>) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
|
||||
@@ -104,3 +100,7 @@ macro_rules! define_wrapper_type {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Technically not needed but this allows for the macro to be found in the `macros` module of the
|
||||
/// crate in addition to being found in the root of the crate.
|
||||
pub use define_wrapper_type;
|
||||
@@ -0,0 +1,3 @@
|
||||
mod define_wrapper_type;
|
||||
|
||||
pub use define_wrapper_type::*;
|
||||
@@ -73,6 +73,12 @@ pub struct Arguments {
|
||||
)]
|
||||
pub account: String,
|
||||
|
||||
/// 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(long = "private-keys-count", default_value_t = 30)]
|
||||
pub private_keys_to_add: usize,
|
||||
|
||||
/// The differential testing leader node implementation.
|
||||
#[arg(short, long = "leader", default_value = "geth")]
|
||||
pub leader: TestingPlatform,
|
||||
|
||||
@@ -13,6 +13,7 @@ name = "retester"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
revive-dt-common = { workspace = true }
|
||||
revive-dt-compiler = { workspace = true }
|
||||
revive-dt-config = { workspace = true }
|
||||
revive-dt-format = { workspace = true }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! The test driver handles the compilation and execution of the test cases.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
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,
|
||||
@@ -20,6 +20,9 @@ use alloy::{
|
||||
};
|
||||
use anyhow::Context;
|
||||
use indexmap::IndexMap;
|
||||
use serde_json::Value;
|
||||
|
||||
use revive_dt_common::iterators::FilesWithExtensionIterator;
|
||||
use revive_dt_compiler::{Compiler, SolidityCompiler};
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_dt_format::case::CaseIdx;
|
||||
@@ -30,11 +33,8 @@ use revive_dt_node::Node;
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
use revive_dt_report::reporter::{CompilationTask, Report, Span};
|
||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||
use serde_json::Value;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::Platform;
|
||||
use crate::common::*;
|
||||
|
||||
pub struct State<'a, T: Platform> {
|
||||
/// The configuration that the framework was started with.
|
||||
@@ -442,9 +442,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 +457,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 +503,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::new_compound([expected]);
|
||||
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 +528,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:?}",
|
||||
);
|
||||
@@ -711,7 +718,7 @@ where
|
||||
);
|
||||
let _guard = tracing_span.enter();
|
||||
|
||||
let case_idx = CaseIdx::new_from(case_idx);
|
||||
let case_idx = CaseIdx::new(case_idx);
|
||||
|
||||
// For inputs if one of the inputs fail we move on to the next case (we do not move
|
||||
// on to the next input as it doesn't make sense. It depends on the previous one).
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
|
||||
use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc};
|
||||
use revive_dt_config::TestingPlatform;
|
||||
use revive_dt_format::traits::ResolverApi;
|
||||
use revive_dt_node::{Node, geth, kitchensink::KitchensinkNode};
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
|
||||
pub mod common;
|
||||
pub mod driver;
|
||||
|
||||
/// One platform can be tested differentially against another.
|
||||
///
|
||||
/// For this we need a blockchain node implementation and a compiler.
|
||||
pub trait Platform {
|
||||
type Blockchain: EthereumNode + Node;
|
||||
type Blockchain: EthereumNode + Node + ResolverApi;
|
||||
type Compiler: SolidityCompiler;
|
||||
|
||||
/// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments].
|
||||
|
||||
+10
-76
@@ -1,13 +1,5 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::LazyLock,
|
||||
};
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
|
||||
use alloy::{
|
||||
network::TxSigner,
|
||||
primitives::FixedBytes,
|
||||
signers::{Signature, local::PrivateKeySigner},
|
||||
};
|
||||
use clap::Parser;
|
||||
use rayon::{ThreadPoolBuilder, prelude::*};
|
||||
|
||||
@@ -16,11 +8,7 @@ use revive_dt_core::{
|
||||
Geth, Kitchensink, Platform,
|
||||
driver::{Driver, State},
|
||||
};
|
||||
use revive_dt_format::{
|
||||
corpus::Corpus,
|
||||
input::default_caller,
|
||||
metadata::{AddressReplacementMap, MetadataFile},
|
||||
};
|
||||
use revive_dt_format::{corpus::Corpus, metadata::MetadataFile};
|
||||
use revive_dt_node::pool::NodePool;
|
||||
use revive_dt_report::reporter::{Report, Span};
|
||||
use temp_dir::TempDir;
|
||||
@@ -32,48 +20,12 @@ static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = init_cli()?;
|
||||
|
||||
let mut corpora = collect_corpora(&args)?;
|
||||
let mut replacement_private_keys = HashSet::<FixedBytes<32>>::new();
|
||||
for case in corpora
|
||||
.values_mut()
|
||||
.flat_map(|metadata| metadata.iter_mut())
|
||||
.flat_map(|metadata| metadata.content.cases.iter_mut())
|
||||
{
|
||||
let mut replacement_map = AddressReplacementMap::new();
|
||||
for address in case.inputs.iter().filter_map(|input| {
|
||||
if input.caller != default_caller() {
|
||||
Some(input.caller)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
replacement_map.add(address);
|
||||
}
|
||||
case.handle_address_replacement(&mut replacement_map)?;
|
||||
replacement_private_keys.extend(
|
||||
replacement_map
|
||||
.into_inner()
|
||||
.into_values()
|
||||
.map(|(sk, _)| sk)
|
||||
.map(|sk| sk.to_bytes()),
|
||||
);
|
||||
}
|
||||
|
||||
for (corpus, tests) in corpora {
|
||||
for (corpus, tests) in collect_corpora(&args)? {
|
||||
let span = Span::new(corpus, args.clone())?;
|
||||
|
||||
match &args.compile_only {
|
||||
Some(platform) => compile_corpus(&args, &tests, platform, span),
|
||||
None => execute_corpus(
|
||||
&args,
|
||||
&tests,
|
||||
replacement_private_keys
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|bytes| PrivateKeySigner::from_bytes(&bytes).expect("Can't fail"))
|
||||
.collect::<Vec<_>>(),
|
||||
span,
|
||||
)?,
|
||||
None => execute_corpus(&args, &tests, span)?,
|
||||
}
|
||||
|
||||
Report::save()?;
|
||||
@@ -131,24 +83,15 @@ fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<Metad
|
||||
Ok(corpora)
|
||||
}
|
||||
|
||||
fn run_driver<L, F>(
|
||||
args: &Arguments,
|
||||
tests: &[MetadataFile],
|
||||
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
span: Span,
|
||||
) -> anyhow::Result<()>
|
||||
fn run_driver<L, F>(args: &Arguments, tests: &[MetadataFile], span: Span) -> anyhow::Result<()>
|
||||
where
|
||||
L: Platform,
|
||||
F: Platform,
|
||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||
{
|
||||
let leader_nodes = NodePool::<L::Blockchain>::new(args, additional_signers.clone())?;
|
||||
let follower_nodes = NodePool::<F::Blockchain>::new(args, additional_signers)?;
|
||||
let leader_nodes = NodePool::<L::Blockchain>::new(args)?;
|
||||
let follower_nodes = NodePool::<F::Blockchain>::new(args)?;
|
||||
|
||||
tests.par_iter().for_each(
|
||||
|MetadataFile {
|
||||
@@ -198,22 +141,13 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_corpus(
|
||||
args: &Arguments,
|
||||
tests: &[MetadataFile],
|
||||
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
span: Span,
|
||||
) -> anyhow::Result<()> {
|
||||
fn execute_corpus(args: &Arguments, tests: &[MetadataFile], span: Span) -> anyhow::Result<()> {
|
||||
match (&args.leader, &args.follower) {
|
||||
(TestingPlatform::Geth, TestingPlatform::Kitchensink) => {
|
||||
run_driver::<Geth, Kitchensink>(args, tests, additional_signers, span)?
|
||||
run_driver::<Geth, Kitchensink>(args, tests, span)?
|
||||
}
|
||||
(TestingPlatform::Geth, TestingPlatform::Geth) => {
|
||||
run_driver::<Geth, Geth>(args, tests, additional_signers, span)?
|
||||
run_driver::<Geth, Geth>(args, tests, span)?
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
revive-dt-node-interaction = { workspace = true }
|
||||
revive-dt-common = { workspace = true }
|
||||
|
||||
alloy = { workspace = true }
|
||||
alloy-primitives = { workspace = true }
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use revive_dt_common::macros::define_wrapper_type;
|
||||
|
||||
use crate::{
|
||||
define_wrapper_type,
|
||||
input::{Expected, Input},
|
||||
metadata::AddressReplacementMap,
|
||||
mode::Mode,
|
||||
};
|
||||
|
||||
@@ -41,23 +41,10 @@ impl Case {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle_address_replacement(
|
||||
&mut self,
|
||||
old_to_new_mapping: &mut AddressReplacementMap,
|
||||
) -> anyhow::Result<()> {
|
||||
for input in self.inputs.iter_mut() {
|
||||
input.handle_address_replacement(old_to_new_mapping)?;
|
||||
}
|
||||
if let Some(ref mut expected) = self.expected {
|
||||
expected.handle_address_replacement(old_to_new_mapping)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
define_wrapper_type!(
|
||||
/// A wrapper type for the index of test cases found in metadata file.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
CaseIdx(usize);
|
||||
pub struct CaseIdx(usize);
|
||||
);
|
||||
|
||||
+545
-347
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,6 @@
|
||||
pub mod case;
|
||||
pub mod corpus;
|
||||
pub mod input;
|
||||
pub mod macros;
|
||||
pub mod metadata;
|
||||
pub mod mode;
|
||||
pub mod traits;
|
||||
|
||||
+13
-146
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
collections::BTreeMap,
|
||||
fmt::Display,
|
||||
fs::{File, read_to_string},
|
||||
ops::Deref,
|
||||
@@ -7,15 +7,12 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use alloy::signers::local::PrivateKeySigner;
|
||||
use alloy_primitives::Address;
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_dt_common::macros::define_wrapper_type;
|
||||
|
||||
use crate::{
|
||||
case::Case,
|
||||
define_wrapper_type,
|
||||
input::resolve_argument,
|
||||
mode::{Mode, SolcMode},
|
||||
};
|
||||
|
||||
@@ -196,10 +193,10 @@ impl Metadata {
|
||||
metadata.file_path = Some(path.to_path_buf());
|
||||
metadata.contracts = Some(
|
||||
[(
|
||||
ContractInstance::new_from("test"),
|
||||
ContractInstance::new("test"),
|
||||
ContractPathAndIdentifier {
|
||||
contract_source_path: path.to_path_buf(),
|
||||
contract_ident: ContractIdent::new_from("Test"),
|
||||
contract_ident: ContractIdent::new("Test"),
|
||||
},
|
||||
)]
|
||||
.into(),
|
||||
@@ -215,35 +212,28 @@ impl Metadata {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_address_replacement(
|
||||
&mut self,
|
||||
old_to_new_mapping: &mut AddressReplacementMap,
|
||||
) -> anyhow::Result<()> {
|
||||
for case in self.cases.iter_mut() {
|
||||
case.handle_address_replacement(old_to_new_mapping)?;
|
||||
}
|
||||
tracing::debug!(metadata = ?self, "Performed replacement on metadata");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
define_wrapper_type!(
|
||||
/// Represents a contract instance found a metadata file.
|
||||
///
|
||||
/// Typically, this is used as the key to the "contracts" field of metadata files.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
ContractInstance(String);
|
||||
pub struct ContractInstance(String);
|
||||
);
|
||||
|
||||
define_wrapper_type!(
|
||||
/// Represents a contract identifier found a metadata file.
|
||||
///
|
||||
/// A contract identifier is the name of the contract in the source code.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
ContractIdent(String);
|
||||
pub struct ContractIdent(String);
|
||||
);
|
||||
|
||||
/// Represents an identifier used for contracts.
|
||||
@@ -324,129 +314,6 @@ impl From<ContractPathAndIdentifier> for String {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AddressReplacementMap(HashMap<Address, (PrivateKeySigner, Address)>);
|
||||
|
||||
impl AddressReplacementMap {
|
||||
pub fn new() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> HashMap<Address, (PrivateKeySigner, Address)> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, address: &Address) -> bool {
|
||||
self.0.contains_key(address)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, address: Address) -> Address {
|
||||
self.0
|
||||
.entry(address)
|
||||
.or_insert_with(|| {
|
||||
let private_key = Self::new_random_private_key_signer();
|
||||
let account = private_key.address();
|
||||
tracing::debug!(
|
||||
old_address = %address,
|
||||
new_address = %account,
|
||||
"Added a new address replacement"
|
||||
);
|
||||
(private_key, account)
|
||||
})
|
||||
.1
|
||||
}
|
||||
|
||||
pub fn resolve(&self, value: &str) -> Option<Address> {
|
||||
// We attempt to resolve the given string without any additional context of the deployed
|
||||
// contracts or the node API as we do not need them. If the resolution fails then we know
|
||||
// that this isn't an address and we skip it.
|
||||
let Ok(resolved) = resolve_argument(value, &Default::default(), &UnimplementedEthereumNode)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let resolved_bytes = resolved.to_be_bytes_trimmed_vec();
|
||||
let Ok(address) = Address::try_from(resolved_bytes.as_slice()) else {
|
||||
return None;
|
||||
};
|
||||
self.0.get(&address).map(|(_, address)| *address)
|
||||
}
|
||||
|
||||
fn new_random_private_key_signer() -> PrivateKeySigner {
|
||||
// TODO: Use a seedable RNG to allow for deterministic allocation of the private keys so
|
||||
// that we get reproducible runs.
|
||||
PrivateKeySigner::random()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<HashMap<Address, (PrivateKeySigner, Address)>> for AddressReplacementMap {
|
||||
fn as_ref(&self) -> &HashMap<Address, (PrivateKeySigner, Address)> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
struct UnimplementedEthereumNode;
|
||||
|
||||
impl EthereumNode for UnimplementedEthereumNode {
|
||||
fn execute_transaction(
|
||||
&self,
|
||||
_: alloy::rpc::types::TransactionRequest,
|
||||
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
|
||||
fn chain_id(&self) -> anyhow::Result<alloy_primitives::ChainId> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
|
||||
fn block_gas_limit(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<u128> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
|
||||
fn block_coinbase(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<Address> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
|
||||
fn block_difficulty(
|
||||
&self,
|
||||
_: alloy::eips::BlockNumberOrTag,
|
||||
) -> anyhow::Result<alloy_primitives::U256> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
|
||||
fn block_hash(
|
||||
&self,
|
||||
_: alloy::eips::BlockNumberOrTag,
|
||||
) -> anyhow::Result<alloy_primitives::BlockHash> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
|
||||
fn block_timestamp(
|
||||
&self,
|
||||
_: alloy::eips::BlockNumberOrTag,
|
||||
) -> anyhow::Result<alloy_primitives::BlockTimestamp> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
|
||||
fn last_block_number(&self) -> anyhow::Result<alloy_primitives::BlockNumber> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
|
||||
fn trace_transaction(
|
||||
&self,
|
||||
_: &alloy::rpc::types::TransactionReceipt,
|
||||
_: alloy::rpc::types::trace::geth::GethDebugTracingOptions,
|
||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
|
||||
fn state_diff(
|
||||
&self,
|
||||
_: &alloy::rpc::types::TransactionReceipt,
|
||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::DiffMode> {
|
||||
anyhow::bail!("Unimplemented")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
use alloy::eips::BlockNumberOrTag;
|
||||
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
|
||||
use anyhow::Result;
|
||||
|
||||
/// A trait of the interface are required to implement to be used by the resolution logic that this
|
||||
/// crate implements to go from string calldata and into the bytes calldata.
|
||||
pub trait ResolverApi {
|
||||
/// 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>;
|
||||
}
|
||||
@@ -11,7 +11,3 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
alloy = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
//! This crate implements all node interactions.
|
||||
|
||||
use alloy::eips::BlockNumberOrTag;
|
||||
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
|
||||
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
|
||||
use alloy::rpc::types::{TransactionReceipt, TransactionRequest};
|
||||
use anyhow::Result;
|
||||
|
||||
mod blocking_executor;
|
||||
pub use blocking_executor::*;
|
||||
|
||||
/// An interface for all interactions with Ethereum compatible nodes.
|
||||
pub trait EthereumNode {
|
||||
/// Execute the [TransactionRequest] and return a [TransactionReceipt].
|
||||
@@ -23,27 +18,4 @@ pub trait EthereumNode {
|
||||
|
||||
/// Returns the state diff of the transaction hash in the [TransactionReceipt].
|
||||
fn state_diff(&self, receipt: &TransactionReceipt) -> Result<DiffMode>;
|
||||
|
||||
/// 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>;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ alloy = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
revive-dt-node-interaction = { workspace = true }
|
||||
revive-dt-common = { workspace = true }
|
||||
revive-dt-config = { workspace = true }
|
||||
revive-dt-format = { workspace = true }
|
||||
revive-dt-node-interaction = { workspace = true }
|
||||
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/// This constant defines how much Wei accounts are pre-seeded with in genesis.
|
||||
///
|
||||
/// Note: After changing this number, check that the tests for kitchensink work as we encountered
|
||||
/// some issues with different values of the initial balance on Kitchensink.
|
||||
pub const INITIAL_BALANCE: u128 = 10u128.pow(37);
|
||||
+58
-64
@@ -12,8 +12,8 @@ use std::{
|
||||
use alloy::{
|
||||
eips::BlockNumberOrTag,
|
||||
genesis::{Genesis, GenesisAccount},
|
||||
network::{Ethereum, EthereumWallet, NetworkWallet, TxSigner},
|
||||
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, U256},
|
||||
network::{Ethereum, EthereumWallet, NetworkWallet},
|
||||
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, FixedBytes, U256},
|
||||
providers::{
|
||||
Provider, ProviderBuilder,
|
||||
ext::DebugApi,
|
||||
@@ -23,13 +23,15 @@ use alloy::{
|
||||
TransactionReceipt, TransactionRequest,
|
||||
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
||||
},
|
||||
signers::Signature,
|
||||
signers::local::PrivateKeySigner,
|
||||
};
|
||||
use revive_dt_common::concepts::BlockingExecutor;
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
|
||||
use revive_dt_format::traits::ResolverApi;
|
||||
use revive_dt_node_interaction::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 +76,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 +88,12 @@ 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()),
|
||||
);
|
||||
// Note, the use of the entry API here means that we only modify the entries for any
|
||||
// account that is not in the `alloc` field of the genesis state.
|
||||
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 +271,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;
|
||||
}
|
||||
})?
|
||||
}
|
||||
@@ -351,7 +345,9 @@ impl EthereumNode for Instance {
|
||||
_ => anyhow::bail!("expected a diff mode trace"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolverApi for Instance {
|
||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||
fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
||||
let provider = self.provider();
|
||||
@@ -435,16 +431,17 @@ impl EthereumNode for Instance {
|
||||
}
|
||||
|
||||
impl Node for Instance {
|
||||
fn new(
|
||||
config: &Arguments,
|
||||
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>,
|
||||
) -> Self {
|
||||
fn new(config: &Arguments) -> Self {
|
||||
let geth_directory = config.directory().join(Self::BASE_DIRECTORY);
|
||||
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
let base_directory = geth_directory.join(id.to_string());
|
||||
|
||||
let mut wallet = config.wallet();
|
||||
for signer in additional_signers {
|
||||
for signer in (1..=config.private_keys_to_add)
|
||||
.map(|id| U256::from(id))
|
||||
.map(|id| id.to_be_bytes::<32>())
|
||||
.map(|id| PrivateKeySigner::from_bytes(&FixedBytes(id)).unwrap())
|
||||
{
|
||||
wallet.register_signer(signer);
|
||||
}
|
||||
|
||||
@@ -532,7 +529,6 @@ impl Drop for Instance {
|
||||
mod tests {
|
||||
use revive_dt_config::Arguments;
|
||||
|
||||
use alloy::signers::local::PrivateKeySigner;
|
||||
use temp_dir::TempDir;
|
||||
|
||||
use crate::{GENESIS_JSON, Node};
|
||||
@@ -549,7 +545,7 @@ mod tests {
|
||||
|
||||
fn new_node() -> (Instance, TempDir) {
|
||||
let (args, temp_dir) = test_config();
|
||||
let mut node = Instance::new(&args, Vec::<PrivateKeySigner>::with_capacity(0));
|
||||
let mut node = Instance::new(&args);
|
||||
node.init(GENESIS_JSON.to_owned())
|
||||
.expect("Failed to initialize the node")
|
||||
.spawn_process()
|
||||
@@ -559,23 +555,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn init_works() {
|
||||
Instance::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0))
|
||||
Instance::new(&test_config().0)
|
||||
.init(GENESIS_JSON.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_works() {
|
||||
Instance::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0))
|
||||
Instance::new(&test_config().0)
|
||||
.spawn(GENESIS_JSON.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_works() {
|
||||
let version = Instance::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0))
|
||||
.version()
|
||||
.unwrap();
|
||||
let version = Instance::new(&test_config().0).version().unwrap();
|
||||
assert!(
|
||||
version.starts_with("geth version"),
|
||||
"expected version string, got: '{version}'"
|
||||
|
||||
@@ -13,9 +13,11 @@ use alloy::{
|
||||
genesis::{Genesis, GenesisAccount},
|
||||
network::{
|
||||
Ethereum, EthereumWallet, Network, NetworkWallet, TransactionBuilder,
|
||||
TransactionBuilderError, TxSigner, UnbuiltTransactionError,
|
||||
TransactionBuilderError, UnbuiltTransactionError,
|
||||
},
|
||||
primitives::{
|
||||
Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, FixedBytes, U256,
|
||||
},
|
||||
primitives::{Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, U256},
|
||||
providers::{
|
||||
Provider, ProviderBuilder,
|
||||
ext::DebugApi,
|
||||
@@ -26,18 +28,20 @@ use alloy::{
|
||||
eth::{Block, Header, Transaction},
|
||||
trace::geth::{DiffMode, GethDebugTracingOptions, PreStateConfig, PreStateFrame},
|
||||
},
|
||||
signers::Signature,
|
||||
signers::local::PrivateKeySigner,
|
||||
};
|
||||
use revive_dt_format::traits::ResolverApi;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value as JsonValue, json};
|
||||
use sp_core::crypto::Ss58Codec;
|
||||
use sp_runtime::AccountId32;
|
||||
use tracing::Level;
|
||||
|
||||
use revive_dt_common::concepts::BlockingExecutor;
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
|
||||
use crate::{Node, common::FallbackGasFiller};
|
||||
use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE};
|
||||
|
||||
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
@@ -129,10 +133,12 @@ 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()),
|
||||
);
|
||||
// Note, the use of the entry API here means that we only modify the entries for any
|
||||
// account that is not in the `alloc` field of the genesis state.
|
||||
genesis
|
||||
.alloc
|
||||
.entry(signer_address)
|
||||
.or_insert(GenesisAccount::default().with_balance(U256::from(INITIAL_BALANCE)));
|
||||
}
|
||||
self.extract_balance_from_genesis_file(&genesis)?
|
||||
};
|
||||
@@ -420,7 +426,9 @@ impl EthereumNode for KitchensinkNode {
|
||||
_ => anyhow::bail!("expected a diff mode trace"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolverApi for KitchensinkNode {
|
||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||
fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
||||
let provider = self.provider();
|
||||
@@ -504,17 +512,18 @@ impl EthereumNode for KitchensinkNode {
|
||||
}
|
||||
|
||||
impl Node for KitchensinkNode {
|
||||
fn new(
|
||||
config: &Arguments,
|
||||
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>,
|
||||
) -> Self {
|
||||
fn new(config: &Arguments) -> Self {
|
||||
let kitchensink_directory = config.directory().join(Self::BASE_DIRECTORY);
|
||||
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
let base_directory = kitchensink_directory.join(id.to_string());
|
||||
let logs_directory = base_directory.join(Self::LOGS_DIRECTORY);
|
||||
|
||||
let mut wallet = config.wallet();
|
||||
for signer in additional_signers {
|
||||
for signer in (1..=config.private_keys_to_add)
|
||||
.map(|id| U256::from(id))
|
||||
.map(|id| id.to_be_bytes::<32>())
|
||||
.map(|id| PrivateKeySigner::from_bytes(&FixedBytes(id)).unwrap())
|
||||
{
|
||||
wallet.register_signer(signer);
|
||||
}
|
||||
|
||||
@@ -1030,7 +1039,7 @@ impl BlockHeader for KitchenSinkHeader {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloy::{rpc::types::TransactionRequest, signers::local::PrivateKeySigner};
|
||||
use alloy::rpc::types::TransactionRequest;
|
||||
use revive_dt_config::Arguments;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
@@ -1073,7 +1082,7 @@ mod tests {
|
||||
let _guard = NODE_START_MUTEX.lock().unwrap();
|
||||
|
||||
let (args, temp_dir) = test_config();
|
||||
let mut node = KitchensinkNode::new(&args, Vec::<PrivateKeySigner>::with_capacity(0));
|
||||
let mut node = KitchensinkNode::new(&args);
|
||||
node.init(GENESIS_JSON)
|
||||
.expect("Failed to initialize the node")
|
||||
.spawn_process()
|
||||
@@ -1128,8 +1137,7 @@ mod tests {
|
||||
}
|
||||
"#;
|
||||
|
||||
let mut dummy_node =
|
||||
KitchensinkNode::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0));
|
||||
let mut dummy_node = KitchensinkNode::new(&test_config().0);
|
||||
|
||||
// Call `init()`
|
||||
dummy_node.init(genesis_content).expect("init failed");
|
||||
@@ -1173,8 +1181,7 @@ mod tests {
|
||||
}
|
||||
"#;
|
||||
|
||||
let node =
|
||||
KitchensinkNode::new(&test_config().0, Vec::<PrivateKeySigner>::with_capacity(0));
|
||||
let node = KitchensinkNode::new(&test_config().0);
|
||||
|
||||
let result = node
|
||||
.extract_balance_from_genesis_file(&serde_json::from_str(genesis_json).unwrap())
|
||||
@@ -1247,7 +1254,7 @@ mod tests {
|
||||
fn spawn_works() {
|
||||
let (config, _temp_dir) = test_config();
|
||||
|
||||
let mut node = KitchensinkNode::new(&config, Vec::<PrivateKeySigner>::with_capacity(0));
|
||||
let mut node = KitchensinkNode::new(&config);
|
||||
node.spawn(GENESIS_JSON.to_string()).unwrap();
|
||||
}
|
||||
|
||||
@@ -1255,7 +1262,7 @@ mod tests {
|
||||
fn version_works() {
|
||||
let (config, _temp_dir) = test_config();
|
||||
|
||||
let node = KitchensinkNode::new(&config, Vec::<PrivateKeySigner>::with_capacity(0));
|
||||
let node = KitchensinkNode::new(&config);
|
||||
let version = node.version().unwrap();
|
||||
|
||||
assert!(
|
||||
@@ -1268,7 +1275,7 @@ mod tests {
|
||||
fn eth_rpc_version_works() {
|
||||
let (config, _temp_dir) = test_config();
|
||||
|
||||
let node = KitchensinkNode::new(&config, Vec::<PrivateKeySigner>::with_capacity(0));
|
||||
let node = KitchensinkNode::new(&config);
|
||||
let version = node.eth_rpc_version().unwrap();
|
||||
|
||||
assert!(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! This crate implements the testing nodes.
|
||||
|
||||
use alloy::{network::TxSigner, signers::Signature};
|
||||
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;
|
||||
@@ -15,10 +15,7 @@ pub const GENESIS_JSON: &str = include_str!("../../../genesis.json");
|
||||
/// An abstract interface for testing nodes.
|
||||
pub trait Node: EthereumNode {
|
||||
/// Create a new uninitialized instance.
|
||||
fn new(
|
||||
config: &Arguments,
|
||||
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>,
|
||||
) -> Self;
|
||||
fn new(config: &Arguments) -> Self;
|
||||
|
||||
/// Spawns a node configured according to the genesis json.
|
||||
///
|
||||
|
||||
+4
-19
@@ -6,7 +6,6 @@ use std::{
|
||||
thread,
|
||||
};
|
||||
|
||||
use alloy::{network::TxSigner, signers::Signature};
|
||||
use anyhow::Context;
|
||||
use revive_dt_config::Arguments;
|
||||
|
||||
@@ -24,14 +23,7 @@ where
|
||||
T: Node + Send + 'static,
|
||||
{
|
||||
/// Create a new Pool. This will start as many nodes as there are workers in `config`.
|
||||
pub fn new(
|
||||
config: &Arguments,
|
||||
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> anyhow::Result<Self> {
|
||||
pub fn new(config: &Arguments) -> anyhow::Result<Self> {
|
||||
let nodes = config.workers;
|
||||
let genesis = read_to_string(&config.genesis_file).context(format!(
|
||||
"can not read genesis file: {}",
|
||||
@@ -42,10 +34,7 @@ where
|
||||
for _ in 0..nodes {
|
||||
let config = config.clone();
|
||||
let genesis = genesis.clone();
|
||||
let additional_signers = additional_signers.clone();
|
||||
handles.push(thread::spawn(move || {
|
||||
spawn_node::<T>(&config, additional_signers, genesis)
|
||||
}));
|
||||
handles.push(thread::spawn(move || spawn_node::<T>(&config, genesis)));
|
||||
}
|
||||
|
||||
let mut nodes = Vec::with_capacity(nodes);
|
||||
@@ -71,12 +60,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_node<T: Node + Send>(
|
||||
args: &Arguments,
|
||||
additional_signers: impl IntoIterator<Item: TxSigner<Signature> + Send + Sync + 'static>,
|
||||
genesis: String,
|
||||
) -> anyhow::Result<T> {
|
||||
let mut node = T::new(args, additional_signers);
|
||||
fn spawn_node<T: Node + Send>(args: &Arguments, genesis: String) -> anyhow::Result<T> {
|
||||
let mut node = T::new(args);
|
||||
tracing::info!("starting node: {}", node.connection_string());
|
||||
node.spawn(genesis)?;
|
||||
Ok(node)
|
||||
|
||||
+1
-5
@@ -33,9 +33,5 @@
|
||||
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"alloc": {
|
||||
"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1": {
|
||||
"balance": "10000000000000000000000"
|
||||
}
|
||||
}
|
||||
"alloc": {}
|
||||
}
|
||||
Reference in New Issue
Block a user