mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-22 21:57:58 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad3d580df9 | |||
| d45b8da8e3 | |||
| 79ce4a239c | |||
| 609ececea6 | |||
| fb3959d345 | |||
| 84026f9aee | |||
| a7ce202a6b | |||
| e19e0a4e7a |
@@ -7,3 +7,5 @@ node_modules
|
|||||||
# We do not want to commit any log files that we produce from running the code locally so this is
|
# We do not want to commit any log files that we produce from running the code locally so this is
|
||||||
# added to the .gitignore file.
|
# added to the .gitignore file.
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
profile.json.gz
|
||||||
Generated
+15
-1
@@ -4482,6 +4482,7 @@ dependencies = [
|
|||||||
"alloy",
|
"alloy",
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"dashmap",
|
||||||
"foundry-compilers-artifacts",
|
"foundry-compilers-artifacts",
|
||||||
"revive-common",
|
"revive-common",
|
||||||
"revive-dt-common",
|
"revive-dt-common",
|
||||||
@@ -4532,6 +4533,7 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4543,6 +4545,7 @@ dependencies = [
|
|||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-sol-types",
|
"alloy-sol-types",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"futures",
|
||||||
"regex",
|
"regex",
|
||||||
"revive-common",
|
"revive-common",
|
||||||
"revive-dt-common",
|
"revive-dt-common",
|
||||||
@@ -4592,7 +4595,6 @@ dependencies = [
|
|||||||
"revive-dt-format",
|
"revive-dt-format",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tracing",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6108,6 +6110,18 @@ dependencies = [
|
|||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-appender"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"time",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.28"
|
version = "0.1.28"
|
||||||
|
|||||||
+5
-1
@@ -28,6 +28,7 @@ anyhow = "1.0"
|
|||||||
bson = { version = "2.15.0" }
|
bson = { version = "2.15.0" }
|
||||||
cacache = { version = "13.1.0" }
|
cacache = { version = "13.1.0" }
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
dashmap = { version = "6.1.0" }
|
||||||
foundry-compilers-artifacts = { version = "0.18.0" }
|
foundry-compilers-artifacts = { version = "0.18.0" }
|
||||||
futures = { version = "0.3.31" }
|
futures = { version = "0.3.31" }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
@@ -54,7 +55,8 @@ tokio = { version = "1.47.0", default-features = false, features = [
|
|||||||
"rt",
|
"rt",
|
||||||
] }
|
] }
|
||||||
uuid = { version = "1.8", features = ["v4"] }
|
uuid = { version = "1.8", features = ["v4"] }
|
||||||
tracing = "0.1.41"
|
tracing = { version = "0.1.41" }
|
||||||
|
tracing-appender = { version = "0.2.3" }
|
||||||
tracing-subscriber = { version = "0.3.19", default-features = false, features = [
|
tracing-subscriber = { version = "0.3.19", default-features = false, features = [
|
||||||
"fmt",
|
"fmt",
|
||||||
"json",
|
"json",
|
||||||
@@ -89,3 +91,5 @@ features = [
|
|||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -15,3 +15,6 @@ once_cell = { workspace = true }
|
|||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
tokio = { workspace = true, default-features = false, features = ["time"] }
|
tokio = { workspace = true, default-features = false, features = ["time"] }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_for_wrapper {
|
||||||
|
(Display, $ident: ident) => {
|
||||||
|
impl std::fmt::Display for $ident {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
std::fmt::Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Defines wrappers around types.
|
/// Defines wrappers around types.
|
||||||
///
|
///
|
||||||
/// For example, the macro invocation seen below:
|
/// For example, the macro invocation seen below:
|
||||||
@@ -42,7 +53,13 @@
|
|||||||
macro_rules! define_wrapper_type {
|
macro_rules! define_wrapper_type {
|
||||||
(
|
(
|
||||||
$(#[$meta: meta])*
|
$(#[$meta: meta])*
|
||||||
$vis:vis struct $ident: ident($ty: ty);
|
$vis:vis struct $ident: ident($ty: ty)
|
||||||
|
|
||||||
|
$(
|
||||||
|
impl $($trait_ident: ident),*
|
||||||
|
)?
|
||||||
|
|
||||||
|
;
|
||||||
) => {
|
) => {
|
||||||
$(#[$meta])*
|
$(#[$meta])*
|
||||||
$vis struct $ident($ty);
|
$vis struct $ident($ty);
|
||||||
@@ -98,9 +115,15 @@ macro_rules! define_wrapper_type {
|
|||||||
value.0
|
value.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
$(
|
||||||
|
$crate::macros::impl_for_wrapper!($trait_ident, $ident);
|
||||||
|
)*
|
||||||
|
)?
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Technically not needed but this allows for the macro to be found in the `macros` module of the
|
/// 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.
|
/// crate in addition to being found in the root of the crate.
|
||||||
pub use define_wrapper_type;
|
pub use {define_wrapper_type, impl_for_wrapper};
|
||||||
|
|||||||
@@ -18,9 +18,13 @@ revive-common = { workspace = true }
|
|||||||
alloy = { workspace = true }
|
alloy = { workspace = true }
|
||||||
alloy-primitives = { workspace = true }
|
alloy-primitives = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
dashmap = { workspace = true }
|
||||||
foundry-compilers-artifacts = { workspace = true }
|
foundry-compilers-artifacts = { workspace = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ pub trait SolidityCompiler {
|
|||||||
version: impl Into<VersionOrRequirement>,
|
version: impl Into<VersionOrRequirement>,
|
||||||
) -> impl Future<Output = anyhow::Result<PathBuf>>;
|
) -> impl Future<Output = anyhow::Result<PathBuf>>;
|
||||||
|
|
||||||
fn version(&self) -> anyhow::Result<Version>;
|
fn version(&self) -> impl Future<Output = anyhow::Result<Version>>;
|
||||||
|
|
||||||
/// Does the compiler support the provided mode and version settings?
|
/// Does the compiler support the provided mode and version settings?
|
||||||
fn supports_mode(
|
fn supports_mode(
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
|
sync::LazyLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use dashmap::DashMap;
|
||||||
use revive_dt_common::types::VersionOrRequirement;
|
use revive_dt_common::types::VersionOrRequirement;
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_solc_json_interface::{
|
use revive_solc_json_interface::{
|
||||||
@@ -219,26 +221,39 @@ impl SolidityCompiler for Resolc {
|
|||||||
Ok(PathBuf::from("resolc"))
|
Ok(PathBuf::from("resolc"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version(&self) -> anyhow::Result<semver::Version> {
|
async fn version(&self) -> anyhow::Result<semver::Version> {
|
||||||
// Logic for parsing the resolc version from the following string:
|
/// This is a cache of the path of the compiler to the version number of the compiler. We
|
||||||
// Solidity frontend for the revive compiler version 0.3.0+commit.b238913.llvm-18.1.8
|
/// choose to cache the version in this way rather than through a field on the struct since
|
||||||
|
/// compiler objects are being created all the time from the path and the compiler object is
|
||||||
|
/// not reused over time.
|
||||||
|
static VERSION_CACHE: LazyLock<DashMap<PathBuf, Version>> = LazyLock::new(Default::default);
|
||||||
|
|
||||||
let output = Command::new(self.resolc_path.as_path())
|
match VERSION_CACHE.entry(self.resolc_path.clone()) {
|
||||||
.arg("--version")
|
dashmap::Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()),
|
||||||
.stdout(Stdio::piped())
|
dashmap::Entry::Vacant(vacant_entry) => {
|
||||||
.spawn()?
|
let output = Command::new(self.resolc_path.as_path())
|
||||||
.wait_with_output()?
|
.arg("--version")
|
||||||
.stdout;
|
.stdout(Stdio::piped())
|
||||||
let output = String::from_utf8_lossy(&output);
|
.spawn()?
|
||||||
let version_string = output
|
.wait_with_output()?
|
||||||
.split("version ")
|
.stdout;
|
||||||
.nth(1)
|
|
||||||
.context("Version parsing failed")?
|
|
||||||
.split("+")
|
|
||||||
.next()
|
|
||||||
.context("Version parsing failed")?;
|
|
||||||
|
|
||||||
Version::parse(version_string).map_err(Into::into)
|
let output = String::from_utf8_lossy(&output);
|
||||||
|
let version_string = output
|
||||||
|
.split("version ")
|
||||||
|
.nth(1)
|
||||||
|
.context("Version parsing failed")?
|
||||||
|
.split("+")
|
||||||
|
.next()
|
||||||
|
.context("Version parsing failed")?;
|
||||||
|
|
||||||
|
let version = Version::parse(version_string)?;
|
||||||
|
|
||||||
|
vacant_entry.insert(version.clone());
|
||||||
|
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_mode(
|
fn supports_mode(
|
||||||
@@ -268,7 +283,7 @@ mod test {
|
|||||||
let compiler = Resolc::new(path);
|
let compiler = Resolc::new(path);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let version = compiler.version();
|
let version = compiler.version().await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let _ = version.expect("Failed to get version");
|
let _ = version.expect("Failed to get version");
|
||||||
|
|||||||
+41
-29
@@ -4,8 +4,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
|
sync::LazyLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use dashmap::DashMap;
|
||||||
use revive_dt_common::types::VersionOrRequirement;
|
use revive_dt_common::types::VersionOrRequirement;
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_dt_solc_binaries::download_solc;
|
use revive_dt_solc_binaries::download_solc;
|
||||||
@@ -47,7 +49,7 @@ impl SolidityCompiler for Solc {
|
|||||||
}: CompilerInput,
|
}: CompilerInput,
|
||||||
_: Self::Options,
|
_: Self::Options,
|
||||||
) -> anyhow::Result<CompilerOutput> {
|
) -> anyhow::Result<CompilerOutput> {
|
||||||
let compiler_supports_via_ir = self.version()? >= SOLC_VERSION_SUPPORTING_VIA_YUL_IR;
|
let compiler_supports_via_ir = self.version().await? >= SOLC_VERSION_SUPPORTING_VIA_YUL_IR;
|
||||||
|
|
||||||
// Be careful to entirely omit the viaIR field if the compiler does not support it,
|
// Be careful to entirely omit the viaIR field if the compiler does not support it,
|
||||||
// as it will error if you provide fields it does not know about. Because
|
// as it will error if you provide fields it does not know about. Because
|
||||||
@@ -209,30 +211,44 @@ impl SolidityCompiler for Solc {
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version(&self) -> anyhow::Result<semver::Version> {
|
async fn version(&self) -> anyhow::Result<semver::Version> {
|
||||||
// The following is the parsing code for the version from the solc version strings which
|
/// This is a cache of the path of the compiler to the version number of the compiler. We
|
||||||
// look like the following:
|
/// choose to cache the version in this way rather than through a field on the struct since
|
||||||
// ```
|
/// compiler objects are being created all the time from the path and the compiler object is
|
||||||
// solc, the solidity compiler commandline interface
|
/// not reused over time.
|
||||||
// Version: 0.8.30+commit.73712a01.Darwin.appleclang
|
static VERSION_CACHE: LazyLock<DashMap<PathBuf, Version>> = LazyLock::new(Default::default);
|
||||||
// ```
|
|
||||||
|
|
||||||
let child = Command::new(self.solc_path.as_path())
|
match VERSION_CACHE.entry(self.solc_path.clone()) {
|
||||||
.arg("--version")
|
dashmap::Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()),
|
||||||
.stdout(Stdio::piped())
|
dashmap::Entry::Vacant(vacant_entry) => {
|
||||||
.spawn()?;
|
// The following is the parsing code for the version from the solc version strings
|
||||||
let output = child.wait_with_output()?;
|
// which look like the following:
|
||||||
let output = String::from_utf8_lossy(&output.stdout);
|
// ```
|
||||||
let version_line = output
|
// solc, the solidity compiler commandline interface
|
||||||
.split("Version: ")
|
// Version: 0.8.30+commit.73712a01.Darwin.appleclang
|
||||||
.nth(1)
|
// ```
|
||||||
.context("Version parsing failed")?;
|
let child = Command::new(self.solc_path.as_path())
|
||||||
let version_string = version_line
|
.arg("--version")
|
||||||
.split("+")
|
.stdout(Stdio::piped())
|
||||||
.next()
|
.spawn()?;
|
||||||
.context("Version parsing failed")?;
|
let output = child.wait_with_output()?;
|
||||||
|
let output = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let version_line = output
|
||||||
|
.split("Version: ")
|
||||||
|
.nth(1)
|
||||||
|
.context("Version parsing failed")?;
|
||||||
|
let version_string = version_line
|
||||||
|
.split("+")
|
||||||
|
.next()
|
||||||
|
.context("Version parsing failed")?;
|
||||||
|
|
||||||
Version::parse(version_string).map_err(Into::into)
|
let version = Version::parse(version_string)?;
|
||||||
|
|
||||||
|
vacant_entry.insert(version.clone());
|
||||||
|
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_mode(
|
fn supports_mode(
|
||||||
@@ -256,15 +272,13 @@ mod test {
|
|||||||
async fn compiler_version_can_be_obtained() {
|
async fn compiler_version_can_be_obtained() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let args = Arguments::default();
|
let args = Arguments::default();
|
||||||
println!("Getting compiler path");
|
|
||||||
let path = Solc::get_compiler_executable(&args, Version::new(0, 7, 6))
|
let path = Solc::get_compiler_executable(&args, Version::new(0, 7, 6))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("Got compiler path");
|
|
||||||
let compiler = Solc::new(path);
|
let compiler = Solc::new(path);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let version = compiler.version();
|
let version = compiler.version().await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -277,15 +291,13 @@ mod test {
|
|||||||
async fn compiler_version_can_be_obtained1() {
|
async fn compiler_version_can_be_obtained1() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let args = Arguments::default();
|
let args = Arguments::default();
|
||||||
println!("Getting compiler path");
|
|
||||||
let path = Solc::get_compiler_executable(&args, Version::new(0, 4, 21))
|
let path = Solc::get_compiler_executable(&args, Version::new(0, 4, 21))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("Got compiler path");
|
|
||||||
let compiler = Solc::new(path);
|
let compiler = Solc::new(path);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let version = compiler.version();
|
let version = compiler.version().await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ async fn contracts_can_be_compiled_with_solc() {
|
|||||||
let compiler_path = Solc::get_compiler_executable(&args, Version::new(0, 8, 30))
|
let compiler_path = Solc::get_compiler_executable(&args, Version::new(0, 8, 30))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("About to assert");
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let output = Compiler::<Solc>::new()
|
let output = Compiler::<Solc>::new()
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ semver = { workspace = true }
|
|||||||
temp-dir = { workspace = true }
|
temp-dir = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ indexmap = { workspace = true }
|
|||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
tracing-appender = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
temp-dir = { workspace = true }
|
temp-dir = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ impl CachedCompiler {
|
|||||||
compiler_version_or_requirement,
|
compiler_version_or_requirement,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let compiler_version =
|
let compiler_version = <P::Compiler as SolidityCompiler>::new(compiler_path.clone())
|
||||||
<P::Compiler as SolidityCompiler>::new(compiler_path.clone()).version()?;
|
.version()
|
||||||
|
.await?;
|
||||||
|
|
||||||
let cache_key = CacheKey {
|
let cache_key = CacheKey {
|
||||||
platform_key: P::config_id().to_string(),
|
platform_key: P::config_id().to_string(),
|
||||||
|
|||||||
+90
-142
@@ -16,26 +16,24 @@ use alloy::rpc::types::trace::geth::{
|
|||||||
};
|
};
|
||||||
use alloy::{
|
use alloy::{
|
||||||
primitives::Address,
|
primitives::Address,
|
||||||
rpc::types::{
|
rpc::types::{TransactionRequest, trace::geth::DiffMode},
|
||||||
TransactionRequest,
|
|
||||||
trace::geth::{AccountState, DiffMode},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use futures::TryStreamExt;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use revive_dt_format::traits::{ResolutionContext, ResolverApi};
|
use revive_dt_format::traits::{ResolutionContext, ResolverApi};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
use revive_dt_format::case::{Case, CaseIdx};
|
use revive_dt_format::case::Case;
|
||||||
use revive_dt_format::input::{
|
use revive_dt_format::input::{
|
||||||
BalanceAssertion, Calldata, EtherValue, Expected, ExpectedOutput, Input, Method,
|
BalanceAssertion, Calldata, EtherValue, Expected, ExpectedOutput, Input, Method, StepIdx,
|
||||||
StorageEmptyAssertion,
|
StorageEmptyAssertion,
|
||||||
};
|
};
|
||||||
use revive_dt_format::metadata::{ContractIdent, ContractInstance, ContractPathAndIdent};
|
use revive_dt_format::metadata::{ContractIdent, ContractInstance, ContractPathAndIdent};
|
||||||
use revive_dt_format::{input::Step, metadata::Metadata};
|
use revive_dt_format::{input::Step, metadata::Metadata};
|
||||||
use revive_dt_node::Node;
|
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
use tracing::Instrument;
|
use tokio::try_join;
|
||||||
|
use tracing::{Instrument, info, info_span, instrument};
|
||||||
|
|
||||||
use crate::Platform;
|
use crate::Platform;
|
||||||
|
|
||||||
@@ -77,38 +75,38 @@ where
|
|||||||
pub async fn handle_step(
|
pub async fn handle_step(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
case_idx: CaseIdx,
|
|
||||||
step: &Step,
|
step: &Step,
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<StepOutput> {
|
) -> anyhow::Result<StepOutput> {
|
||||||
match step {
|
match step {
|
||||||
Step::FunctionCall(input) => {
|
Step::FunctionCall(input) => {
|
||||||
let (receipt, geth_trace, diff_mode) =
|
let (receipt, geth_trace, diff_mode) =
|
||||||
self.handle_input(metadata, case_idx, input, node).await?;
|
self.handle_input(metadata, input, node).await?;
|
||||||
Ok(StepOutput::FunctionCall(receipt, geth_trace, diff_mode))
|
Ok(StepOutput::FunctionCall(receipt, geth_trace, diff_mode))
|
||||||
}
|
}
|
||||||
Step::BalanceAssertion(balance_assertion) => {
|
Step::BalanceAssertion(balance_assertion) => {
|
||||||
self.handle_balance_assertion(metadata, case_idx, balance_assertion, node)
|
self.handle_balance_assertion(metadata, balance_assertion, node)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(StepOutput::BalanceAssertion)
|
Ok(StepOutput::BalanceAssertion)
|
||||||
}
|
}
|
||||||
Step::StorageEmptyAssertion(storage_empty) => {
|
Step::StorageEmptyAssertion(storage_empty) => {
|
||||||
self.handle_storage_empty(metadata, case_idx, storage_empty, node)
|
self.handle_storage_empty(metadata, storage_empty, node)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(StepOutput::StorageEmptyAssertion)
|
Ok(StepOutput::StorageEmptyAssertion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.inspect(|_| info!("Step Succeeded"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", name = "Handling Input", skip_all)]
|
||||||
pub async fn handle_input(
|
pub async fn handle_input(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
case_idx: CaseIdx,
|
|
||||||
input: &Input,
|
input: &Input,
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||||
let deployment_receipts = self
|
let deployment_receipts = self
|
||||||
.handle_input_contract_deployment(metadata, case_idx, input, node)
|
.handle_input_contract_deployment(metadata, input, node)
|
||||||
.await?;
|
.await?;
|
||||||
let execution_receipt = self
|
let execution_receipt = self
|
||||||
.handle_input_execution(input, deployment_receipts, node)
|
.handle_input_execution(input, deployment_receipts, node)
|
||||||
@@ -117,16 +115,17 @@ where
|
|||||||
.handle_input_call_frame_tracing(&execution_receipt, node)
|
.handle_input_call_frame_tracing(&execution_receipt, node)
|
||||||
.await?;
|
.await?;
|
||||||
self.handle_input_variable_assignment(input, &tracing_result)?;
|
self.handle_input_variable_assignment(input, &tracing_result)?;
|
||||||
self.handle_input_expectations(input, &execution_receipt, node, &tracing_result)
|
let (_, (geth_trace, diff_mode)) = try_join!(
|
||||||
.await?;
|
self.handle_input_expectations(input, &execution_receipt, node, &tracing_result),
|
||||||
self.handle_input_diff(case_idx, execution_receipt, node)
|
self.handle_input_diff(&execution_receipt, node)
|
||||||
.await
|
)?;
|
||||||
|
Ok((execution_receipt, geth_trace, diff_mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", name = "Handling Balance Assertion", skip_all)]
|
||||||
pub async fn handle_balance_assertion(
|
pub async fn handle_balance_assertion(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
_: CaseIdx,
|
|
||||||
balance_assertion: &BalanceAssertion,
|
balance_assertion: &BalanceAssertion,
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
@@ -137,10 +136,10 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", name = "Handling Storage Assertion", skip_all)]
|
||||||
pub async fn handle_storage_empty(
|
pub async fn handle_storage_empty(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
_: CaseIdx,
|
|
||||||
storage_empty: &StorageEmptyAssertion,
|
storage_empty: &StorageEmptyAssertion,
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
@@ -152,10 +151,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_contract_deployment(
|
async fn handle_input_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
_: CaseIdx,
|
|
||||||
input: &Input,
|
input: &Input,
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
|
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
|
||||||
@@ -170,11 +169,6 @@ where
|
|||||||
instances_we_must_deploy.insert(input.instance.clone(), true);
|
instances_we_must_deploy.insert(input.instance.clone(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!(
|
|
||||||
instances_to_deploy = instances_we_must_deploy.len(),
|
|
||||||
"Computed the number of required deployments for input"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut receipts = HashMap::new();
|
let mut receipts = HashMap::new();
|
||||||
for (instance, deploy_with_constructor_arguments) in instances_we_must_deploy.into_iter() {
|
for (instance, deploy_with_constructor_arguments) in instances_we_must_deploy.into_iter() {
|
||||||
let calldata = deploy_with_constructor_arguments.then_some(&input.calldata);
|
let calldata = deploy_with_constructor_arguments.then_some(&input.calldata);
|
||||||
@@ -201,6 +195,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the execution of the input in terms of the calls that need to be made.
|
/// Handles the execution of the input in terms of the calls that need to be made.
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_execution(
|
async fn handle_input_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &Input,
|
input: &Input,
|
||||||
@@ -218,33 +213,21 @@ where
|
|||||||
.legacy_transaction(node, self.default_resolution_context())
|
.legacy_transaction(node, self.default_resolution_context())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(tx) => {
|
Ok(tx) => tx,
|
||||||
tracing::debug!("Legacy transaction data: {tx:#?}");
|
|
||||||
tx
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::error!("Failed to construct legacy transaction: {err:?}");
|
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!("Executing transaction for input: {input:?}");
|
|
||||||
|
|
||||||
match node.execute_transaction(tx).await {
|
match node.execute_transaction(tx).await {
|
||||||
Ok(receipt) => Ok(receipt),
|
Ok(receipt) => Ok(receipt),
|
||||||
Err(err) => {
|
Err(err) => Err(err),
|
||||||
tracing::error!(
|
|
||||||
"Failed to execute transaction when executing the contract: {}, {:?}",
|
|
||||||
&*input.instance,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_call_frame_tracing(
|
async fn handle_input_call_frame_tracing(
|
||||||
&self,
|
&self,
|
||||||
execution_receipt: &TransactionReceipt,
|
execution_receipt: &TransactionReceipt,
|
||||||
@@ -259,7 +242,10 @@ where
|
|||||||
tracer_config: GethDebugTracerConfig(serde_json::json! {{
|
tracer_config: GethDebugTracerConfig(serde_json::json! {{
|
||||||
"onlyTopCall": true,
|
"onlyTopCall": true,
|
||||||
"withLog": false,
|
"withLog": false,
|
||||||
"withReturnData": false
|
"withStorage": false,
|
||||||
|
"withMemory": false,
|
||||||
|
"withStack": false,
|
||||||
|
"withReturnData": true
|
||||||
}}),
|
}}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -272,6 +258,7 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
fn handle_input_variable_assignment(
|
fn handle_input_variable_assignment(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &Input,
|
input: &Input,
|
||||||
@@ -302,8 +289,9 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_expectations(
|
async fn handle_input_expectations(
|
||||||
&mut self,
|
&self,
|
||||||
input: &Input,
|
input: &Input,
|
||||||
execution_receipt: &TransactionReceipt,
|
execution_receipt: &TransactionReceipt,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &impl ResolverApi,
|
||||||
@@ -337,24 +325,25 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for expectation in expectations.iter() {
|
futures::stream::iter(expectations.into_iter().map(Ok))
|
||||||
self.handle_input_expectation_item(
|
.try_for_each_concurrent(None, |expectation| async move {
|
||||||
execution_receipt,
|
self.handle_input_expectation_item(
|
||||||
resolver,
|
execution_receipt,
|
||||||
expectation,
|
resolver,
|
||||||
tracing_result,
|
expectation,
|
||||||
)
|
tracing_result,
|
||||||
.await?;
|
)
|
||||||
}
|
.await
|
||||||
|
})
|
||||||
Ok(())
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_expectation_item(
|
async fn handle_input_expectation_item(
|
||||||
&mut self,
|
&self,
|
||||||
execution_receipt: &TransactionReceipt,
|
execution_receipt: &TransactionReceipt,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &impl ResolverApi,
|
||||||
expectation: &ExpectedOutput,
|
expectation: ExpectedOutput,
|
||||||
tracing_result: &CallFrame,
|
tracing_result: &CallFrame,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if let Some(ref version_requirement) = expectation.compiler_version {
|
if let Some(ref version_requirement) = expectation.compiler_version {
|
||||||
@@ -492,12 +481,12 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_diff(
|
async fn handle_input_diff(
|
||||||
&mut self,
|
&self,
|
||||||
_: CaseIdx,
|
execution_receipt: &TransactionReceipt,
|
||||||
execution_receipt: TransactionReceipt,
|
|
||||||
node: &T::Blockchain,
|
node: &T::Blockchain,
|
||||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
) -> anyhow::Result<(GethTrace, DiffMode)> {
|
||||||
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,
|
||||||
@@ -505,13 +494,14 @@ where
|
|||||||
});
|
});
|
||||||
|
|
||||||
let trace = node
|
let trace = node
|
||||||
.trace_transaction(&execution_receipt, trace_options)
|
.trace_transaction(execution_receipt, trace_options)
|
||||||
.await?;
|
.await?;
|
||||||
let diff = node.state_diff(&execution_receipt).await?;
|
let diff = node.state_diff(execution_receipt).await?;
|
||||||
|
|
||||||
Ok((execution_receipt, trace, diff))
|
Ok((trace, diff))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
pub async fn handle_balance_assertion_contract_deployment(
|
pub async fn handle_balance_assertion_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
@@ -537,6 +527,7 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
pub async fn handle_balance_assertion_execution(
|
pub async fn handle_balance_assertion_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
BalanceAssertion {
|
BalanceAssertion {
|
||||||
@@ -572,6 +563,7 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
pub async fn handle_storage_empty_assertion_contract_deployment(
|
pub async fn handle_storage_empty_assertion_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
@@ -597,6 +589,7 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all)]
|
||||||
pub async fn handle_storage_empty_assertion_execution(
|
pub async fn handle_storage_empty_assertion_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
StorageEmptyAssertion {
|
StorageEmptyAssertion {
|
||||||
@@ -658,7 +651,6 @@ where
|
|||||||
contract_ident,
|
contract_ident,
|
||||||
}) = metadata.contract_sources()?.remove(contract_instance)
|
}) = metadata.contract_sources()?.remove(contract_instance)
|
||||||
else {
|
else {
|
||||||
tracing::error!("Contract source not found for instance");
|
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Contract source not found for instance {:?}",
|
"Contract source not found for instance {:?}",
|
||||||
contract_instance
|
contract_instance
|
||||||
@@ -671,11 +663,6 @@ where
|
|||||||
.and_then(|source_file_contracts| source_file_contracts.get(contract_ident.as_ref()))
|
.and_then(|source_file_contracts| source_file_contracts.get(contract_ident.as_ref()))
|
||||||
.cloned()
|
.cloned()
|
||||||
else {
|
else {
|
||||||
tracing::error!(
|
|
||||||
contract_source_path = contract_source_path.display().to_string(),
|
|
||||||
contract_ident = contract_ident.as_ref(),
|
|
||||||
"Failed to find information for contract"
|
|
||||||
);
|
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Failed to find information for contract {:?}",
|
"Failed to find information for contract {:?}",
|
||||||
contract_instance
|
contract_instance
|
||||||
@@ -724,7 +711,6 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Some(address) = receipt.contract_address else {
|
let Some(address) = receipt.contract_address else {
|
||||||
tracing::error!("Contract deployment transaction didn't return an address");
|
|
||||||
anyhow::bail!("Contract deployment didn't return an address");
|
anyhow::bail!("Contract deployment didn't return an address");
|
||||||
};
|
};
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@@ -751,7 +737,6 @@ where
|
|||||||
pub struct CaseDriver<'a, Leader: Platform, Follower: Platform> {
|
pub struct CaseDriver<'a, Leader: Platform, Follower: Platform> {
|
||||||
metadata: &'a Metadata,
|
metadata: &'a Metadata,
|
||||||
case: &'a Case,
|
case: &'a Case,
|
||||||
case_idx: CaseIdx,
|
|
||||||
leader_node: &'a Leader::Blockchain,
|
leader_node: &'a Leader::Blockchain,
|
||||||
follower_node: &'a Follower::Blockchain,
|
follower_node: &'a Follower::Blockchain,
|
||||||
leader_state: CaseState<Leader>,
|
leader_state: CaseState<Leader>,
|
||||||
@@ -767,7 +752,6 @@ where
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
metadata: &'a Metadata,
|
metadata: &'a Metadata,
|
||||||
case: &'a Case,
|
case: &'a Case,
|
||||||
case_idx: impl Into<CaseIdx>,
|
|
||||||
leader_node: &'a L::Blockchain,
|
leader_node: &'a L::Blockchain,
|
||||||
follower_node: &'a F::Blockchain,
|
follower_node: &'a F::Blockchain,
|
||||||
leader_state: CaseState<L>,
|
leader_state: CaseState<L>,
|
||||||
@@ -776,7 +760,6 @@ where
|
|||||||
Self {
|
Self {
|
||||||
metadata,
|
metadata,
|
||||||
case,
|
case,
|
||||||
case_idx: case_idx.into(),
|
|
||||||
leader_node,
|
leader_node,
|
||||||
follower_node,
|
follower_node,
|
||||||
leader_state,
|
leader_state,
|
||||||
@@ -784,79 +767,44 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trace_diff_mode(label: &str, diff: &DiffMode) {
|
#[instrument(level = "info", name = "Executing Case", skip_all)]
|
||||||
tracing::trace!("{label} - PRE STATE:");
|
|
||||||
for (addr, state) in &diff.pre {
|
|
||||||
Self::trace_account_state(" [pre]", addr, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::trace!("{label} - POST STATE:");
|
|
||||||
for (addr, state) in &diff.post {
|
|
||||||
Self::trace_account_state(" [post]", addr, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trace_account_state(prefix: &str, addr: &Address, state: &AccountState) {
|
|
||||||
tracing::trace!("{prefix} 0x{addr:x}");
|
|
||||||
|
|
||||||
if let Some(balance) = &state.balance {
|
|
||||||
tracing::trace!("{prefix} balance: {balance}");
|
|
||||||
}
|
|
||||||
if let Some(nonce) = &state.nonce {
|
|
||||||
tracing::trace!("{prefix} nonce: {nonce}");
|
|
||||||
}
|
|
||||||
if let Some(code) = &state.code {
|
|
||||||
tracing::trace!("{prefix} code: {code}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execute(&mut self) -> anyhow::Result<usize> {
|
pub async fn execute(&mut self) -> anyhow::Result<usize> {
|
||||||
if !self
|
|
||||||
.leader_node
|
|
||||||
.matches_target(self.metadata.targets.as_deref())
|
|
||||||
|| !self
|
|
||||||
.follower_node
|
|
||||||
.matches_target(self.metadata.targets.as_deref())
|
|
||||||
{
|
|
||||||
tracing::warn!(
|
|
||||||
targets = ?self.metadata.targets,
|
|
||||||
"Either the leader or follower node do not support the targets of the file"
|
|
||||||
);
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut steps_executed = 0;
|
let mut steps_executed = 0;
|
||||||
for (step_idx, step) in self.case.steps_iterator().enumerate() {
|
for (step_idx, step) in self
|
||||||
let tracing_span = tracing::info_span!("Handling input", step_idx);
|
.case
|
||||||
|
.steps_iterator()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, v)| (StepIdx::new(idx), v))
|
||||||
|
{
|
||||||
|
let (leader_step_output, follower_step_output) = try_join!(
|
||||||
|
self.leader_state
|
||||||
|
.handle_step(self.metadata, &step, self.leader_node)
|
||||||
|
.instrument(info_span!(
|
||||||
|
"Handling Step",
|
||||||
|
%step_idx,
|
||||||
|
target = "Leader",
|
||||||
|
)),
|
||||||
|
self.follower_state
|
||||||
|
.handle_step(self.metadata, &step, self.follower_node)
|
||||||
|
.instrument(info_span!(
|
||||||
|
"Handling Step",
|
||||||
|
%step_idx,
|
||||||
|
target = "Follower",
|
||||||
|
))
|
||||||
|
)?;
|
||||||
|
|
||||||
let leader_step_output = self
|
|
||||||
.leader_state
|
|
||||||
.handle_step(self.metadata, self.case_idx, &step, self.leader_node)
|
|
||||||
.instrument(tracing_span.clone())
|
|
||||||
.await?;
|
|
||||||
let follower_step_output = self
|
|
||||||
.follower_state
|
|
||||||
.handle_step(self.metadata, self.case_idx, &step, self.follower_node)
|
|
||||||
.instrument(tracing_span)
|
|
||||||
.await?;
|
|
||||||
match (leader_step_output, follower_step_output) {
|
match (leader_step_output, follower_step_output) {
|
||||||
(
|
(StepOutput::FunctionCall(..), StepOutput::FunctionCall(..)) => {
|
||||||
StepOutput::FunctionCall(leader_receipt, _, leader_diff),
|
// TODO: We need to actually work out how/if we will compare the diff between
|
||||||
StepOutput::FunctionCall(follower_receipt, _, follower_diff),
|
// the leader and the follower. The diffs are almost guaranteed to be different
|
||||||
) => {
|
// from leader and follower and therefore without an actual strategy for this
|
||||||
if leader_diff == follower_diff {
|
// we have something that's guaranteed to fail. Even a simple call to some
|
||||||
tracing::debug!("State diffs match between leader and follower.");
|
// contract will produce two non-equal diffs because on the leader the contract
|
||||||
} else {
|
// has address X and on the follower it has address Y. On the leader contract X
|
||||||
tracing::debug!("State diffs mismatch between leader and follower.");
|
// contains address A in the state and on the follower it contains address B. So
|
||||||
Self::trace_diff_mode("Leader", &leader_diff);
|
// this isn't exactly a straightforward thing to do and I'm not even sure that
|
||||||
Self::trace_diff_mode("Follower", &follower_diff);
|
// it's possible to do. Once we have an actual strategy for doing the diffs we
|
||||||
}
|
// will implement it here. Until then, this remains empty.
|
||||||
|
|
||||||
if leader_receipt.logs() != follower_receipt.logs() {
|
|
||||||
tracing::debug!("Log/event mismatch between leader and follower.");
|
|
||||||
tracing::trace!("Leader logs: {:?}", leader_receipt.logs());
|
|
||||||
tracing::trace!("Follower logs: {:?}", follower_receipt.logs());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(StepOutput::BalanceAssertion, StepOutput::BalanceAssertion) => {}
|
(StepOutput::BalanceAssertion, StepOutput::BalanceAssertion) => {}
|
||||||
(StepOutput::StorageEmptyAssertion, StepOutput::StorageEmptyAssertion) => {}
|
(StepOutput::StorageEmptyAssertion, StepOutput::StorageEmptyAssertion) => {}
|
||||||
|
|||||||
+268
-202
@@ -1,8 +1,9 @@
|
|||||||
mod cached_compiler;
|
mod cached_compiler;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{BTreeMap, HashMap},
|
||||||
path::{Path, PathBuf},
|
io::{BufWriter, Write, stderr},
|
||||||
|
path::Path,
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
@@ -13,16 +14,18 @@ use alloy::{
|
|||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::stream::futures_unordered::FuturesUnordered;
|
use futures::stream;
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
|
use indexmap::IndexMap;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
use temp_dir::TempDir;
|
use temp_dir::TempDir;
|
||||||
use tokio::sync::mpsc;
|
use tokio::{sync::mpsc, try_join};
|
||||||
use tracing::{Instrument, Level};
|
use tracing::{debug, info, info_span, instrument};
|
||||||
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||||
|
|
||||||
use revive_dt_common::types::Mode;
|
use revive_dt_common::types::Mode;
|
||||||
use revive_dt_compiler::SolidityCompiler;
|
use revive_dt_compiler::{CompilerOutput, SolidityCompiler};
|
||||||
use revive_dt_config::*;
|
use revive_dt_config::*;
|
||||||
use revive_dt_core::{
|
use revive_dt_core::{
|
||||||
Geth, Kitchensink, Platform,
|
Geth, Kitchensink, Platform,
|
||||||
@@ -32,9 +35,10 @@ use revive_dt_format::{
|
|||||||
case::{Case, CaseIdx},
|
case::{Case, CaseIdx},
|
||||||
corpus::Corpus,
|
corpus::Corpus,
|
||||||
input::{Input, Step},
|
input::{Input, Step},
|
||||||
metadata::{ContractPathAndIdent, Metadata, MetadataFile},
|
metadata::{ContractPathAndIdent, MetadataFile},
|
||||||
|
mode::ParsedMode,
|
||||||
};
|
};
|
||||||
use revive_dt_node::pool::NodePool;
|
use revive_dt_node::{Node, pool::NodePool};
|
||||||
use revive_dt_report::reporter::{Report, Span};
|
use revive_dt_report::reporter::{Report, Span};
|
||||||
|
|
||||||
use crate::cached_compiler::CachedCompiler;
|
use crate::cached_compiler::CachedCompiler;
|
||||||
@@ -42,20 +46,28 @@ use crate::cached_compiler::CachedCompiler;
|
|||||||
static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
|
static TEMP_DIR: LazyLock<TempDir> = LazyLock::new(|| TempDir::new().unwrap());
|
||||||
|
|
||||||
/// this represents a single "test"; a mode, path and collection of cases.
|
/// this represents a single "test"; a mode, path and collection of cases.
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
struct Test {
|
struct Test<'a> {
|
||||||
metadata: Metadata,
|
metadata: &'a MetadataFile,
|
||||||
path: PathBuf,
|
metadata_file_path: &'a Path,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
case_idx: CaseIdx,
|
case_idx: CaseIdx,
|
||||||
case: Case,
|
case: &'a Case,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This represents the results that we gather from running test cases.
|
/// This represents the results that we gather from running test cases.
|
||||||
type CaseResult = Result<usize, anyhow::Error>;
|
type CaseResult = Result<usize, anyhow::Error>;
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let args = init_cli()?;
|
let (args, _guard) = init_cli()?;
|
||||||
|
info!(
|
||||||
|
leader = args.leader.to_string(),
|
||||||
|
follower = args.follower.to_string(),
|
||||||
|
working_directory = %args.directory().display(),
|
||||||
|
number_of_nodes = args.number_of_nodes,
|
||||||
|
invalidate_compilation_cache = args.invalidate_compilation_cache,
|
||||||
|
"Differential testing tool has been initialized"
|
||||||
|
);
|
||||||
|
|
||||||
let body = async {
|
let body = async {
|
||||||
for (corpus, tests) in collect_corpora(&args)? {
|
for (corpus, tests) in collect_corpora(&args)? {
|
||||||
@@ -77,15 +89,25 @@ fn main() -> anyhow::Result<()> {
|
|||||||
.block_on(body)
|
.block_on(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_cli() -> anyhow::Result<Arguments> {
|
fn init_cli() -> anyhow::Result<(Arguments, WorkerGuard)> {
|
||||||
|
let (writer, guard) = tracing_appender::non_blocking::NonBlockingBuilder::default()
|
||||||
|
.lossy(false)
|
||||||
|
// Assuming that each line contains 255 characters and that each character is one byte, then
|
||||||
|
// this means that our buffer is about 4GBs large.
|
||||||
|
.buffered_lines_limit(0x1000000)
|
||||||
|
.thread_name("buffered writer")
|
||||||
|
.finish(std::io::stdout());
|
||||||
|
|
||||||
let subscriber = FmtSubscriber::builder()
|
let subscriber = FmtSubscriber::builder()
|
||||||
.with_thread_ids(true)
|
.with_writer(writer)
|
||||||
.with_thread_names(true)
|
.with_thread_ids(false)
|
||||||
|
.with_thread_names(false)
|
||||||
.with_env_filter(EnvFilter::from_default_env())
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
.with_ansi(false)
|
.with_ansi(false)
|
||||||
.pretty()
|
.pretty()
|
||||||
.finish();
|
.finish();
|
||||||
tracing::subscriber::set_global_default(subscriber)?;
|
tracing::subscriber::set_global_default(subscriber)?;
|
||||||
|
info!("Differential testing tool is starting");
|
||||||
|
|
||||||
let mut args = Arguments::parse();
|
let mut args = Arguments::parse();
|
||||||
|
|
||||||
@@ -103,19 +125,25 @@ fn init_cli() -> anyhow::Result<Arguments> {
|
|||||||
args.temp_dir = Some(&TEMP_DIR);
|
args.temp_dir = Some(&TEMP_DIR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tracing::info!("workdir: {}", args.directory().display());
|
|
||||||
|
|
||||||
Ok(args)
|
Ok((args, guard))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", name = "Collecting Corpora", skip_all)]
|
||||||
fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<MetadataFile>>> {
|
fn collect_corpora(args: &Arguments) -> anyhow::Result<HashMap<Corpus, Vec<MetadataFile>>> {
|
||||||
let mut corpora = HashMap::new();
|
let mut corpora = HashMap::new();
|
||||||
|
|
||||||
for path in &args.corpus {
|
for path in &args.corpus {
|
||||||
|
let span = info_span!("Processing corpus file", path = %path.display());
|
||||||
|
let _guard = span.enter();
|
||||||
|
|
||||||
let corpus = Corpus::try_from_path(path)?;
|
let corpus = Corpus::try_from_path(path)?;
|
||||||
tracing::info!("found corpus: {}", path.display());
|
info!(
|
||||||
|
name = corpus.name(),
|
||||||
|
number_of_contained_paths = corpus.path_count(),
|
||||||
|
"Deserialized corpus file"
|
||||||
|
);
|
||||||
let tests = corpus.enumerate_tests();
|
let tests = corpus.enumerate_tests();
|
||||||
tracing::info!("corpus '{}' contains {} tests", &corpus.name(), tests.len());
|
|
||||||
corpora.insert(corpus, tests);
|
corpora.insert(corpus, tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +161,7 @@ where
|
|||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let (report_tx, report_rx) = mpsc::unbounded_channel::<(Test, CaseResult)>();
|
let (report_tx, report_rx) = mpsc::unbounded_channel::<(Test<'_>, CaseResult)>();
|
||||||
|
|
||||||
let tests = prepare_tests::<L, F>(args, metadata_files);
|
let tests = prepare_tests::<L, F>(args, metadata_files);
|
||||||
let driver_task = start_driver_task::<L, F>(args, tests, span, report_tx).await?;
|
let driver_task = start_driver_task::<L, F>(args, tests, span, report_tx).await?;
|
||||||
@@ -144,111 +172,148 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_tests<L, F>(
|
fn prepare_tests<'a, L, F>(
|
||||||
args: &Arguments,
|
args: &Arguments,
|
||||||
metadata_files: &[MetadataFile],
|
metadata_files: &'a [MetadataFile],
|
||||||
) -> impl Stream<Item = Test>
|
) -> impl Stream<Item = Test<'a>>
|
||||||
where
|
where
|
||||||
L: Platform,
|
L: Platform,
|
||||||
F: Platform,
|
F: Platform,
|
||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
metadata_files
|
let filtered_tests = metadata_files
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(
|
.flat_map(|metadata_file| {
|
||||||
|MetadataFile {
|
metadata_file
|
||||||
path,
|
.cases
|
||||||
content: metadata,
|
.iter()
|
||||||
}| {
|
.enumerate()
|
||||||
metadata
|
.map(move |(case_idx, case)| (metadata_file, case_idx, case))
|
||||||
.cases
|
})
|
||||||
.iter()
|
// Flatten over the modes, prefer the case modes over the metadata file modes.
|
||||||
.enumerate()
|
.flat_map(|(metadata_file, case_idx, case)| {
|
||||||
.flat_map(move |(case_idx, case)| {
|
case.modes
|
||||||
metadata
|
.as_ref()
|
||||||
.solc_modes()
|
.or(metadata_file.modes.as_ref())
|
||||||
.into_iter()
|
.map(|modes| ParsedMode::many_to_modes(modes.iter()).collect::<Vec<_>>())
|
||||||
.map(move |solc_mode| (path, metadata, case_idx, case, solc_mode))
|
.unwrap_or(Mode::all().collect())
|
||||||
})
|
.into_iter()
|
||||||
|
.map(move |mode| (metadata_file, case_idx, case, mode))
|
||||||
|
})
|
||||||
|
.fold(
|
||||||
|
IndexMap::<_, BTreeMap<_, Vec<_>>>::new(),
|
||||||
|
|mut map, (metadata_file, case_idx, case, mode)| {
|
||||||
|
let test = Test {
|
||||||
|
metadata: metadata_file,
|
||||||
|
metadata_file_path: metadata_file.metadata_file_path.as_path(),
|
||||||
|
mode: mode.clone(),
|
||||||
|
case_idx: CaseIdx::new(case_idx),
|
||||||
|
case,
|
||||||
|
};
|
||||||
|
map.entry(mode)
|
||||||
|
.or_default()
|
||||||
|
.entry(test.case_idx)
|
||||||
|
.or_default()
|
||||||
|
.push(test);
|
||||||
|
map
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.filter(
|
.into_values()
|
||||||
|(metadata_file_path, metadata, _, _, _)| match metadata.ignore {
|
.flatten()
|
||||||
Some(true) => {
|
.flat_map(|(_, value)| value.into_iter())
|
||||||
tracing::warn!(
|
// Filter the test out if the leader and follower do not support the target.
|
||||||
metadata_file_path = %metadata_file_path.display(),
|
.filter(|test| {
|
||||||
"Ignoring metadata file"
|
let leader_support =
|
||||||
);
|
<L::Blockchain as Node>::matches_target(test.metadata.targets.as_deref());
|
||||||
false
|
let follower_support =
|
||||||
}
|
<F::Blockchain as Node>::matches_target(test.metadata.targets.as_deref());
|
||||||
Some(false) | None => true,
|
let is_allowed = leader_support && follower_support;
|
||||||
},
|
|
||||||
)
|
if !is_allowed {
|
||||||
.filter(
|
debug!(
|
||||||
|(metadata_file_path, _, case_idx, case, _)| match case.ignore {
|
file_path = %test.metadata.relative_path().display(),
|
||||||
Some(true) => {
|
leader_support,
|
||||||
tracing::warn!(
|
follower_support,
|
||||||
metadata_file_path = %metadata_file_path.display(),
|
"Target is not supported, throwing metadata file out"
|
||||||
case_idx,
|
)
|
||||||
case_name = ?case.name,
|
}
|
||||||
"Ignoring case"
|
|
||||||
);
|
is_allowed
|
||||||
false
|
})
|
||||||
}
|
// Filter the test out if the metadata file is ignored.
|
||||||
Some(false) | None => true,
|
.filter(|test| {
|
||||||
},
|
if test.metadata.ignore.is_some_and(|ignore| ignore) {
|
||||||
)
|
debug!(
|
||||||
.filter(|(metadata_file_path, metadata, ..)| match metadata.required_evm_version {
|
file_path = %test.metadata.relative_path().display(),
|
||||||
Some(evm_version_requirement) => {
|
"Metadata file is ignored, throwing case out"
|
||||||
let is_allowed = evm_version_requirement
|
);
|
||||||
.matches(&<L::Blockchain as revive_dt_node::Node>::evm_version())
|
false
|
||||||
&& evm_version_requirement
|
} else {
|
||||||
.matches(&<F::Blockchain as revive_dt_node::Node>::evm_version());
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Filter the test case if the case is ignored.
|
||||||
|
.filter(|test| {
|
||||||
|
if test.case.ignore.is_some_and(|ignore| ignore) {
|
||||||
|
debug!(
|
||||||
|
file_path = %test.metadata.relative_path().display(),
|
||||||
|
case_idx = %test.case_idx,
|
||||||
|
"Case is ignored, throwing case out"
|
||||||
|
);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Filtering based on the EVM version compatibility
|
||||||
|
.filter(|test| {
|
||||||
|
if let Some(evm_version_requirement) = test.metadata.required_evm_version {
|
||||||
|
let leader_compatibility = evm_version_requirement
|
||||||
|
.matches(&<L::Blockchain as revive_dt_node::Node>::evm_version());
|
||||||
|
let follower_compatibility = evm_version_requirement
|
||||||
|
.matches(&<F::Blockchain as revive_dt_node::Node>::evm_version());
|
||||||
|
let is_allowed = leader_compatibility && follower_compatibility;
|
||||||
|
|
||||||
if !is_allowed {
|
if !is_allowed {
|
||||||
tracing::warn!(
|
debug!(
|
||||||
metadata_file_path = %metadata_file_path.display(),
|
file_path = %test.metadata.relative_path().display(),
|
||||||
leader_evm_version = %<L::Blockchain as revive_dt_node::Node>::evm_version(),
|
case_idx = %test.case_idx,
|
||||||
follower_evm_version = %<F::Blockchain as revive_dt_node::Node>::evm_version(),
|
leader_compatibility,
|
||||||
version_requirement = %evm_version_requirement,
|
follower_compatibility,
|
||||||
"Skipped test since the EVM version requirement was not fulfilled."
|
"EVM Version is incompatible, throwing case out"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
is_allowed
|
is_allowed
|
||||||
}
|
|
||||||
None => true,
|
|
||||||
})
|
|
||||||
.map(|(metadata_file_path, metadata, case_idx, case, solc_mode)| {
|
|
||||||
Test {
|
|
||||||
metadata: metadata.clone(),
|
|
||||||
path: metadata_file_path.to_path_buf(),
|
|
||||||
mode: solc_mode,
|
|
||||||
case_idx: case_idx.into(),
|
|
||||||
case: case.clone(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(async |test| test)
|
|
||||||
.collect::<FuturesUnordered<_>>()
|
|
||||||
.filter_map(async move |test| {
|
|
||||||
// Check that both compilers support this test, else we skip it
|
|
||||||
let is_supported = does_compiler_support_mode::<L>(args, &test.mode).await.ok().unwrap_or(false) &&
|
|
||||||
does_compiler_support_mode::<F>(args, &test.mode).await.ok().unwrap_or(false);
|
|
||||||
|
|
||||||
// We filter_map to avoid needing to clone `test`, but return it as-is.
|
|
||||||
if is_supported {
|
|
||||||
Some(test)
|
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!(
|
true
|
||||||
metadata_file_path = %test.path.display(),
|
|
||||||
case_idx = %test.case_idx,
|
|
||||||
case_name = ?test.case.name,
|
|
||||||
mode = %test.mode,
|
|
||||||
"Skipping test as one or both of the compilers don't support it"
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream::iter(filtered_tests)
|
||||||
|
// Filter based on the compiler compatibility
|
||||||
|
.filter_map(move |test| async move {
|
||||||
|
let leader_support = does_compiler_support_mode::<L>(args, &test.mode)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap_or(false);
|
||||||
|
let follower_support = does_compiler_support_mode::<F>(args, &test.mode)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap_or(false);
|
||||||
|
let is_allowed = leader_support && follower_support;
|
||||||
|
|
||||||
|
if !is_allowed {
|
||||||
|
debug!(
|
||||||
|
file_path = %test.metadata.relative_path().display(),
|
||||||
|
leader_support,
|
||||||
|
follower_support,
|
||||||
|
"Compilers do not support this, throwing case out"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
is_allowed.then_some(test)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +324,7 @@ async fn does_compiler_support_mode<P: Platform>(
|
|||||||
let compiler_version_or_requirement = mode.compiler_version_to_use(args.solc.clone());
|
let compiler_version_or_requirement = mode.compiler_version_to_use(args.solc.clone());
|
||||||
let compiler_path =
|
let compiler_path =
|
||||||
P::Compiler::get_compiler_executable(args, compiler_version_or_requirement).await?;
|
P::Compiler::get_compiler_executable(args, compiler_version_or_requirement).await?;
|
||||||
let compiler_version = P::Compiler::new(compiler_path.clone()).version()?;
|
let compiler_version = P::Compiler::new(compiler_path.clone()).version().await?;
|
||||||
|
|
||||||
Ok(P::Compiler::supports_mode(
|
Ok(P::Compiler::supports_mode(
|
||||||
&compiler_version,
|
&compiler_version,
|
||||||
@@ -268,11 +333,11 @@ async fn does_compiler_support_mode<P: Platform>(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_driver_task<L, F>(
|
async fn start_driver_task<'a, L, F>(
|
||||||
args: &Arguments,
|
args: &Arguments,
|
||||||
tests: impl Stream<Item = Test>,
|
tests: impl Stream<Item = Test<'a>>,
|
||||||
span: Span,
|
span: Span,
|
||||||
report_tx: mpsc::UnboundedSender<(Test, CaseResult)>,
|
report_tx: mpsc::UnboundedSender<(Test<'a>, CaseResult)>,
|
||||||
) -> anyhow::Result<impl Future<Output = ()>>
|
) -> anyhow::Result<impl Future<Output = ()>>
|
||||||
where
|
where
|
||||||
L: Platform,
|
L: Platform,
|
||||||
@@ -310,19 +375,11 @@ where
|
|||||||
let leader_node = leader_nodes.round_robbin();
|
let leader_node = leader_nodes.round_robbin();
|
||||||
let follower_node = follower_nodes.round_robbin();
|
let follower_node = follower_nodes.round_robbin();
|
||||||
|
|
||||||
let tracing_span = tracing::span!(
|
|
||||||
Level::INFO,
|
|
||||||
"Running driver",
|
|
||||||
metadata_file_path = %test.path.display(),
|
|
||||||
case_idx = ?test.case_idx,
|
|
||||||
solc_mode = ?test.mode,
|
|
||||||
);
|
|
||||||
|
|
||||||
let result = handle_case_driver::<L, F>(
|
let result = handle_case_driver::<L, F>(
|
||||||
&test.path,
|
test.metadata_file_path,
|
||||||
&test.metadata,
|
test.metadata,
|
||||||
test.case_idx,
|
test.case_idx,
|
||||||
&test.case,
|
test.case,
|
||||||
test.mode.clone(),
|
test.mode.clone(),
|
||||||
args,
|
args,
|
||||||
cached_compiler,
|
cached_compiler,
|
||||||
@@ -330,7 +387,6 @@ where
|
|||||||
follower_node,
|
follower_node,
|
||||||
span,
|
span,
|
||||||
)
|
)
|
||||||
.instrument(tracing_span)
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
report_tx
|
report_tx
|
||||||
@@ -341,7 +397,7 @@ where
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseResult)>) {
|
async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test<'_>, CaseResult)>) {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
const GREEN: &str = "\x1B[32m";
|
const GREEN: &str = "\x1B[32m";
|
||||||
@@ -355,22 +411,25 @@ async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseR
|
|||||||
let mut failures = vec![];
|
let mut failures = vec![];
|
||||||
|
|
||||||
// Wait for reports to come from our test runner. When the channel closes, this ends.
|
// Wait for reports to come from our test runner. When the channel closes, this ends.
|
||||||
|
let mut buf = BufWriter::new(stderr());
|
||||||
while let Some((test, case_result)) = report_rx.recv().await {
|
while let Some((test, case_result)) = report_rx.recv().await {
|
||||||
let case_name = test.case.name.as_deref().unwrap_or("unnamed_case");
|
let case_name = test.case.name.as_deref().unwrap_or("unnamed_case");
|
||||||
let case_idx = test.case_idx;
|
let case_idx = test.case_idx;
|
||||||
let test_path = test.path.display();
|
let test_path = test.metadata_file_path.display();
|
||||||
let test_mode = test.mode.clone();
|
let test_mode = test.mode.clone();
|
||||||
|
|
||||||
match case_result {
|
match case_result {
|
||||||
Ok(_inputs) => {
|
Ok(_inputs) => {
|
||||||
number_of_successes += 1;
|
number_of_successes += 1;
|
||||||
eprintln!(
|
let _ = writeln!(
|
||||||
|
buf,
|
||||||
"{GREEN}Case Succeeded:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
"{GREEN}Case Succeeded:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
number_of_failures += 1;
|
number_of_failures += 1;
|
||||||
eprintln!(
|
let _ = writeln!(
|
||||||
|
buf,
|
||||||
"{RED}Case Failed:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
"{RED}Case Failed:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
||||||
);
|
);
|
||||||
failures.push((test, err));
|
failures.push((test, err));
|
||||||
@@ -378,29 +437,31 @@ async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!();
|
let _ = writeln!(buf,);
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
// Now, log the failures with more complete errors at the bottom, like `cargo test` does, so
|
// Now, log the failures with more complete errors at the bottom, like `cargo test` does, so
|
||||||
// that we don't have to scroll through the entire output to find them.
|
// that we don't have to scroll through the entire output to find them.
|
||||||
if !failures.is_empty() {
|
if !failures.is_empty() {
|
||||||
eprintln!("{BOLD}Failures:{BOLD_RESET}\n");
|
let _ = writeln!(buf, "{BOLD}Failures:{BOLD_RESET}\n");
|
||||||
|
|
||||||
for failure in failures {
|
for failure in failures {
|
||||||
let (test, err) = failure;
|
let (test, err) = failure;
|
||||||
let case_name = test.case.name.as_deref().unwrap_or("unnamed_case");
|
let case_name = test.case.name.as_deref().unwrap_or("unnamed_case");
|
||||||
let case_idx = test.case_idx;
|
let case_idx = test.case_idx;
|
||||||
let test_path = test.path.display();
|
let test_path = test.metadata_file_path.display();
|
||||||
let test_mode = test.mode.clone();
|
let test_mode = test.mode.clone();
|
||||||
|
|
||||||
eprintln!(
|
let _ = writeln!(
|
||||||
|
buf,
|
||||||
"---- {RED}Case Failed:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode}) ----\n\n{err}\n"
|
"---- {RED}Case Failed:{COLOUR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode}) ----\n\n{err}\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary at the end.
|
// Summary at the end.
|
||||||
eprintln!(
|
let _ = writeln!(
|
||||||
|
buf,
|
||||||
"{} cases: {GREEN}{number_of_successes}{COLOUR_RESET} cases succeeded, {RED}{number_of_failures}{COLOUR_RESET} cases failed in {} seconds",
|
"{} cases: {GREEN}{number_of_successes}{COLOUR_RESET} cases succeeded, {RED}{number_of_failures}{COLOUR_RESET} cases failed in {} seconds",
|
||||||
number_of_successes + number_of_failures,
|
number_of_successes + number_of_failures,
|
||||||
elapsed.as_secs()
|
elapsed.as_secs()
|
||||||
@@ -408,9 +469,22 @@ async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test, CaseR
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[instrument(
|
||||||
|
level = "info",
|
||||||
|
name = "Handling Case"
|
||||||
|
skip_all,
|
||||||
|
fields(
|
||||||
|
metadata_file_path = %metadata.relative_path().display(),
|
||||||
|
mode = %mode,
|
||||||
|
%case_idx,
|
||||||
|
case_name = case.name.as_deref().unwrap_or("Unnamed Case"),
|
||||||
|
leader_node = leader_node.id(),
|
||||||
|
follower_node = follower_node.id(),
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn handle_case_driver<L, F>(
|
async fn handle_case_driver<L, F>(
|
||||||
metadata_file_path: &Path,
|
metadata_file_path: &Path,
|
||||||
metadata: &Metadata,
|
metadata: &MetadataFile,
|
||||||
case_idx: CaseIdx,
|
case_idx: CaseIdx,
|
||||||
case: &Case,
|
case: &Case,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
@@ -426,16 +500,23 @@ where
|
|||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let leader_pre_link_contracts = cached_compiler
|
let (
|
||||||
.compile_contracts::<L>(metadata, metadata_file_path, &mode, config, None)
|
(
|
||||||
.await?
|
CompilerOutput {
|
||||||
.0
|
contracts: leader_pre_link_contracts,
|
||||||
.contracts;
|
},
|
||||||
let follower_pre_link_contracts = cached_compiler
|
_,
|
||||||
.compile_contracts::<F>(metadata, metadata_file_path, &mode, config, None)
|
),
|
||||||
.await?
|
(
|
||||||
.0
|
CompilerOutput {
|
||||||
.contracts;
|
contracts: follower_pre_link_contracts,
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
),
|
||||||
|
) = try_join!(
|
||||||
|
cached_compiler.compile_contracts::<L>(metadata, metadata_file_path, &mode, config, None),
|
||||||
|
cached_compiler.compile_contracts::<F>(metadata, metadata_file_path, &mode, config, None)
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut leader_deployed_libraries = None::<HashMap<_, _>>;
|
let mut leader_deployed_libraries = None::<HashMap<_, _>>;
|
||||||
let mut follower_deployed_libraries = None::<HashMap<_, _>>;
|
let mut follower_deployed_libraries = None::<HashMap<_, _>>;
|
||||||
@@ -446,6 +527,8 @@ where
|
|||||||
.flatten()
|
.flatten()
|
||||||
.flat_map(|(_, map)| map.values())
|
.flat_map(|(_, map)| map.values())
|
||||||
{
|
{
|
||||||
|
debug!(%library_instance, "Deploying Library Instance");
|
||||||
|
|
||||||
let ContractPathAndIdent {
|
let ContractPathAndIdent {
|
||||||
contract_source_path: library_source_path,
|
contract_source_path: library_source_path,
|
||||||
contract_ident: library_ident,
|
contract_ident: library_ident,
|
||||||
@@ -465,24 +548,12 @@ where
|
|||||||
let leader_code = match alloy::hex::decode(leader_code) {
|
let leader_code = match alloy::hex::decode(leader_code) {
|
||||||
Ok(code) => code,
|
Ok(code) => code,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!(
|
|
||||||
?error,
|
|
||||||
contract_source_path = library_source_path.display().to_string(),
|
|
||||||
contract_ident = library_ident.as_ref(),
|
|
||||||
"Failed to hex-decode byte code - This could possibly mean that the bytecode requires linking"
|
|
||||||
);
|
|
||||||
anyhow::bail!("Failed to hex-decode the byte code {}", error)
|
anyhow::bail!("Failed to hex-decode the byte code {}", error)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let follower_code = match alloy::hex::decode(follower_code) {
|
let follower_code = match alloy::hex::decode(follower_code) {
|
||||||
Ok(code) => code,
|
Ok(code) => code,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!(
|
|
||||||
?error,
|
|
||||||
contract_source_path = library_source_path.display().to_string(),
|
|
||||||
contract_ident = library_ident.as_ref(),
|
|
||||||
"Failed to hex-decode byte code - This could possibly mean that the bytecode requires linking"
|
|
||||||
);
|
|
||||||
anyhow::bail!("Failed to hex-decode the byte code {}", error)
|
anyhow::bail!("Failed to hex-decode the byte code {}", error)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -509,46 +580,28 @@ where
|
|||||||
follower_code,
|
follower_code,
|
||||||
);
|
);
|
||||||
|
|
||||||
let leader_receipt = match leader_node.execute_transaction(leader_tx).await {
|
let (leader_receipt, follower_receipt) = try_join!(
|
||||||
Ok(receipt) => receipt,
|
leader_node.execute_transaction(leader_tx),
|
||||||
Err(error) => {
|
follower_node.execute_transaction(follower_tx)
|
||||||
tracing::error!(
|
)?;
|
||||||
node = std::any::type_name::<L>(),
|
|
||||||
?error,
|
|
||||||
"Contract deployment transaction failed."
|
|
||||||
);
|
|
||||||
return Err(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let follower_receipt = match follower_node.execute_transaction(follower_tx).await {
|
|
||||||
Ok(receipt) => receipt,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!(
|
|
||||||
node = std::any::type_name::<F>(),
|
|
||||||
?error,
|
|
||||||
"Contract deployment transaction failed."
|
|
||||||
);
|
|
||||||
return Err(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::info!(
|
debug!(
|
||||||
?library_instance,
|
?library_instance,
|
||||||
library_address = ?leader_receipt.contract_address,
|
library_address = ?leader_receipt.contract_address,
|
||||||
"Deployed library to leader"
|
"Deployed library to leader"
|
||||||
);
|
);
|
||||||
tracing::info!(
|
debug!(
|
||||||
?library_instance,
|
?library_instance,
|
||||||
library_address = ?follower_receipt.contract_address,
|
library_address = ?follower_receipt.contract_address,
|
||||||
"Deployed library to follower"
|
"Deployed library to follower"
|
||||||
);
|
);
|
||||||
|
|
||||||
let Some(leader_library_address) = leader_receipt.contract_address else {
|
let leader_library_address = leader_receipt
|
||||||
anyhow::bail!("Contract deployment didn't return an address");
|
.contract_address
|
||||||
};
|
.context("Contract deployment didn't return an address")?;
|
||||||
let Some(follower_library_address) = follower_receipt.contract_address else {
|
let follower_library_address = follower_receipt
|
||||||
anyhow::bail!("Contract deployment didn't return an address");
|
.contract_address
|
||||||
};
|
.context("Contract deployment didn't return an address")?;
|
||||||
|
|
||||||
leader_deployed_libraries.get_or_insert_default().insert(
|
leader_deployed_libraries.get_or_insert_default().insert(
|
||||||
library_instance.clone(),
|
library_instance.clone(),
|
||||||
@@ -568,46 +621,59 @@ where
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (leader_post_link_contracts, leader_compiler_version) = cached_compiler
|
let (
|
||||||
.compile_contracts::<L>(
|
(
|
||||||
|
CompilerOutput {
|
||||||
|
contracts: leader_post_link_contracts,
|
||||||
|
},
|
||||||
|
leader_compiler_version,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
CompilerOutput {
|
||||||
|
contracts: follower_post_link_contracts,
|
||||||
|
},
|
||||||
|
follower_compiler_version,
|
||||||
|
),
|
||||||
|
) = try_join!(
|
||||||
|
cached_compiler.compile_contracts::<L>(
|
||||||
metadata,
|
metadata,
|
||||||
metadata_file_path,
|
metadata_file_path,
|
||||||
&mode,
|
&mode,
|
||||||
config,
|
config,
|
||||||
leader_deployed_libraries.as_ref(),
|
leader_deployed_libraries.as_ref()
|
||||||
)
|
),
|
||||||
.await?;
|
cached_compiler.compile_contracts::<F>(
|
||||||
let (follower_post_link_contracts, follower_compiler_version) = cached_compiler
|
|
||||||
.compile_contracts::<F>(
|
|
||||||
metadata,
|
metadata,
|
||||||
metadata_file_path,
|
metadata_file_path,
|
||||||
&mode,
|
&mode,
|
||||||
config,
|
config,
|
||||||
follower_deployed_libraries.as_ref(),
|
follower_deployed_libraries.as_ref()
|
||||||
)
|
)
|
||||||
.await?;
|
)?;
|
||||||
|
|
||||||
let leader_state = CaseState::<L>::new(
|
let leader_state = CaseState::<L>::new(
|
||||||
leader_compiler_version,
|
leader_compiler_version,
|
||||||
leader_post_link_contracts.contracts,
|
leader_post_link_contracts,
|
||||||
leader_deployed_libraries.unwrap_or_default(),
|
leader_deployed_libraries.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
let follower_state = CaseState::<F>::new(
|
let follower_state = CaseState::<F>::new(
|
||||||
follower_compiler_version,
|
follower_compiler_version,
|
||||||
follower_post_link_contracts.contracts,
|
follower_post_link_contracts,
|
||||||
follower_deployed_libraries.unwrap_or_default(),
|
follower_deployed_libraries.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut driver = CaseDriver::<L, F>::new(
|
let mut driver = CaseDriver::<L, F>::new(
|
||||||
metadata,
|
metadata,
|
||||||
case,
|
case,
|
||||||
case_idx,
|
|
||||||
leader_node,
|
leader_node,
|
||||||
follower_node,
|
follower_node,
|
||||||
leader_state,
|
leader_state,
|
||||||
follower_state,
|
follower_state,
|
||||||
);
|
);
|
||||||
driver.execute().await
|
driver
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.inspect(|steps_executed| info!(steps_executed, "Case succeeded"))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_corpus(
|
async fn execute_corpus(
|
||||||
@@ -657,7 +723,7 @@ async fn compile_corpus(
|
|||||||
let _ = cached_compiler
|
let _ = cached_compiler
|
||||||
.compile_contracts::<Geth>(
|
.compile_contracts::<Geth>(
|
||||||
metadata,
|
metadata,
|
||||||
metadata.path.as_path(),
|
metadata.metadata_file_path.as_path(),
|
||||||
&mode,
|
&mode,
|
||||||
config,
|
config,
|
||||||
None,
|
None,
|
||||||
@@ -668,7 +734,7 @@ async fn compile_corpus(
|
|||||||
let _ = cached_compiler
|
let _ = cached_compiler
|
||||||
.compile_contracts::<Kitchensink>(
|
.compile_contracts::<Kitchensink>(
|
||||||
metadata,
|
metadata,
|
||||||
metadata.path.as_path(),
|
metadata.metadata_file_path.as_path(),
|
||||||
&mode,
|
&mode,
|
||||||
config,
|
config,
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ alloy = { workspace = true }
|
|||||||
alloy-primitives = { workspace = true }
|
alloy-primitives = { workspace = true }
|
||||||
alloy-sol-types = { workspace = true }
|
alloy-sol-types = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
futures = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
@@ -25,3 +26,6 @@ serde_json = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use revive_dt_common::macros::define_wrapper_type;
|
use revive_dt_common::{macros::define_wrapper_type, types::Mode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
input::{Expected, Step},
|
input::{Expected, Step},
|
||||||
@@ -60,16 +60,17 @@ impl Case {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn solc_modes(&self) -> Vec<Mode> {
|
||||||
|
match &self.modes {
|
||||||
|
Some(modes) => ParsedMode::many_to_modes(modes.iter()).collect(),
|
||||||
|
None => Mode::all().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
/// A wrapper type for the index of test cases found in metadata file.
|
/// A wrapper type for the index of test cases found in metadata file.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct CaseIdx(usize);
|
pub struct CaseIdx(usize) impl Display;
|
||||||
);
|
);
|
||||||
|
|
||||||
impl std::fmt::Display for CaseIdx {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+54
-55
@@ -3,10 +3,11 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use revive_dt_common::cached_fs::read_dir;
|
use revive_dt_common::iterators::FilesWithExtensionIterator;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::metadata::MetadataFile;
|
use crate::metadata::{Metadata, MetadataFile};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
@@ -18,7 +19,7 @@ pub enum Corpus {
|
|||||||
impl Corpus {
|
impl Corpus {
|
||||||
pub fn try_from_path(file_path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
pub fn try_from_path(file_path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||||
let mut corpus = File::open(file_path.as_ref())
|
let mut corpus = File::open(file_path.as_ref())
|
||||||
.map_err(Into::<anyhow::Error>::into)
|
.map_err(anyhow::Error::from)
|
||||||
.and_then(|file| serde_json::from_reader::<_, Corpus>(file).map_err(Into::into))?;
|
.and_then(|file| serde_json::from_reader::<_, Corpus>(file).map_err(Into::into))?;
|
||||||
|
|
||||||
for path in corpus.paths_iter_mut() {
|
for path in corpus.paths_iter_mut() {
|
||||||
@@ -42,10 +43,52 @@ impl Corpus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn enumerate_tests(&self) -> Vec<MetadataFile> {
|
pub fn enumerate_tests(&self) -> Vec<MetadataFile> {
|
||||||
let mut tests = Vec::new();
|
let mut tests = self
|
||||||
for path in self.paths_iter() {
|
.paths_iter()
|
||||||
collect_metadata(path, &mut tests);
|
.flat_map(|root_path| {
|
||||||
}
|
if !root_path.is_dir() {
|
||||||
|
Box::new(std::iter::once(root_path.to_path_buf()))
|
||||||
|
as Box<dyn Iterator<Item = _>>
|
||||||
|
} else {
|
||||||
|
Box::new(
|
||||||
|
FilesWithExtensionIterator::new(root_path)
|
||||||
|
.with_use_cached_fs(true)
|
||||||
|
.with_allowed_extension("sol")
|
||||||
|
.with_allowed_extension("json"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.map(move |metadata_file_path| (root_path, metadata_file_path))
|
||||||
|
})
|
||||||
|
.filter_map(|(root_path, metadata_file_path)| {
|
||||||
|
Metadata::try_from_file(&metadata_file_path)
|
||||||
|
.or_else(|| {
|
||||||
|
debug!(
|
||||||
|
discovered_from = %root_path.display(),
|
||||||
|
metadata_file_path = %metadata_file_path.display(),
|
||||||
|
"Skipping file since it doesn't contain valid metadata"
|
||||||
|
);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.map(|metadata| MetadataFile {
|
||||||
|
metadata_file_path,
|
||||||
|
corpus_file_path: root_path.to_path_buf(),
|
||||||
|
content: metadata,
|
||||||
|
})
|
||||||
|
.inspect(|metadata_file| {
|
||||||
|
debug!(
|
||||||
|
metadata_file_path = %metadata_file.relative_path().display(),
|
||||||
|
"Loaded metadata file"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
tests.sort_by(|a, b| a.metadata_file_path.cmp(&b.metadata_file_path));
|
||||||
|
tests.dedup_by(|a, b| a.metadata_file_path == b.metadata_file_path);
|
||||||
|
info!(
|
||||||
|
len = tests.len(),
|
||||||
|
corpus_name = self.name(),
|
||||||
|
"Found tests in Corpus"
|
||||||
|
);
|
||||||
tests
|
tests
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,55 +119,11 @@ impl Corpus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively walks `path` and parses any JSON or Solidity file into a test
|
pub fn path_count(&self) -> usize {
|
||||||
/// definition [Metadata].
|
match self {
|
||||||
///
|
Corpus::SinglePath { .. } => 1,
|
||||||
/// Found tests are inserted into `tests`.
|
Corpus::MultiplePaths { paths, .. } => paths.len(),
|
||||||
///
|
|
||||||
/// `path` is expected to be a directory.
|
|
||||||
pub fn collect_metadata(path: &Path, tests: &mut Vec<MetadataFile>) {
|
|
||||||
if path.is_dir() {
|
|
||||||
let dir_entry = match read_dir(path) {
|
|
||||||
Ok(dir_entry) => dir_entry,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!("failed to read dir '{}': {error}", path.display());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for path in dir_entry {
|
|
||||||
let path = match path {
|
|
||||||
Ok(entry) => entry,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!("error reading dir entry: {error}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if path.is_dir() {
|
|
||||||
collect_metadata(&path, tests);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if path.is_file() {
|
|
||||||
if let Some(metadata) = MetadataFile::try_from_file(&path) {
|
|
||||||
tests.push(metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let Some(extension) = path.extension() else {
|
|
||||||
tracing::error!("Failed to get file extension");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if extension.eq_ignore_ascii_case("sol") || extension.eq_ignore_ascii_case("json") {
|
|
||||||
if let Some(metadata) = MetadataFile::try_from_file(path) {
|
|
||||||
tests.push(metadata)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::error!(?extension, "Unsupported file extension");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-57
@@ -2,7 +2,6 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
eips::BlockNumberOrTag,
|
eips::BlockNumberOrTag,
|
||||||
hex::ToHexExt,
|
|
||||||
json_abi::Function,
|
json_abi::Function,
|
||||||
network::TransactionBuilder,
|
network::TransactionBuilder,
|
||||||
primitives::{Address, Bytes, U256},
|
primitives::{Address, Bytes, U256},
|
||||||
@@ -10,10 +9,12 @@ use alloy::{
|
|||||||
};
|
};
|
||||||
use alloy_primitives::{FixedBytes, utils::parse_units};
|
use alloy_primitives::{FixedBytes, utils::parse_units};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, stream};
|
||||||
use semver::VersionReq;
|
use semver::VersionReq;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use revive_dt_common::macros::define_wrapper_type;
|
use revive_dt_common::macros::define_wrapper_type;
|
||||||
|
use tracing::{Instrument, info_span, instrument};
|
||||||
|
|
||||||
use crate::traits::ResolverApi;
|
use crate::traits::ResolverApi;
|
||||||
use crate::{metadata::ContractInstance, traits::ResolutionContext};
|
use crate::{metadata::ContractInstance, traits::ResolutionContext};
|
||||||
@@ -33,6 +34,11 @@ pub enum Step {
|
|||||||
StorageEmptyAssertion(Box<StorageEmptyAssertion>),
|
StorageEmptyAssertion(Box<StorageEmptyAssertion>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
define_wrapper_type!(
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct StepIdx(usize) impl Display;
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
#[serde(default = "Input::default_caller")]
|
#[serde(default = "Input::default_caller")]
|
||||||
@@ -188,7 +194,7 @@ define_wrapper_type! {
|
|||||||
/// This represents an item in the [`Calldata::Compound`] variant.
|
/// This represents an item in the [`Calldata::Compound`] variant.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct CalldataItem(String);
|
pub struct CalldataItem(String) impl Display;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
@@ -233,7 +239,7 @@ pub enum Method {
|
|||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct EtherValue(U256);
|
pub struct EtherValue(U256) impl Display;
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
@@ -268,15 +274,9 @@ impl Input {
|
|||||||
}
|
}
|
||||||
Method::FunctionName(ref function_name) => {
|
Method::FunctionName(ref function_name) => {
|
||||||
let Some(abi) = context.deployed_contract_abi(&self.instance) else {
|
let Some(abi) = context.deployed_contract_abi(&self.instance) else {
|
||||||
tracing::error!(
|
|
||||||
contract_name = self.instance.as_ref(),
|
|
||||||
"Attempted to lookup ABI of contract but it wasn't found"
|
|
||||||
);
|
|
||||||
anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref());
|
anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref());
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!("ABI found for instance: {}", &self.instance.as_ref());
|
|
||||||
|
|
||||||
// We follow the same logic that's implemented in the matter-labs-tester where they resolve
|
// We follow the same logic that's implemented in the matter-labs-tester where they resolve
|
||||||
// the function name into a function selector and they assume that he function doesn't have
|
// the function name into a function selector and they assume that he function doesn't have
|
||||||
// any existing overloads.
|
// any existing overloads.
|
||||||
@@ -302,13 +302,6 @@ impl Input {
|
|||||||
.selector()
|
.selector()
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!("Functions found for instance: {}", self.instance.as_ref());
|
|
||||||
|
|
||||||
tracing::trace!(
|
|
||||||
"Starting encoding ABI's parameters for instance: {}",
|
|
||||||
self.instance.as_ref()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Allocating a vector that we will be using for the calldata. The vector size will be:
|
// Allocating a vector that we will be using for the calldata. The vector size will be:
|
||||||
// 4 bytes for the function selector.
|
// 4 bytes for the function selector.
|
||||||
// function.inputs.len() * 32 bytes for the arguments (each argument is a U256).
|
// function.inputs.len() * 32 bytes for the arguments (each argument is a U256).
|
||||||
@@ -435,17 +428,18 @@ impl Calldata {
|
|||||||
buffer.extend_from_slice(bytes);
|
buffer.extend_from_slice(bytes);
|
||||||
}
|
}
|
||||||
Calldata::Compound(items) => {
|
Calldata::Compound(items) => {
|
||||||
for (arg_idx, arg) in items.iter().enumerate() {
|
let resolved = stream::iter(items.iter().enumerate())
|
||||||
match arg.resolve(resolver, context).await {
|
.map(|(arg_idx, arg)| async move {
|
||||||
Ok(resolved) => {
|
arg.resolve(resolver, context)
|
||||||
buffer.extend(resolved.to_be_bytes::<32>());
|
.instrument(info_span!("Resolving argument", %arg, arg_idx))
|
||||||
}
|
.map_ok(|value| value.to_be_bytes::<32>())
|
||||||
Err(error) => {
|
.await
|
||||||
tracing::error!(?arg, arg_idx, ?error, "Failed to resolve argument");
|
})
|
||||||
return Err(error);
|
.buffered(0xFF)
|
||||||
}
|
.try_collect::<Vec<_>>()
|
||||||
};
|
.await?;
|
||||||
}
|
|
||||||
|
buffer.extend(resolved.into_iter().flatten());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -468,36 +462,37 @@ impl Calldata {
|
|||||||
match self {
|
match self {
|
||||||
Calldata::Single(calldata) => Ok(calldata == other),
|
Calldata::Single(calldata) => Ok(calldata == other),
|
||||||
Calldata::Compound(items) => {
|
Calldata::Compound(items) => {
|
||||||
// Chunking the "other" calldata into 32 byte chunks since each
|
stream::iter(items.iter().zip(other.chunks(32)))
|
||||||
// one of the items in the compound calldata represents 32 bytes
|
.map(|(this, other)| async move {
|
||||||
for (this, other) in items.iter().zip(other.chunks(32)) {
|
// The matterlabs format supports wildcards and therefore we
|
||||||
// The matterlabs format supports wildcards and therefore we
|
// also need to support them.
|
||||||
// also need to support them.
|
if this.as_ref() == "*" {
|
||||||
if this.as_ref() == "*" {
|
return Ok::<_, anyhow::Error>(true);
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let other = if other.len() < 32 {
|
let other = if other.len() < 32 {
|
||||||
let mut vec = other.to_vec();
|
let mut vec = other.to_vec();
|
||||||
vec.resize(32, 0);
|
vec.resize(32, 0);
|
||||||
std::borrow::Cow::Owned(vec)
|
std::borrow::Cow::Owned(vec)
|
||||||
} else {
|
} else {
|
||||||
std::borrow::Cow::Borrowed(other)
|
std::borrow::Cow::Borrowed(other)
|
||||||
};
|
};
|
||||||
|
|
||||||
let this = this.resolve(resolver, context).await?;
|
let this = this.resolve(resolver, context).await?;
|
||||||
let other = U256::from_be_slice(&other);
|
let other = U256::from_be_slice(&other);
|
||||||
if this != other {
|
Ok(this == other)
|
||||||
return Ok(false);
|
})
|
||||||
}
|
.buffered(0xFF)
|
||||||
}
|
.all(|v| async move { v.is_ok_and(|v| v) })
|
||||||
Ok(true)
|
.map(Ok)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CalldataItem {
|
impl CalldataItem {
|
||||||
|
#[instrument(level = "info", skip_all, err)]
|
||||||
async fn resolve(
|
async fn resolve(
|
||||||
&self,
|
&self,
|
||||||
resolver: &impl ResolverApi,
|
resolver: &impl ResolverApi,
|
||||||
@@ -548,14 +543,7 @@ impl CalldataItem {
|
|||||||
match stack.as_slice() {
|
match stack.as_slice() {
|
||||||
// Empty stack means that we got an empty compound calldata which we resolve to zero.
|
// Empty stack means that we got an empty compound calldata which we resolve to zero.
|
||||||
[] => Ok(U256::ZERO),
|
[] => Ok(U256::ZERO),
|
||||||
[CalldataToken::Item(item)] => {
|
[CalldataToken::Item(item)] => Ok(*item),
|
||||||
tracing::debug!(
|
|
||||||
original = self.0,
|
|
||||||
resolved = item.to_be_bytes::<32>().encode_hex(),
|
|
||||||
"Resolved a Calldata item"
|
|
||||||
);
|
|
||||||
Ok(*item)
|
|
||||||
}
|
|
||||||
_ => Err(anyhow::anyhow!(
|
_ => Err(anyhow::anyhow!(
|
||||||
"Invalid calldata arithmetic operation - Invalid stack"
|
"Invalid calldata arithmetic operation - Invalid stack"
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use revive_dt_common::{
|
|||||||
cached_fs::read_to_string, iterators::FilesWithExtensionIterator, macros::define_wrapper_type,
|
cached_fs::read_to_string, iterators::FilesWithExtensionIterator, macros::define_wrapper_type,
|
||||||
types::Mode,
|
types::Mode,
|
||||||
};
|
};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{case::Case, mode::ParsedMode};
|
use crate::{case::Case, mode::ParsedMode};
|
||||||
|
|
||||||
@@ -24,16 +25,26 @@ pub const SOLIDITY_CASE_COMMENT_MARKER: &str = "//!";
|
|||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
||||||
pub struct MetadataFile {
|
pub struct MetadataFile {
|
||||||
pub path: PathBuf,
|
/// The path of the metadata file. This will either be a JSON or solidity file.
|
||||||
|
pub metadata_file_path: PathBuf,
|
||||||
|
|
||||||
|
/// This is the path contained within the corpus file. This could either be the path of some dir
|
||||||
|
/// or could be the actual metadata file path.
|
||||||
|
pub corpus_file_path: PathBuf,
|
||||||
|
|
||||||
|
/// The metadata contained within the file.
|
||||||
pub content: Metadata,
|
pub content: Metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetadataFile {
|
impl MetadataFile {
|
||||||
pub fn try_from_file(path: &Path) -> Option<Self> {
|
pub fn relative_path(&self) -> &Path {
|
||||||
Metadata::try_from_file(path).map(|metadata| Self {
|
if self.corpus_file_path.is_file() {
|
||||||
path: path.to_owned(),
|
&self.corpus_file_path
|
||||||
content: metadata,
|
} else {
|
||||||
})
|
self.metadata_file_path
|
||||||
|
.strip_prefix(&self.corpus_file_path)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +156,7 @@ impl Metadata {
|
|||||||
pub fn try_from_file(path: &Path) -> Option<Self> {
|
pub fn try_from_file(path: &Path) -> Option<Self> {
|
||||||
assert!(path.is_file(), "not a file: {}", path.display());
|
assert!(path.is_file(), "not a file: {}", path.display());
|
||||||
|
|
||||||
let Some(file_extension) = path.extension() else {
|
let file_extension = path.extension()?;
|
||||||
tracing::debug!("skipping corpus file: {}", path.display());
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if file_extension == METADATA_FILE_EXTENSION {
|
if file_extension == METADATA_FILE_EXTENSION {
|
||||||
return Self::try_from_json(path);
|
return Self::try_from_json(path);
|
||||||
@@ -158,18 +166,12 @@ impl Metadata {
|
|||||||
return Self::try_from_solidity(path);
|
return Self::try_from_solidity(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("ignoring invalid corpus file: {}", path.display());
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_from_json(path: &Path) -> Option<Self> {
|
fn try_from_json(path: &Path) -> Option<Self> {
|
||||||
let file = File::open(path)
|
let file = File::open(path)
|
||||||
.inspect_err(|error| {
|
.inspect_err(|err| error!(path = %path.display(), %err, "Failed to open file"))
|
||||||
tracing::error!(
|
|
||||||
"opening JSON test metadata file '{}' error: {error}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
match serde_json::from_reader::<_, Metadata>(file) {
|
match serde_json::from_reader::<_, Metadata>(file) {
|
||||||
@@ -177,11 +179,8 @@ impl Metadata {
|
|||||||
metadata.file_path = Some(path.to_path_buf());
|
metadata.file_path = Some(path.to_path_buf());
|
||||||
Some(metadata)
|
Some(metadata)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(err) => {
|
||||||
tracing::error!(
|
error!(path = %path.display(), %err, "Deserialization of metadata failed");
|
||||||
"parsing JSON test metadata file '{}' error: {error}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,12 +188,7 @@ impl Metadata {
|
|||||||
|
|
||||||
fn try_from_solidity(path: &Path) -> Option<Self> {
|
fn try_from_solidity(path: &Path) -> Option<Self> {
|
||||||
let spec = read_to_string(path)
|
let spec = read_to_string(path)
|
||||||
.inspect_err(|error| {
|
.inspect_err(|err| error!(path = %path.display(), %err, "Failed to read file content"))
|
||||||
tracing::error!(
|
|
||||||
"opening JSON test metadata file '{}' error: {error}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.ok()?
|
.ok()?
|
||||||
.lines()
|
.lines()
|
||||||
.filter_map(|line| line.strip_prefix(SOLIDITY_CASE_COMMENT_MARKER))
|
.filter_map(|line| line.strip_prefix(SOLIDITY_CASE_COMMENT_MARKER))
|
||||||
@@ -222,11 +216,8 @@ impl Metadata {
|
|||||||
);
|
);
|
||||||
Some(metadata)
|
Some(metadata)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(err) => {
|
||||||
tracing::error!(
|
error!(path = %path.display(), %err, "Failed to deserialize metadata");
|
||||||
"parsing Solidity test metadata file '{}' error: '{error}' from data: {spec}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,7 +257,7 @@ define_wrapper_type!(
|
|||||||
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||||
)]
|
)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ContractInstance(String);
|
pub struct ContractInstance(String) impl Display;
|
||||||
);
|
);
|
||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
@@ -277,7 +268,7 @@ define_wrapper_type!(
|
|||||||
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||||
)]
|
)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ContractIdent(String);
|
pub struct ContractIdent(String) impl Display;
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Represents an identifier used for contracts.
|
/// Represents an identifier used for contracts.
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ mod tests {
|
|||||||
|
|
||||||
for (actual, expected) in strings {
|
for (actual, expected) in strings {
|
||||||
let parsed = ParsedMode::from_str(actual)
|
let parsed = ParsedMode::from_str(actual)
|
||||||
.expect(format!("Failed to parse mode string '{actual}'").as_str());
|
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected,
|
expected,
|
||||||
parsed.to_string(),
|
parsed.to_string(),
|
||||||
@@ -249,7 +249,7 @@ mod tests {
|
|||||||
|
|
||||||
for (actual, expected) in strings {
|
for (actual, expected) in strings {
|
||||||
let parsed = ParsedMode::from_str(actual)
|
let parsed = ParsedMode::from_str(actual)
|
||||||
.expect(format!("Failed to parse mode string '{actual}'").as_str());
|
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
|
||||||
let expected_set: HashSet<_> = expected.into_iter().map(|s| s.to_owned()).collect();
|
let expected_set: HashSet<_> = expected.into_iter().map(|s| s.to_owned()).collect();
|
||||||
let actual_set: HashSet<_> = parsed.to_modes().map(|m| m.to_string()).collect();
|
let actual_set: HashSet<_> = parsed.to_modes().map(|m| m.to_string()).collect();
|
||||||
|
|
||||||
|
|||||||
@@ -11,3 +11,6 @@ rust-version.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
alloy = { workspace = true }
|
alloy = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -29,3 +29,6 @@ sp-runtime = { workspace = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
temp-dir = { workspace = true }
|
temp-dir = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
+84
-79
@@ -33,9 +33,12 @@ use alloy::{
|
|||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use revive_common::EVMVersion;
|
use revive_common::EVMVersion;
|
||||||
use tracing::{Instrument, Level};
|
use tracing::{Instrument, instrument};
|
||||||
|
|
||||||
use revive_dt_common::{fs::clear_directory, futures::poll};
|
use revive_dt_common::{
|
||||||
|
fs::clear_directory,
|
||||||
|
futures::{PollingWaitBehavior, poll},
|
||||||
|
};
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_dt_format::traits::ResolverApi;
|
use revive_dt_format::traits::ResolverApi;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
@@ -52,6 +55,7 @@ static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
|||||||
///
|
///
|
||||||
/// Prunes the child process and the base directory on drop.
|
/// Prunes the child process and the base directory on drop.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub struct GethNode {
|
pub struct GethNode {
|
||||||
connection_string: String,
|
connection_string: String,
|
||||||
base_directory: PathBuf,
|
base_directory: PathBuf,
|
||||||
@@ -61,8 +65,9 @@ pub struct GethNode {
|
|||||||
id: u32,
|
id: u32,
|
||||||
handle: Option<Child>,
|
handle: Option<Child>,
|
||||||
start_timeout: u64,
|
start_timeout: u64,
|
||||||
wallet: EthereumWallet,
|
wallet: Arc<EthereumWallet>,
|
||||||
nonce_manager: CachedNonceManager,
|
nonce_manager: CachedNonceManager,
|
||||||
|
chain_id_filler: ChainIdFiller,
|
||||||
/// This vector stores [`File`] objects that we use for logging which we want to flush when the
|
/// This vector stores [`File`] objects that we use for logging which we want to flush when the
|
||||||
/// node object is dropped. We do not store them in a structured fashion at the moment (in
|
/// node object is dropped. We do not store them in a structured fashion at the moment (in
|
||||||
/// separate fields) as the logic that we need to apply to them is all the same regardless of
|
/// separate fields) as the logic that we need to apply to them is all the same regardless of
|
||||||
@@ -91,7 +96,7 @@ impl GethNode {
|
|||||||
const TRACE_POLLING_DURATION: Duration = Duration::from_secs(60);
|
const TRACE_POLLING_DURATION: Duration = Duration::from_secs(60);
|
||||||
|
|
||||||
/// Create the node directory and call `geth init` to configure the genesis.
|
/// Create the node directory and call `geth init` to configure the genesis.
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn init(&mut self, genesis: String) -> anyhow::Result<&mut Self> {
|
fn init(&mut self, genesis: String) -> anyhow::Result<&mut Self> {
|
||||||
let _ = clear_directory(&self.base_directory);
|
let _ = clear_directory(&self.base_directory);
|
||||||
let _ = clear_directory(&self.logs_directory);
|
let _ = clear_directory(&self.logs_directory);
|
||||||
@@ -141,7 +146,7 @@ impl GethNode {
|
|||||||
/// Spawn the go-ethereum node child process.
|
/// Spawn the go-ethereum node child process.
|
||||||
///
|
///
|
||||||
/// [Instance::init] must be called prior.
|
/// [Instance::init] must be called prior.
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn spawn_process(&mut self) -> anyhow::Result<&mut Self> {
|
fn spawn_process(&mut self) -> anyhow::Result<&mut Self> {
|
||||||
// This is the `OpenOptions` that we wish to use for all of the log files that we will be
|
// This is the `OpenOptions` that we wish to use for all of the log files that we will be
|
||||||
// opening in this method. We need to construct it in this way to:
|
// opening in this method. We need to construct it in this way to:
|
||||||
@@ -197,7 +202,7 @@ impl GethNode {
|
|||||||
/// Wait for the g-ethereum node child process getting ready.
|
/// Wait for the g-ethereum node child process getting ready.
|
||||||
///
|
///
|
||||||
/// [Instance::spawn_process] must be called priorly.
|
/// [Instance::spawn_process] must be called priorly.
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn wait_ready(&mut self) -> anyhow::Result<&mut Self> {
|
fn wait_ready(&mut self) -> anyhow::Result<&mut Self> {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
@@ -231,80 +236,75 @@ impl GethNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id), level = Level::TRACE)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn geth_stdout_log_file_path(&self) -> PathBuf {
|
fn geth_stdout_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory.join(Self::GETH_STDOUT_LOG_FILE_NAME)
|
self.logs_directory.join(Self::GETH_STDOUT_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id), level = Level::TRACE)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
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(
|
async fn provider(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Future<
|
) -> anyhow::Result<FillProvider<impl TxFiller<Ethereum>, impl Provider<Ethereum>, Ethereum>>
|
||||||
Output = anyhow::Result<
|
{
|
||||||
FillProvider<impl TxFiller<Ethereum>, impl Provider<Ethereum>, Ethereum>,
|
ProviderBuilder::new()
|
||||||
>,
|
.disable_recommended_fillers()
|
||||||
> + 'static {
|
.filler(FallbackGasFiller::new(
|
||||||
let connection_string = self.connection_string();
|
25_000_000,
|
||||||
let wallet = self.wallet.clone();
|
1_000_000_000,
|
||||||
|
1_000_000_000,
|
||||||
// Note: We would like all providers to make use of the same nonce manager so that we have
|
))
|
||||||
// monotonically increasing nonces that are cached. The cached nonce manager uses Arc's in
|
.filler(self.chain_id_filler.clone())
|
||||||
// its implementation and therefore it means that when we clone it then it still references
|
.filler(NonceFiller::new(self.nonce_manager.clone()))
|
||||||
// the same state.
|
.wallet(self.wallet.clone())
|
||||||
let nonce_manager = self.nonce_manager.clone();
|
.connect(&self.connection_string)
|
||||||
|
.await
|
||||||
Box::pin(async move {
|
.map_err(Into::into)
|
||||||
ProviderBuilder::new()
|
|
||||||
.disable_recommended_fillers()
|
|
||||||
.filler(FallbackGasFiller::new(
|
|
||||||
25_000_000,
|
|
||||||
1_000_000_000,
|
|
||||||
1_000_000_000,
|
|
||||||
))
|
|
||||||
.filler(ChainIdFiller::default())
|
|
||||||
.filler(NonceFiller::new(nonce_manager))
|
|
||||||
.wallet(wallet)
|
|
||||||
.connect(&connection_string)
|
|
||||||
.await
|
|
||||||
.map_err(Into::into)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EthereumNode for GethNode {
|
impl EthereumNode for GethNode {
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(
|
||||||
|
level = "info",
|
||||||
|
skip_all,
|
||||||
|
fields(geth_node_id = self.id, connection_string = self.connection_string),
|
||||||
|
err,
|
||||||
|
)]
|
||||||
async fn execute_transaction(
|
async fn execute_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: TransactionRequest,
|
transaction: TransactionRequest,
|
||||||
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
||||||
let provider = Arc::new(self.provider().await?);
|
let provider = self.provider().await?;
|
||||||
let transaction_hash = *provider.send_transaction(transaction).await?.tx_hash();
|
|
||||||
|
|
||||||
// The following is a fix for the "transaction indexing is in progress" error that we
|
let pending_transaction = provider.send_transaction(transaction).await.inspect_err(
|
||||||
// used to get. You can find more information on this in the following GH issue in geth
|
|err| tracing::error!(%err, "Encountered an error when submitting the transaction"),
|
||||||
|
)?;
|
||||||
|
let transaction_hash = *pending_transaction.tx_hash();
|
||||||
|
|
||||||
|
// The following is a fix for the "transaction indexing is in progress" error that we used
|
||||||
|
// to get. You can find more information on this in the following GH issue in geth
|
||||||
// https://github.com/ethereum/go-ethereum/issues/28877. To summarize what's going on,
|
// https://github.com/ethereum/go-ethereum/issues/28877. To summarize what's going on,
|
||||||
// before we can get the receipt of the transaction it needs to have been indexed by the
|
// before we can get the receipt of the transaction it needs to have been indexed by the
|
||||||
// node's indexer. Just because the transaction has been confirmed it doesn't mean that
|
// node's indexer. Just because the transaction has been confirmed it doesn't mean that it
|
||||||
// it has been indexed. When we call alloy's `get_receipt` it checks if the transaction
|
// has been indexed. When we call alloy's `get_receipt` it checks if the transaction was
|
||||||
// was confirmed. If it has been, then it will call `eth_getTransactionReceipt` method
|
// confirmed. If it has been, then it will call `eth_getTransactionReceipt` method which
|
||||||
// which _might_ return the above error if the tx has not yet been indexed yet. So, we
|
// _might_ return the above error if the tx has not yet been indexed yet. So, we need to
|
||||||
// need to implement a retry mechanism for the receipt to keep retrying to get it until
|
// implement a retry mechanism for the receipt to keep retrying to get it until it
|
||||||
// it eventually works, but we only do that if the error we get back is the "transaction
|
// 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.
|
// indexing is in progress" error or if the receipt is None.
|
||||||
//
|
//
|
||||||
// Getting the transaction indexed and taking a receipt can take a long time especially
|
// Getting the transaction indexed and taking a receipt can take a long time especially when
|
||||||
// when a lot of transactions are being submitted to the node. Thus, while initially we
|
// a lot of transactions are being submitted to the node. Thus, while initially we only
|
||||||
// only allowed for 60 seconds of waiting with a 1 second delay in polling, we need to
|
// allowed for 60 seconds of waiting with a 1 second delay in polling, we need to allow for
|
||||||
// allow for a larger wait time. Therefore, in here we allow for 5 minutes of waiting
|
// a larger wait time. Therefore, in here we allow for 5 minutes of waiting with exponential
|
||||||
// with exponential backoff each time we attempt to get the receipt and find that it's
|
// backoff each time we attempt to get the receipt and find that it's not available.
|
||||||
// not available.
|
let provider = Arc::new(provider);
|
||||||
poll(
|
poll(
|
||||||
Self::RECEIPT_POLLING_DURATION,
|
Self::RECEIPT_POLLING_DURATION,
|
||||||
Default::default(),
|
PollingWaitBehavior::Constant(Duration::from_millis(200)),
|
||||||
move || {
|
move || {
|
||||||
let provider = provider.clone();
|
let provider = provider.clone();
|
||||||
async move {
|
async move {
|
||||||
@@ -329,7 +329,7 @@ impl EthereumNode for GethNode {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn trace_transaction(
|
async fn trace_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: &TransactionReceipt,
|
transaction: &TransactionReceipt,
|
||||||
@@ -338,7 +338,7 @@ impl EthereumNode for GethNode {
|
|||||||
let provider = Arc::new(self.provider().await?);
|
let provider = Arc::new(self.provider().await?);
|
||||||
poll(
|
poll(
|
||||||
Self::TRACE_POLLING_DURATION,
|
Self::TRACE_POLLING_DURATION,
|
||||||
Default::default(),
|
PollingWaitBehavior::Constant(Duration::from_millis(200)),
|
||||||
move || {
|
move || {
|
||||||
let provider = provider.clone();
|
let provider = provider.clone();
|
||||||
let trace_options = trace_options.clone();
|
let trace_options = trace_options.clone();
|
||||||
@@ -362,7 +362,7 @@ impl EthereumNode for GethNode {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
||||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||||
diff_mode: Some(true),
|
diff_mode: Some(true),
|
||||||
@@ -379,7 +379,7 @@ impl EthereumNode for GethNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn balance_of(&self, address: Address) -> anyhow::Result<U256> {
|
async fn balance_of(&self, address: Address) -> anyhow::Result<U256> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -388,7 +388,7 @@ impl EthereumNode for GethNode {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn latest_state_proof(
|
async fn latest_state_proof(
|
||||||
&self,
|
&self,
|
||||||
address: Address,
|
address: Address,
|
||||||
@@ -404,7 +404,7 @@ impl EthereumNode for GethNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResolverApi for GethNode {
|
impl ResolverApi for GethNode {
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
async fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -413,7 +413,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -423,7 +423,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|receipt| receipt.effective_gas_price)
|
.map(|receipt| receipt.effective_gas_price)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -433,7 +433,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| block.header.gas_limit as _)
|
.map(|block| block.header.gas_limit as _)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -443,7 +443,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| block.header.beneficiary)
|
.map(|block| block.header.beneficiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -453,7 +453,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -468,7 +468,7 @@ impl ResolverApi for GethNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -478,7 +478,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| block.header.hash)
|
.map(|block| block.header.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -488,7 +488,7 @@ impl ResolverApi for GethNode {
|
|||||||
.map(|block| block.header.timestamp)
|
.map(|block| block.header.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -522,20 +522,26 @@ impl Node for GethNode {
|
|||||||
id,
|
id,
|
||||||
handle: None,
|
handle: None,
|
||||||
start_timeout: config.geth_start_timeout,
|
start_timeout: config.geth_start_timeout,
|
||||||
wallet,
|
wallet: Arc::new(wallet),
|
||||||
|
chain_id_filler: Default::default(),
|
||||||
|
nonce_manager: Default::default(),
|
||||||
// We know that we only need to be storing 2 files so we can specify that when creating
|
// We know that we only need to be storing 2 files so we can specify that when creating
|
||||||
// the vector. It's the stdout and stderr of the geth node.
|
// the vector. It's the stdout and stderr of the geth node.
|
||||||
logs_file_to_flush: Vec::with_capacity(2),
|
logs_file_to_flush: Vec::with_capacity(2),
|
||||||
nonce_manager: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
|
fn id(&self) -> usize {
|
||||||
|
self.id as _
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn connection_string(&self) -> String {
|
fn connection_string(&self) -> String {
|
||||||
self.connection_string.clone()
|
self.connection_string.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn shutdown(&mut self) -> anyhow::Result<()> {
|
fn shutdown(&mut self) -> anyhow::Result<()> {
|
||||||
// Terminate the processes in a graceful manner to allow for the output to be flushed.
|
// Terminate the processes in a graceful manner to allow for the output to be flushed.
|
||||||
if let Some(mut child) = self.handle.take() {
|
if let Some(mut child) = self.handle.take() {
|
||||||
@@ -557,13 +563,13 @@ impl Node for GethNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn spawn(&mut self, genesis: String) -> anyhow::Result<()> {
|
fn spawn(&mut self, genesis: String) -> anyhow::Result<()> {
|
||||||
self.init(genesis)?.spawn_process()?;
|
self.init(genesis)?.spawn_process()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id), err)]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn version(&self) -> anyhow::Result<String> {
|
fn version(&self) -> anyhow::Result<String> {
|
||||||
let output = Command::new(&self.geth)
|
let output = Command::new(&self.geth)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
@@ -576,8 +582,7 @@ impl Node for GethNode {
|
|||||||
Ok(String::from_utf8_lossy(&output).into())
|
Ok(String::from_utf8_lossy(&output).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
fn matches_target(targets: Option<&[String]>) -> bool {
|
||||||
fn matches_target(&self, targets: Option<&[String]>) -> bool {
|
|
||||||
match targets {
|
match targets {
|
||||||
None => true,
|
None => true,
|
||||||
Some(targets) => targets.iter().any(|str| str.as_str() == "evm"),
|
Some(targets) => targets.iter().any(|str| str.as_str() == "evm"),
|
||||||
@@ -590,7 +595,7 @@ impl Node for GethNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for GethNode {
|
impl Drop for GethNode {
|
||||||
#[tracing::instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.shutdown().expect("Failed to shutdown")
|
self.shutdown().expect("Failed to shutdown")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ use std::{
|
|||||||
io::{BufRead, Write},
|
io::{BufRead, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{Child, Command, Stdio},
|
process::{Child, Command, Stdio},
|
||||||
sync::atomic::{AtomicU32, Ordering},
|
sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,7 +42,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_json::{Value as JsonValue, json};
|
use serde_json::{Value as JsonValue, json};
|
||||||
use sp_core::crypto::Ss58Codec;
|
use sp_core::crypto::Ss58Codec;
|
||||||
use sp_runtime::AccountId32;
|
use sp_runtime::AccountId32;
|
||||||
use tracing::Level;
|
|
||||||
|
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
@@ -54,12 +56,13 @@ pub struct KitchensinkNode {
|
|||||||
substrate_binary: PathBuf,
|
substrate_binary: PathBuf,
|
||||||
eth_proxy_binary: PathBuf,
|
eth_proxy_binary: PathBuf,
|
||||||
rpc_url: String,
|
rpc_url: String,
|
||||||
wallet: EthereumWallet,
|
|
||||||
base_directory: PathBuf,
|
base_directory: PathBuf,
|
||||||
logs_directory: PathBuf,
|
logs_directory: PathBuf,
|
||||||
process_substrate: Option<Child>,
|
process_substrate: Option<Child>,
|
||||||
process_proxy: Option<Child>,
|
process_proxy: Option<Child>,
|
||||||
|
wallet: Arc<EthereumWallet>,
|
||||||
nonce_manager: CachedNonceManager,
|
nonce_manager: CachedNonceManager,
|
||||||
|
chain_id_filler: ChainIdFiller,
|
||||||
/// This vector stores [`File`] objects that we use for logging which we want to flush when the
|
/// This vector stores [`File`] objects that we use for logging which we want to flush when the
|
||||||
/// node object is dropped. We do not store them in a structured fashion at the moment (in
|
/// node object is dropped. We do not store them in a structured fashion at the moment (in
|
||||||
/// separate fields) as the logic that we need to apply to them is all the same regardless of
|
/// separate fields) as the logic that we need to apply to them is all the same regardless of
|
||||||
@@ -87,7 +90,6 @@ impl KitchensinkNode {
|
|||||||
const PROXY_STDOUT_LOG_FILE_NAME: &str = "proxy_stdout.log";
|
const PROXY_STDOUT_LOG_FILE_NAME: &str = "proxy_stdout.log";
|
||||||
const PROXY_STDERR_LOG_FILE_NAME: &str = "proxy_stderr.log";
|
const PROXY_STDERR_LOG_FILE_NAME: &str = "proxy_stderr.log";
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
fn init(&mut self, genesis: &str) -> anyhow::Result<&mut Self> {
|
fn init(&mut self, genesis: &str) -> anyhow::Result<&mut Self> {
|
||||||
let _ = clear_directory(&self.base_directory);
|
let _ = clear_directory(&self.base_directory);
|
||||||
let _ = clear_directory(&self.logs_directory);
|
let _ = clear_directory(&self.logs_directory);
|
||||||
@@ -160,7 +162,6 @@ impl KitchensinkNode {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
fn spawn_process(&mut self) -> anyhow::Result<()> {
|
fn spawn_process(&mut self) -> anyhow::Result<()> {
|
||||||
let substrate_rpc_port = Self::BASE_SUBSTRATE_RPC_PORT + self.id as u16;
|
let substrate_rpc_port = Self::BASE_SUBSTRATE_RPC_PORT + self.id as u16;
|
||||||
let proxy_rpc_port = Self::BASE_PROXY_RPC_PORT + self.id as u16;
|
let proxy_rpc_port = Self::BASE_PROXY_RPC_PORT + self.id as u16;
|
||||||
@@ -214,10 +215,6 @@ impl KitchensinkNode {
|
|||||||
Self::SUBSTRATE_READY_MARKER,
|
Self::SUBSTRATE_READY_MARKER,
|
||||||
Duration::from_secs(60),
|
Duration::from_secs(60),
|
||||||
) {
|
) {
|
||||||
tracing::error!(
|
|
||||||
?error,
|
|
||||||
"Failed to start substrate, shutting down gracefully"
|
|
||||||
);
|
|
||||||
self.shutdown()?;
|
self.shutdown()?;
|
||||||
return Err(error);
|
return Err(error);
|
||||||
};
|
};
|
||||||
@@ -243,7 +240,6 @@ impl KitchensinkNode {
|
|||||||
Self::ETH_PROXY_READY_MARKER,
|
Self::ETH_PROXY_READY_MARKER,
|
||||||
Duration::from_secs(60),
|
Duration::from_secs(60),
|
||||||
) {
|
) {
|
||||||
tracing::error!(?error, "Failed to start proxy, shutting down gracefully");
|
|
||||||
self.shutdown()?;
|
self.shutdown()?;
|
||||||
return Err(error);
|
return Err(error);
|
||||||
};
|
};
|
||||||
@@ -258,7 +254,6 @@ impl KitchensinkNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
fn extract_balance_from_genesis_file(
|
fn extract_balance_from_genesis_file(
|
||||||
&self,
|
&self,
|
||||||
genesis: &Genesis,
|
genesis: &Genesis,
|
||||||
@@ -307,7 +302,6 @@ impl KitchensinkNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
pub fn eth_rpc_version(&self) -> anyhow::Result<String> {
|
pub fn eth_rpc_version(&self) -> anyhow::Result<String> {
|
||||||
let output = Command::new(&self.eth_proxy_binary)
|
let output = Command::new(&self.eth_proxy_binary)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
@@ -320,74 +314,55 @@ impl KitchensinkNode {
|
|||||||
Ok(String::from_utf8_lossy(&output).trim().to_string())
|
Ok(String::from_utf8_lossy(&output).trim().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), level = Level::TRACE)]
|
|
||||||
fn kitchensink_stdout_log_file_path(&self) -> PathBuf {
|
fn kitchensink_stdout_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory
|
self.logs_directory
|
||||||
.join(Self::KITCHENSINK_STDOUT_LOG_FILE_NAME)
|
.join(Self::KITCHENSINK_STDOUT_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), level = Level::TRACE)]
|
|
||||||
fn kitchensink_stderr_log_file_path(&self) -> PathBuf {
|
fn kitchensink_stderr_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory
|
self.logs_directory
|
||||||
.join(Self::KITCHENSINK_STDERR_LOG_FILE_NAME)
|
.join(Self::KITCHENSINK_STDERR_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), level = Level::TRACE)]
|
|
||||||
fn proxy_stdout_log_file_path(&self) -> PathBuf {
|
fn proxy_stdout_log_file_path(&self) -> PathBuf {
|
||||||
self.logs_directory.join(Self::PROXY_STDOUT_LOG_FILE_NAME)
|
self.logs_directory.join(Self::PROXY_STDOUT_LOG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), level = Level::TRACE)]
|
|
||||||
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(
|
async fn provider(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Future<
|
) -> anyhow::Result<
|
||||||
Output = anyhow::Result<
|
FillProvider<
|
||||||
FillProvider<
|
impl TxFiller<KitchenSinkNetwork>,
|
||||||
impl TxFiller<KitchenSinkNetwork>,
|
impl Provider<KitchenSinkNetwork>,
|
||||||
impl Provider<KitchenSinkNetwork>,
|
KitchenSinkNetwork,
|
||||||
KitchenSinkNetwork,
|
|
||||||
>,
|
|
||||||
>,
|
>,
|
||||||
> + 'static {
|
> {
|
||||||
let connection_string = self.connection_string();
|
ProviderBuilder::new()
|
||||||
let wallet = self.wallet.clone();
|
.disable_recommended_fillers()
|
||||||
|
.network::<KitchenSinkNetwork>()
|
||||||
// Note: We would like all providers to make use of the same nonce manager so that we have
|
.filler(FallbackGasFiller::new(
|
||||||
// monotonically increasing nonces that are cached. The cached nonce manager uses Arc's in
|
25_000_000,
|
||||||
// its implementation and therefore it means that when we clone it then it still references
|
1_000_000_000,
|
||||||
// the same state.
|
1_000_000_000,
|
||||||
let nonce_manager = self.nonce_manager.clone();
|
))
|
||||||
|
.filler(self.chain_id_filler.clone())
|
||||||
Box::pin(async move {
|
.filler(NonceFiller::new(self.nonce_manager.clone()))
|
||||||
ProviderBuilder::new()
|
.wallet(self.wallet.clone())
|
||||||
.disable_recommended_fillers()
|
.connect(&self.rpc_url)
|
||||||
.network::<KitchenSinkNetwork>()
|
.await
|
||||||
.filler(FallbackGasFiller::new(
|
.map_err(Into::into)
|
||||||
25_000_000,
|
|
||||||
1_000_000_000,
|
|
||||||
1_000_000_000,
|
|
||||||
))
|
|
||||||
.filler(ChainIdFiller::default())
|
|
||||||
.filler(NonceFiller::new(nonce_manager))
|
|
||||||
.wallet(wallet)
|
|
||||||
.connect(&connection_string)
|
|
||||||
.await
|
|
||||||
.map_err(Into::into)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EthereumNode for KitchensinkNode {
|
impl EthereumNode for KitchensinkNode {
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn execute_transaction(
|
async fn execute_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: alloy::rpc::types::TransactionRequest,
|
transaction: alloy::rpc::types::TransactionRequest,
|
||||||
) -> anyhow::Result<TransactionReceipt> {
|
) -> anyhow::Result<TransactionReceipt> {
|
||||||
tracing::debug!(?transaction, "Submitting transaction");
|
|
||||||
let receipt = self
|
let receipt = self
|
||||||
.provider()
|
.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -395,11 +370,9 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
.await?
|
.await?
|
||||||
.get_receipt()
|
.get_receipt()
|
||||||
.await?;
|
.await?;
|
||||||
tracing::info!(?receipt, "Submitted tx to kitchensink");
|
|
||||||
Ok(receipt)
|
Ok(receipt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn trace_transaction(
|
async fn trace_transaction(
|
||||||
&self,
|
&self,
|
||||||
transaction: &TransactionReceipt,
|
transaction: &TransactionReceipt,
|
||||||
@@ -413,7 +386,6 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
async fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
||||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||||
diff_mode: Some(true),
|
diff_mode: Some(true),
|
||||||
@@ -430,7 +402,6 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn balance_of(&self, address: Address) -> anyhow::Result<U256> {
|
async fn balance_of(&self, address: Address) -> anyhow::Result<U256> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -439,7 +410,6 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn latest_state_proof(
|
async fn latest_state_proof(
|
||||||
&self,
|
&self,
|
||||||
address: Address,
|
address: Address,
|
||||||
@@ -455,7 +425,6 @@ impl EthereumNode for KitchensinkNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResolverApi for KitchensinkNode {
|
impl ResolverApi for KitchensinkNode {
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
async fn chain_id(&self) -> anyhow::Result<alloy::primitives::ChainId> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -464,7 +433,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result<u128> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -474,7 +442,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|receipt| receipt.effective_gas_price)
|
.map(|receipt| receipt.effective_gas_price)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -484,7 +451,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.gas_limit as _)
|
.map(|block| block.header.gas_limit as _)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -494,7 +460,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.beneficiary)
|
.map(|block| block.header.beneficiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -504,7 +469,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
.map(|block| U256::from_be_bytes(block.header.mix_hash.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result<u64> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -519,7 +483,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
async fn block_hash(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockHash> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -529,7 +492,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.hash)
|
.map(|block| block.header.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
async fn block_timestamp(&self, number: BlockNumberOrTag) -> anyhow::Result<BlockTimestamp> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -539,7 +501,6 @@ impl ResolverApi for KitchensinkNode {
|
|||||||
.map(|block| block.header.timestamp)
|
.map(|block| block.header.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
async fn last_block_number(&self) -> anyhow::Result<BlockNumber> {
|
||||||
self.provider()
|
self.provider()
|
||||||
.await?
|
.await?
|
||||||
@@ -570,11 +531,12 @@ impl Node for KitchensinkNode {
|
|||||||
substrate_binary: config.kitchensink.clone(),
|
substrate_binary: config.kitchensink.clone(),
|
||||||
eth_proxy_binary: config.eth_proxy.clone(),
|
eth_proxy_binary: config.eth_proxy.clone(),
|
||||||
rpc_url: String::new(),
|
rpc_url: String::new(),
|
||||||
wallet,
|
|
||||||
base_directory,
|
base_directory,
|
||||||
logs_directory,
|
logs_directory,
|
||||||
process_substrate: None,
|
process_substrate: None,
|
||||||
process_proxy: None,
|
process_proxy: None,
|
||||||
|
wallet: Arc::new(wallet),
|
||||||
|
chain_id_filler: Default::default(),
|
||||||
nonce_manager: Default::default(),
|
nonce_manager: Default::default(),
|
||||||
// We know that we only need to be storing 4 files so we can specify that when creating
|
// We know that we only need to be storing 4 files so we can specify that when creating
|
||||||
// the vector. It's the stdout and stderr of the substrate-node and the eth-rpc.
|
// the vector. It's the stdout and stderr of the substrate-node and the eth-rpc.
|
||||||
@@ -582,12 +544,14 @@ impl Node for KitchensinkNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
fn id(&self) -> usize {
|
||||||
|
self.id as _
|
||||||
|
}
|
||||||
|
|
||||||
fn connection_string(&self) -> String {
|
fn connection_string(&self) -> String {
|
||||||
self.rpc_url.clone()
|
self.rpc_url.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
fn shutdown(&mut self) -> anyhow::Result<()> {
|
fn shutdown(&mut self) -> anyhow::Result<()> {
|
||||||
// Terminate the processes in a graceful manner to allow for the output to be flushed.
|
// Terminate the processes in a graceful manner to allow for the output to be flushed.
|
||||||
if let Some(mut child) = self.process_proxy.take() {
|
if let Some(mut child) = self.process_proxy.take() {
|
||||||
@@ -614,12 +578,10 @@ impl Node for KitchensinkNode {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
fn spawn(&mut self, genesis: String) -> anyhow::Result<()> {
|
fn spawn(&mut self, genesis: String) -> anyhow::Result<()> {
|
||||||
self.init(&genesis)?.spawn_process()
|
self.init(&genesis)?.spawn_process()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id), err)]
|
|
||||||
fn version(&self) -> anyhow::Result<String> {
|
fn version(&self) -> anyhow::Result<String> {
|
||||||
let output = Command::new(&self.substrate_binary)
|
let output = Command::new(&self.substrate_binary)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
@@ -632,8 +594,7 @@ impl Node for KitchensinkNode {
|
|||||||
Ok(String::from_utf8_lossy(&output).into())
|
Ok(String::from_utf8_lossy(&output).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
fn matches_target(targets: Option<&[String]>) -> bool {
|
||||||
fn matches_target(&self, targets: Option<&[String]>) -> bool {
|
|
||||||
match targets {
|
match targets {
|
||||||
None => true,
|
None => true,
|
||||||
Some(targets) => targets.iter().any(|str| str.as_str() == "pvm"),
|
Some(targets) => targets.iter().any(|str| str.as_str() == "pvm"),
|
||||||
@@ -646,7 +607,6 @@ impl Node for KitchensinkNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for KitchensinkNode {
|
impl Drop for KitchensinkNode {
|
||||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.shutdown().expect("Failed to shutdown")
|
self.shutdown().expect("Failed to shutdown")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ pub trait Node: EthereumNode {
|
|||||||
/// Create a new uninitialized instance.
|
/// Create a new uninitialized instance.
|
||||||
fn new(config: &Arguments) -> Self;
|
fn new(config: &Arguments) -> Self;
|
||||||
|
|
||||||
|
/// Returns the identifier of the node.
|
||||||
|
fn id(&self) -> usize;
|
||||||
|
|
||||||
/// Spawns a node configured according to the genesis json.
|
/// Spawns a node configured according to the genesis json.
|
||||||
///
|
///
|
||||||
/// Blocking until it's ready to accept transactions.
|
/// Blocking until it's ready to accept transactions.
|
||||||
@@ -36,7 +39,7 @@ pub trait Node: EthereumNode {
|
|||||||
|
|
||||||
/// Given a list of targets from the metadata file, this function determines if the metadata
|
/// Given a list of targets from the metadata file, this function determines if the metadata
|
||||||
/// file can be ran on this node or not.
|
/// file can be ran on this node or not.
|
||||||
fn matches_target(&self, targets: Option<&[String]>) -> bool;
|
fn matches_target(targets: Option<&[String]>) -> bool;
|
||||||
|
|
||||||
/// Returns the EVM version of the node.
|
/// Returns the EVM version of the node.
|
||||||
fn evm_version() -> EVMVersion;
|
fn evm_version() -> EVMVersion;
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ where
|
|||||||
|
|
||||||
fn spawn_node<T: Node + Send>(args: &Arguments, genesis: String) -> anyhow::Result<T> {
|
fn spawn_node<T: Node + Send>(args: &Arguments, genesis: String) -> anyhow::Result<T> {
|
||||||
let mut node = T::new(args);
|
let mut node = T::new(args);
|
||||||
tracing::info!("starting node: {}", node.connection_string());
|
|
||||||
node.spawn(genesis)?;
|
node.spawn(genesis)?;
|
||||||
Ok(node)
|
Ok(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ revive-dt-format = { workspace = true }
|
|||||||
revive-dt-compiler = { workspace = true }
|
revive-dt-compiler = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
tracing = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -185,8 +185,6 @@ impl Report {
|
|||||||
let file = File::create(&path).context(path.display().to_string())?;
|
let file = File::create(&path).context(path.display().to_string())?;
|
||||||
serde_json::to_writer_pretty(file, &self)?;
|
serde_json::to_writer_pretty(file, &self)?;
|
||||||
|
|
||||||
tracing::info!("report written to: {}", path.display());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,3 +19,6 @@ reqwest = { workspace = true }
|
|||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
sha2 = { workspace = true }
|
sha2 = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -39,10 +39,7 @@ pub(crate) async fn get_or_download(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn download_to_file(path: &Path, downloader: &SolcDownloader) -> anyhow::Result<()> {
|
async fn download_to_file(path: &Path, downloader: &SolcDownloader) -> anyhow::Result<()> {
|
||||||
tracing::info!("caching file: {}", path.display());
|
|
||||||
|
|
||||||
let Ok(file) = File::create_new(path) else {
|
let Ok(file) = File::create_new(path) else {
|
||||||
tracing::debug!("cache file already exists: {}", path.display());
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ impl SolcDownloader {
|
|||||||
/// Errors out if the download fails or the digest of the downloaded file
|
/// Errors out if the download fails or the digest of the downloaded file
|
||||||
/// mismatches the expected digest from the release [List].
|
/// mismatches the expected digest from the release [List].
|
||||||
pub async fn download(&self) -> anyhow::Result<Vec<u8>> {
|
pub async fn download(&self) -> anyhow::Result<Vec<u8>> {
|
||||||
tracing::info!("downloading solc: {self:?}");
|
|
||||||
let builds = List::download(self.list).await?.builds;
|
let builds = List::download(self.list).await?.builds;
|
||||||
let build = builds
|
let build = builds
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
Reference in New Issue
Block a user