mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-22 10:17:56 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a28a90168 | |||
| ee55eea6b2 | |||
| 1026426616 | |||
| 2827f32478 | |||
| 3c86cbb7ef | |||
| fde07b7c0d | |||
| ebc24a588b | |||
| 21e25f09e6 |
Generated
+215
-20
@@ -782,6 +782,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
@@ -2328,7 +2337,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2927,6 +2936,22 @@ dependencies = [
|
||||
"sp-crypto-hashing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "frame-decode"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd"
|
||||
dependencies = [
|
||||
"frame-metadata",
|
||||
"parity-scale-codec",
|
||||
"scale-decode",
|
||||
"scale-encode",
|
||||
"scale-info",
|
||||
"scale-type-resolver",
|
||||
"sp-crypto-hashing",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "frame-metadata"
|
||||
version = "23.0.0"
|
||||
@@ -5564,6 +5589,7 @@ dependencies = [
|
||||
"clap",
|
||||
"moka",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"schemars 1.0.4",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
@@ -5611,6 +5637,7 @@ name = "revive-dt-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"alloy",
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
"bson",
|
||||
"cacache",
|
||||
@@ -5641,7 +5668,6 @@ dependencies = [
|
||||
"alloy",
|
||||
"anyhow",
|
||||
"futures",
|
||||
"regex",
|
||||
"revive-common",
|
||||
"revive-dt-common",
|
||||
"schemars 1.0.4",
|
||||
@@ -5670,6 +5696,7 @@ dependencies = [
|
||||
"serde_yaml_ng",
|
||||
"sp-core",
|
||||
"sp-runtime",
|
||||
"subxt 0.44.0",
|
||||
"temp-dir",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
@@ -5942,7 +5969,7 @@ dependencies = [
|
||||
"security-framework 3.3.0",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs 0.26.11",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7339,11 +7366,48 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sp-crypto-hashing",
|
||||
"subxt-core",
|
||||
"subxt-lightclient",
|
||||
"subxt-macro",
|
||||
"subxt-metadata",
|
||||
"subxt-rpcs",
|
||||
"subxt-core 0.43.0",
|
||||
"subxt-lightclient 0.43.0",
|
||||
"subxt-macro 0.43.0",
|
||||
"subxt-metadata 0.43.0",
|
||||
"subxt-rpcs 0.43.0",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
"wasm-bindgen-futures",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddbf938ac1d86a361a84709a71cdbae5d87f370770b563651d1ec052eed9d0b4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"derive-where",
|
||||
"either",
|
||||
"frame-metadata",
|
||||
"futures",
|
||||
"hex",
|
||||
"jsonrpsee",
|
||||
"parity-scale-codec",
|
||||
"primitive-types 0.13.1",
|
||||
"scale-bits",
|
||||
"scale-decode",
|
||||
"scale-encode",
|
||||
"scale-info",
|
||||
"scale-value",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sp-crypto-hashing",
|
||||
"subxt-core 0.44.0",
|
||||
"subxt-lightclient 0.44.0",
|
||||
"subxt-macro 0.44.0",
|
||||
"subxt-metadata 0.44.0",
|
||||
"subxt-rpcs 0.44.0",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -7365,7 +7429,24 @@ dependencies = [
|
||||
"quote",
|
||||
"scale-info",
|
||||
"scale-typegen",
|
||||
"subxt-metadata",
|
||||
"subxt-metadata 0.43.0",
|
||||
"syn 2.0.101",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-codegen"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c250ad8cd102d40ae47977b03295a2ff791375f30ddc7474d399fb56efb793b"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"parity-scale-codec",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scale-info",
|
||||
"scale-typegen",
|
||||
"subxt-metadata 0.44.0",
|
||||
"syn 2.0.101",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
@@ -7379,7 +7460,7 @@ dependencies = [
|
||||
"base58",
|
||||
"blake2",
|
||||
"derive-where",
|
||||
"frame-decode",
|
||||
"frame-decode 0.8.3",
|
||||
"frame-metadata",
|
||||
"hashbrown 0.14.5",
|
||||
"hex",
|
||||
@@ -7395,7 +7476,37 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sp-crypto-hashing",
|
||||
"subxt-metadata",
|
||||
"subxt-metadata 0.43.0",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-core"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5705c5b420294524e41349bf23c6b11aa474ce731de7317f4153390e1927f702"
|
||||
dependencies = [
|
||||
"base58",
|
||||
"blake2",
|
||||
"derive-where",
|
||||
"frame-decode 0.9.0",
|
||||
"frame-metadata",
|
||||
"hashbrown 0.14.5",
|
||||
"hex",
|
||||
"impl-serde",
|
||||
"keccak-hash",
|
||||
"parity-scale-codec",
|
||||
"primitive-types 0.13.1",
|
||||
"scale-bits",
|
||||
"scale-decode",
|
||||
"scale-encode",
|
||||
"scale-info",
|
||||
"scale-value",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sp-crypto-hashing",
|
||||
"subxt-metadata 0.44.0",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
]
|
||||
@@ -7417,6 +7528,23 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-lightclient"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64e02732a6c9ae46bc282c1a741b3d3e494021b3e87e7e92cfb3620116d92911"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-util",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smoldot-light",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-macro"
|
||||
version = "0.43.0"
|
||||
@@ -7428,9 +7556,26 @@ dependencies = [
|
||||
"proc-macro-error2",
|
||||
"quote",
|
||||
"scale-typegen",
|
||||
"subxt-codegen",
|
||||
"subxt-metadata",
|
||||
"subxt-utils-fetchmetadata",
|
||||
"subxt-codegen 0.43.0",
|
||||
"subxt-metadata 0.43.0",
|
||||
"subxt-utils-fetchmetadata 0.43.0",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-macro"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501bf358698f5ab02a6199a1fcd3f1b482e2f5b6eb5d185411e6a74a175ec8e8"
|
||||
dependencies = [
|
||||
"darling 0.20.11",
|
||||
"parity-scale-codec",
|
||||
"proc-macro-error2",
|
||||
"quote",
|
||||
"scale-typegen",
|
||||
"subxt-codegen 0.44.0",
|
||||
"subxt-metadata 0.44.0",
|
||||
"subxt-utils-fetchmetadata 0.44.0",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
@@ -7440,7 +7585,22 @@ version = "0.43.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c134068711c0c46906abc0e6e4911204420331530738e18ca903a5469364d9f"
|
||||
dependencies = [
|
||||
"frame-decode",
|
||||
"frame-decode 0.8.3",
|
||||
"frame-metadata",
|
||||
"hashbrown 0.14.5",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"sp-crypto-hashing",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-metadata"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01fb7c0bfafad78dda7084c6a2444444744af3bbf7b2502399198b9b4c20eddf"
|
||||
dependencies = [
|
||||
"frame-decode 0.9.0",
|
||||
"frame-metadata",
|
||||
"hashbrown 0.14.5",
|
||||
"parity-scale-codec",
|
||||
@@ -7465,8 +7625,32 @@ dependencies = [
|
||||
"primitive-types 0.13.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"subxt-core",
|
||||
"subxt-lightclient",
|
||||
"subxt-core 0.43.0",
|
||||
"subxt-lightclient 0.43.0",
|
||||
"thiserror 2.0.12",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-rpcs"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab68a9c20ecedb0cb7d62d64f884e6add91bb70485783bf40aa8eac5c389c6e0"
|
||||
dependencies = [
|
||||
"derive-where",
|
||||
"frame-metadata",
|
||||
"futures",
|
||||
"hex",
|
||||
"impl-serde",
|
||||
"jsonrpsee",
|
||||
"parity-scale-codec",
|
||||
"primitive-types 0.13.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"subxt-core 0.44.0",
|
||||
"subxt-lightclient 0.44.0",
|
||||
"thiserror 2.0.12",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
@@ -7496,7 +7680,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sp-crypto-hashing",
|
||||
"subxt-core",
|
||||
"subxt-core 0.43.0",
|
||||
"thiserror 2.0.12",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -7512,6 +7696,17 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-utils-fetchmetadata"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e450f6812a653c5a3e63a079aa3b60a3f4c362722753c3222286eaa1800f9002"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"parity-scale-codec",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -9305,7 +9500,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sp-core",
|
||||
"subxt",
|
||||
"subxt 0.43.0",
|
||||
"subxt-signer",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
@@ -9365,7 +9560,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"subxt",
|
||||
"subxt 0.43.0",
|
||||
"subxt-signer",
|
||||
"tokio",
|
||||
"zombienet-configuration",
|
||||
|
||||
+3
-1
@@ -22,6 +22,7 @@ revive-dt-node-pool = { version = "0.1.0", path = "crates/node-pool" }
|
||||
revive-dt-report = { version = "0.1.0", path = "crates/report" }
|
||||
revive-dt-solc-binaries = { version = "0.1.0", path = "crates/solc-binaries" }
|
||||
|
||||
ansi_term = "0.12.1"
|
||||
anyhow = "1.0"
|
||||
bson = { version = "2.15.0" }
|
||||
cacache = { version = "13.1.0" }
|
||||
@@ -49,6 +50,7 @@ sha2 = { version = "0.10.9" }
|
||||
sp-core = "36.1.0"
|
||||
sp-runtime = "41.1.0"
|
||||
strum = { version = "0.27.2", features = ["derive"] }
|
||||
subxt = { version = "0.44.0" }
|
||||
temp-dir = { version = "0.1.16" }
|
||||
tempfile = "3.3"
|
||||
thiserror = "2"
|
||||
@@ -73,7 +75,7 @@ revive-solc-json-interface = { git = "https://github.com/paritytech/revive", rev
|
||||
revive-common = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
|
||||
revive-differential = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
|
||||
|
||||
zombienet-sdk = { git = "https://github.com/paritytech/zombienet-sdk.git", rev ="891f6554354ce466abd496366dbf8b4f82141241" }
|
||||
zombienet-sdk = { git = "https://github.com/paritytech/zombienet-sdk.git", rev = "891f6554354ce466abd496366dbf8b4f82141241" }
|
||||
|
||||
[workspace.dependencies.alloy]
|
||||
version = "1.0.37"
|
||||
|
||||
Binary file not shown.
@@ -14,6 +14,7 @@ anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
moka = { workspace = true, features = ["sync"] }
|
||||
once_cell = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
use crate::iterators::EitherIter;
|
||||
use crate::types::VersionOrRequirement;
|
||||
use anyhow::{Context as _, bail};
|
||||
use regex::Regex;
|
||||
use schemars::JsonSchema;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
@@ -33,6 +38,19 @@ impl Display for Mode {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Mode {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parsed_mode = ParsedMode::from_str(s)?;
|
||||
let mut iter = parsed_mode.to_modes();
|
||||
let (Some(mode), None) = (iter.next(), iter.next()) else {
|
||||
bail!("Failed to parse the mode")
|
||||
};
|
||||
Ok(mode)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
/// Return all of the available mode combinations.
|
||||
pub fn all() -> impl Iterator<Item = &'static Mode> {
|
||||
@@ -171,3 +189,250 @@ impl ModeOptimizerSetting {
|
||||
!matches!(self, ModeOptimizerSetting::M0)
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents a mode that has been parsed from test metadata.
|
||||
///
|
||||
/// Mode strings can take the following form (in pseudo-regex):
|
||||
///
|
||||
/// ```text
|
||||
/// [YEILV][+-]? (M[0123sz])? <semver>?
|
||||
/// ```
|
||||
///
|
||||
/// We can parse valid mode strings into [`ParsedMode`] using [`ParsedMode::from_str`].
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub struct ParsedMode {
|
||||
pub pipeline: Option<ModePipeline>,
|
||||
pub optimize_flag: Option<bool>,
|
||||
pub optimize_setting: Option<ModeOptimizerSetting>,
|
||||
pub version: Option<semver::VersionReq>,
|
||||
}
|
||||
|
||||
impl FromStr for ParsedMode {
|
||||
type Err = anyhow::Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
static REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?x)
|
||||
^
|
||||
(?:(?P<pipeline>[YEILV])(?P<optimize_flag>[+-])?)? # Pipeline to use eg Y, E+, E-
|
||||
\s*
|
||||
(?P<optimize_setting>M[a-zA-Z0-9])? # Optimize setting eg M0, Ms, Mz
|
||||
\s*
|
||||
(?P<version>[>=<^]*\d+(?:\.\d+)*)? # Optional semver version eg >=0.8.0, 0.7, <0.8
|
||||
$
|
||||
").unwrap()
|
||||
});
|
||||
|
||||
let Some(caps) = REGEX.captures(s) else {
|
||||
anyhow::bail!("Cannot parse mode '{s}' from string");
|
||||
};
|
||||
|
||||
let pipeline = match caps.name("pipeline") {
|
||||
Some(m) => Some(
|
||||
ModePipeline::from_str(m.as_str())
|
||||
.context("Failed to parse mode pipeline from string")?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let optimize_flag = caps.name("optimize_flag").map(|m| m.as_str() == "+");
|
||||
|
||||
let optimize_setting = match caps.name("optimize_setting") {
|
||||
Some(m) => Some(
|
||||
ModeOptimizerSetting::from_str(m.as_str())
|
||||
.context("Failed to parse optimizer setting from string")?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let version = match caps.name("version") {
|
||||
Some(m) => Some(
|
||||
semver::VersionReq::parse(m.as_str())
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"Cannot parse the version requirement '{}': {e}",
|
||||
m.as_str()
|
||||
)
|
||||
})
|
||||
.context("Failed to parse semver requirement from mode string")?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(ParsedMode {
|
||||
pipeline,
|
||||
optimize_flag,
|
||||
optimize_setting,
|
||||
version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParsedMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut has_written = false;
|
||||
|
||||
if let Some(pipeline) = self.pipeline {
|
||||
pipeline.fmt(f)?;
|
||||
if let Some(optimize_flag) = self.optimize_flag {
|
||||
f.write_str(if optimize_flag { "+" } else { "-" })?;
|
||||
}
|
||||
has_written = true;
|
||||
}
|
||||
|
||||
if let Some(optimize_setting) = self.optimize_setting {
|
||||
if has_written {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
optimize_setting.fmt(f)?;
|
||||
has_written = true;
|
||||
}
|
||||
|
||||
if let Some(version) = &self.version {
|
||||
if has_written {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
version.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParsedMode> for String {
|
||||
fn from(parsed_mode: ParsedMode) -> Self {
|
||||
parsed_mode.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ParsedMode {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
ParsedMode::from_str(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParsedMode {
|
||||
/// This takes a [`ParsedMode`] and expands it into a list of [`Mode`]s that we should try.
|
||||
pub fn to_modes(&self) -> impl Iterator<Item = Mode> {
|
||||
let pipeline_iter = self.pipeline.as_ref().map_or_else(
|
||||
|| EitherIter::A(ModePipeline::test_cases()),
|
||||
|p| EitherIter::B(std::iter::once(*p)),
|
||||
);
|
||||
|
||||
let optimize_flag_setting = self.optimize_flag.map(|flag| {
|
||||
if flag {
|
||||
ModeOptimizerSetting::M3
|
||||
} else {
|
||||
ModeOptimizerSetting::M0
|
||||
}
|
||||
});
|
||||
|
||||
let optimize_flag_iter = match optimize_flag_setting {
|
||||
Some(setting) => EitherIter::A(std::iter::once(setting)),
|
||||
None => EitherIter::B(ModeOptimizerSetting::test_cases()),
|
||||
};
|
||||
|
||||
let optimize_settings_iter = self.optimize_setting.as_ref().map_or_else(
|
||||
|| EitherIter::A(optimize_flag_iter),
|
||||
|s| EitherIter::B(std::iter::once(*s)),
|
||||
);
|
||||
|
||||
pipeline_iter.flat_map(move |pipeline| {
|
||||
optimize_settings_iter
|
||||
.clone()
|
||||
.map(move |optimize_setting| Mode {
|
||||
pipeline,
|
||||
optimize_setting,
|
||||
version: self.version.clone(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a set of [`Mode`]s that correspond to the given [`ParsedMode`]s.
|
||||
/// This avoids any duplicate entries.
|
||||
pub fn many_to_modes<'a>(
|
||||
parsed: impl Iterator<Item = &'a ParsedMode>,
|
||||
) -> impl Iterator<Item = Mode> {
|
||||
let modes: HashSet<_> = parsed.flat_map(|p| p.to_modes()).collect();
|
||||
modes.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parsed_mode_from_str() {
|
||||
let strings = vec![
|
||||
("Mz", "Mz"),
|
||||
("Y", "Y"),
|
||||
("Y+", "Y+"),
|
||||
("Y-", "Y-"),
|
||||
("E", "E"),
|
||||
("E+", "E+"),
|
||||
("E-", "E-"),
|
||||
("Y M0", "Y M0"),
|
||||
("Y M1", "Y M1"),
|
||||
("Y M2", "Y M2"),
|
||||
("Y M3", "Y M3"),
|
||||
("Y Ms", "Y Ms"),
|
||||
("Y Mz", "Y Mz"),
|
||||
("E M0", "E M0"),
|
||||
("E M1", "E M1"),
|
||||
("E M2", "E M2"),
|
||||
("E M3", "E M3"),
|
||||
("E Ms", "E Ms"),
|
||||
("E Mz", "E Mz"),
|
||||
// When stringifying semver again, 0.8.0 becomes ^0.8.0 (same meaning)
|
||||
("Y 0.8.0", "Y ^0.8.0"),
|
||||
("E+ 0.8.0", "E+ ^0.8.0"),
|
||||
("Y M3 >=0.8.0", "Y M3 >=0.8.0"),
|
||||
("E Mz <0.7.0", "E Mz <0.7.0"),
|
||||
// We can parse +- _and_ M1/M2 but the latter takes priority.
|
||||
("Y+ M1 0.8.0", "Y+ M1 ^0.8.0"),
|
||||
("E- M2 0.7.0", "E- M2 ^0.7.0"),
|
||||
// We don't see this in the wild but it is parsed.
|
||||
("<=0.8", "<=0.8"),
|
||||
];
|
||||
|
||||
for (actual, expected) in strings {
|
||||
let parsed = ParsedMode::from_str(actual)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
|
||||
assert_eq!(
|
||||
expected,
|
||||
parsed.to_string(),
|
||||
"Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parsed_mode_to_test_modes() {
|
||||
let strings = vec![
|
||||
("Mz", vec!["Y Mz", "E Mz"]),
|
||||
("Y", vec!["Y M0", "Y M3"]),
|
||||
("E", vec!["E M0", "E M3"]),
|
||||
("Y+", vec!["Y M3"]),
|
||||
("Y-", vec!["Y M0"]),
|
||||
("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]),
|
||||
(
|
||||
"<=0.8",
|
||||
vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"],
|
||||
),
|
||||
];
|
||||
|
||||
for (actual, expected) in strings {
|
||||
let parsed = ParsedMode::from_str(actual)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
|
||||
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();
|
||||
|
||||
assert_eq!(
|
||||
expected_set, actual_set,
|
||||
"Mode string '{actual}' did not expand to '{expected_set:?}': got '{actual_set:?}'"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ pragma solidity >=0.6.9;
|
||||
import "./callable.sol";
|
||||
|
||||
contract Main {
|
||||
function main(uint[1] calldata p1, Callable callable) public returns(uint) {
|
||||
function main(
|
||||
uint[1] calldata p1,
|
||||
Callable callable
|
||||
) public pure returns (uint) {
|
||||
return callable.f(p1);
|
||||
}
|
||||
}
|
||||
|
||||
+114
-47
@@ -12,19 +12,18 @@ use std::{
|
||||
|
||||
use alloy::{
|
||||
genesis::Genesis,
|
||||
hex::ToHexExt,
|
||||
network::EthereumWallet,
|
||||
primitives::{FixedBytes, U256},
|
||||
primitives::{B256, FixedBytes, U256},
|
||||
signers::local::PrivateKeySigner,
|
||||
};
|
||||
use clap::{Parser, ValueEnum, ValueHint};
|
||||
use revive_dt_common::types::PlatformIdentifier;
|
||||
use semver::Version;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use strum::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||
use temp_dir::TempDir;
|
||||
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
#[command(name = "retester")]
|
||||
pub enum Context {
|
||||
/// Executes tests in the MatterLabs format differentially on multiple targets concurrently.
|
||||
@@ -200,8 +199,30 @@ impl AsRef<ReportConfiguration> for Context {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
impl AsRef<IgnoreSuccessConfiguration> for Context {
|
||||
fn as_ref(&self) -> &IgnoreSuccessConfiguration {
|
||||
match self {
|
||||
Self::Test(context) => context.as_ref().as_ref(),
|
||||
Self::Benchmark(..) => unreachable!(),
|
||||
Self::ExportJsonSchema => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct TestExecutionContext {
|
||||
/// The set of platforms that the differential tests should run on.
|
||||
#[arg(
|
||||
short = 'p',
|
||||
long = "platform",
|
||||
default_values = ["geth-evm-solc", "revive-dev-node-polkavm-resolc"]
|
||||
)]
|
||||
pub platforms: Vec<PlatformIdentifier>,
|
||||
|
||||
/// The output format to use for the tool's output.
|
||||
#[arg(short, long, default_value_t = OutputFormat::CargoTestLike)]
|
||||
pub output_format: OutputFormat,
|
||||
|
||||
/// The working directory that the program will use for all of the temporary artifacts needed at
|
||||
/// runtime.
|
||||
///
|
||||
@@ -215,14 +236,6 @@ pub struct TestExecutionContext {
|
||||
)]
|
||||
pub working_directory: WorkingDirectoryConfiguration,
|
||||
|
||||
/// The set of platforms that the differential tests should run on.
|
||||
#[arg(
|
||||
short = 'p',
|
||||
long = "platform",
|
||||
default_values = ["geth-evm-solc", "revive-dev-node-polkavm-resolc"]
|
||||
)]
|
||||
pub platforms: Vec<PlatformIdentifier>,
|
||||
|
||||
/// Configuration parameters for the corpus files to use.
|
||||
#[clap(flatten, next_help_heading = "Corpus Configuration")]
|
||||
pub corpus_configuration: CorpusConfiguration,
|
||||
@@ -278,9 +291,13 @@ pub struct TestExecutionContext {
|
||||
/// Configuration parameters for the report.
|
||||
#[clap(flatten, next_help_heading = "Report Configuration")]
|
||||
pub report_configuration: ReportConfiguration,
|
||||
|
||||
/// Configuration parameters for ignoring certain test cases based on the report
|
||||
#[clap(flatten, next_help_heading = "Ignore Success Configuration")]
|
||||
pub ignore_success_configuration: IgnoreSuccessConfiguration,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct BenchmarkingContext {
|
||||
/// The working directory that the program will use for all of the temporary artifacts needed at
|
||||
/// runtime.
|
||||
@@ -457,6 +474,12 @@ impl AsRef<ReportConfiguration> for TestExecutionContext {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<IgnoreSuccessConfiguration> for TestExecutionContext {
|
||||
fn as_ref(&self) -> &IgnoreSuccessConfiguration {
|
||||
&self.ignore_success_configuration
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BenchmarkingContext {
|
||||
fn default() -> Self {
|
||||
Self::parse_from(["execution-context"])
|
||||
@@ -548,7 +571,7 @@ impl AsRef<ReportConfiguration> for BenchmarkingContext {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for the corpus files to use for the execution.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct CorpusConfiguration {
|
||||
/// A list of test corpus JSON files to be tested.
|
||||
#[arg(short = 'c', long = "corpus")]
|
||||
@@ -556,7 +579,7 @@ pub struct CorpusConfiguration {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for Solc.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct SolcConfiguration {
|
||||
/// Specifies the default version of the Solc compiler that should be used if there is no
|
||||
/// override specified by one of the test cases.
|
||||
@@ -565,7 +588,7 @@ pub struct SolcConfiguration {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for Resolc.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct ResolcConfiguration {
|
||||
/// Specifies the path of the resolc compiler to be used by the tool.
|
||||
///
|
||||
@@ -576,7 +599,7 @@ pub struct ResolcConfiguration {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for Polkadot Parachain.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct PolkadotParachainConfiguration {
|
||||
/// Specifies the path of the polkadot-parachain node to be used by the tool.
|
||||
///
|
||||
@@ -600,7 +623,7 @@ pub struct PolkadotParachainConfiguration {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for Geth.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct GethConfiguration {
|
||||
/// Specifies the path of the geth node to be used by the tool.
|
||||
///
|
||||
@@ -620,7 +643,7 @@ pub struct GethConfiguration {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for kurtosis.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct KurtosisConfiguration {
|
||||
/// Specifies the path of the kurtosis node to be used by the tool.
|
||||
///
|
||||
@@ -635,7 +658,7 @@ pub struct KurtosisConfiguration {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for Kitchensink.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct KitchensinkConfiguration {
|
||||
/// Specifies the path of the kitchensink node to be used by the tool.
|
||||
///
|
||||
@@ -659,7 +682,7 @@ pub struct KitchensinkConfiguration {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for the revive dev node.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct ReviveDevNodeConfiguration {
|
||||
/// Specifies the path of the revive dev node to be used by the tool.
|
||||
///
|
||||
@@ -680,10 +703,18 @@ pub struct ReviveDevNodeConfiguration {
|
||||
value_parser = parse_duration
|
||||
)]
|
||||
pub start_timeout_ms: Duration,
|
||||
|
||||
/// The consensus to use for the spawned revive-dev-node.
|
||||
#[clap(
|
||||
id = "revive-dev-node.consensus",
|
||||
long = "revive-dev-node.consensus",
|
||||
default_value = "instant-seal"
|
||||
)]
|
||||
pub consensus: String,
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for the ETH RPC.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct EthRpcConfiguration {
|
||||
/// Specifies the path of the ETH RPC to be used by the tool.
|
||||
///
|
||||
@@ -703,7 +734,7 @@ pub struct EthRpcConfiguration {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for the genesis.
|
||||
#[derive(Clone, Debug, Default, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Default, Parser, Serialize, Deserialize)]
|
||||
pub struct GenesisConfiguration {
|
||||
/// Specifies the path of the genesis file to use for the nodes that are started.
|
||||
///
|
||||
@@ -741,15 +772,14 @@ impl GenesisConfiguration {
|
||||
}
|
||||
|
||||
/// A set of configuration parameters for the wallet.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct WalletConfiguration {
|
||||
/// The private key of the default signer.
|
||||
#[clap(
|
||||
long = "wallet.default-private-key",
|
||||
default_value = "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_private_key")]
|
||||
default_key: PrivateKeySigner,
|
||||
default_key: B256,
|
||||
|
||||
/// This argument controls which private keys the nodes should have access to and be added to
|
||||
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
|
||||
@@ -767,7 +797,8 @@ impl WalletConfiguration {
|
||||
pub fn wallet(&self) -> Arc<EthereumWallet> {
|
||||
self.wallet
|
||||
.get_or_init(|| {
|
||||
let mut wallet = EthereumWallet::new(self.default_key.clone());
|
||||
let mut wallet =
|
||||
EthereumWallet::new(PrivateKeySigner::from_bytes(&self.default_key).unwrap());
|
||||
for signer in (1..=self.additional_keys)
|
||||
.map(|id| U256::from(id))
|
||||
.map(|id| id.to_be_bytes::<32>())
|
||||
@@ -785,15 +816,8 @@ impl WalletConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_private_key<S>(value: &PrivateKeySigner, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
value.to_bytes().encode_hex().serialize(serializer)
|
||||
}
|
||||
|
||||
/// A set of configuration for concurrency.
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct ConcurrencyConfiguration {
|
||||
/// Determines the amount of nodes that will be spawned for each chain.
|
||||
#[clap(long = "concurrency.number-of-nodes", default_value_t = 5)]
|
||||
@@ -831,14 +855,14 @@ impl ConcurrencyConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct CompilationConfiguration {
|
||||
/// Controls if the compilation cache should be invalidated or not.
|
||||
#[arg(long = "compilation.invalidate-cache")]
|
||||
pub invalidate_compilation_cache: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser, Serialize)]
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct ReportConfiguration {
|
||||
/// Controls if the compiler input is included in the final report.
|
||||
#[clap(long = "report.include-compiler-input")]
|
||||
@@ -849,6 +873,13 @@ pub struct ReportConfiguration {
|
||||
pub include_compiler_output: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser, Serialize, Deserialize)]
|
||||
pub struct IgnoreSuccessConfiguration {
|
||||
/// The path of the report generated by the tool to use to ignore the cases that succeeded.
|
||||
#[clap(long = "ignore-success.report-path")]
|
||||
pub path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Represents the working directory that the program uses.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WorkingDirectoryConfiguration {
|
||||
@@ -858,6 +889,24 @@ pub enum WorkingDirectoryConfiguration {
|
||||
Path(PathBuf),
|
||||
}
|
||||
|
||||
impl Serialize for WorkingDirectoryConfiguration {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.as_path().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deserialize<'a> for WorkingDirectoryConfiguration {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'a>,
|
||||
{
|
||||
PathBuf::deserialize(deserializer).map(Self::Path)
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkingDirectoryConfiguration {
|
||||
pub fn as_path(&self) -> &Path {
|
||||
self.as_ref()
|
||||
@@ -907,15 +956,6 @@ impl Display for WorkingDirectoryConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for WorkingDirectoryConfiguration {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.as_path().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_duration(s: &str) -> anyhow::Result<Duration> {
|
||||
u64::from_str(s)
|
||||
.map(Duration::from_millis)
|
||||
@@ -950,3 +990,30 @@ pub enum TestingPlatform {
|
||||
/// A polkadot/Substrate based network
|
||||
Zombienet,
|
||||
}
|
||||
|
||||
/// The output format to use for the test execution output.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
ValueEnum,
|
||||
EnumString,
|
||||
Display,
|
||||
AsRefStr,
|
||||
IntoStaticStr,
|
||||
)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
pub enum OutputFormat {
|
||||
/// The legacy format that was used in the past for the output.
|
||||
Legacy,
|
||||
|
||||
/// An output format that looks heavily resembles the output from `cargo test`.
|
||||
CargoTestLike,
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ revive-dt-node = { workspace = true }
|
||||
revive-dt-node-interaction = { workspace = true }
|
||||
revive-dt-report = { workspace = true }
|
||||
|
||||
ansi_term = { workspace = true }
|
||||
alloy = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
bson = { workspace = true }
|
||||
|
||||
@@ -22,6 +22,7 @@ use alloy::{
|
||||
},
|
||||
};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use futures::TryFutureExt;
|
||||
use indexmap::IndexMap;
|
||||
use revive_dt_common::{
|
||||
futures::{PollingWaitBehavior, poll},
|
||||
@@ -35,7 +36,7 @@ use revive_dt_format::{
|
||||
},
|
||||
traits::{ResolutionContext, ResolverApi},
|
||||
};
|
||||
use tokio::sync::{Mutex, mpsc::UnboundedSender};
|
||||
use tokio::sync::{Mutex, OnceCell, mpsc::UnboundedSender};
|
||||
use tracing::{Instrument, Span, debug, error, field::display, info, info_span, instrument};
|
||||
|
||||
use crate::{
|
||||
@@ -123,13 +124,7 @@ where
|
||||
&self.platform_information.reporter,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
platform_identifier = %self.platform_information.platform.platform_identifier(),
|
||||
"Pre-linking compilation failed"
|
||||
)
|
||||
})
|
||||
.inspect_err(|err| error!(?err, "Pre-linking compilation failed"))
|
||||
.context("Failed to produce the pre-linking compiled contracts")?;
|
||||
|
||||
let mut deployed_libraries = None::<HashMap<_, _>>;
|
||||
@@ -137,13 +132,7 @@ where
|
||||
.test_definition
|
||||
.metadata
|
||||
.contract_sources()
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
platform_identifier = %self.platform_information.platform.platform_identifier(),
|
||||
"Failed to retrieve contract sources from metadata"
|
||||
)
|
||||
})
|
||||
.inspect_err(|err| error!(?err, "Failed to retrieve contract sources from metadata"))
|
||||
.context("Failed to get the contract instances from the metadata file")?;
|
||||
for library_instance in self
|
||||
.test_definition
|
||||
@@ -191,20 +180,19 @@ where
|
||||
TransactionRequest::default().from(deployer_address),
|
||||
code,
|
||||
);
|
||||
let receipt = self.execute_transaction(tx).await.inspect_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
%library_instance,
|
||||
platform_identifier = %self.platform_information.platform.platform_identifier(),
|
||||
"Failed to deploy the library"
|
||||
)
|
||||
})?;
|
||||
let receipt = self
|
||||
.execute_transaction(tx)
|
||||
.and_then(|(_, receipt_fut)| receipt_fut)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
%library_instance,
|
||||
"Failed to deploy the library"
|
||||
)
|
||||
})?;
|
||||
|
||||
debug!(
|
||||
?library_instance,
|
||||
platform_identifier = %self.platform_information.platform.platform_identifier(),
|
||||
"Deployed library"
|
||||
);
|
||||
debug!(?library_instance, "Deployed library");
|
||||
|
||||
let library_address = receipt
|
||||
.contract_address
|
||||
@@ -227,13 +215,7 @@ where
|
||||
&self.platform_information.reporter,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
platform_identifier = %self.platform_information.platform.platform_identifier(),
|
||||
"Post-linking compilation failed"
|
||||
)
|
||||
})
|
||||
.inspect_err(|err| error!(?err, "Post-linking compilation failed"))
|
||||
.context("Failed to compile the post-link contracts")?;
|
||||
|
||||
self.execution_state = ExecutionState::new(
|
||||
@@ -269,7 +251,6 @@ where
|
||||
skip_all,
|
||||
fields(
|
||||
driver_id = self.driver_id,
|
||||
platform_identifier = %self.platform_information.platform.platform_identifier(),
|
||||
%step_path,
|
||||
),
|
||||
err(Debug),
|
||||
@@ -305,15 +286,11 @@ where
|
||||
.handle_function_call_contract_deployment(step)
|
||||
.await
|
||||
.context("Failed to deploy contracts for the function call step")?;
|
||||
let execution_receipt = self
|
||||
let transaction_hash = self
|
||||
.handle_function_call_execution(step, deployment_receipts)
|
||||
.await
|
||||
.context("Failed to handle the function call execution")?;
|
||||
let tracing_result = self
|
||||
.handle_function_call_call_frame_tracing(execution_receipt.transaction_hash)
|
||||
.await
|
||||
.context("Failed to handle the function call call frame tracing")?;
|
||||
self.handle_function_call_variable_assignment(step, &tracing_result)
|
||||
self.handle_function_call_variable_assignment(step, transaction_hash)
|
||||
.await
|
||||
.context("Failed to handle function call variable assignment")?;
|
||||
Ok(1)
|
||||
@@ -367,18 +344,19 @@ where
|
||||
&mut self,
|
||||
step: &FunctionCallStep,
|
||||
mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
|
||||
) -> Result<TransactionReceipt> {
|
||||
) -> Result<TxHash> {
|
||||
match step.method {
|
||||
// This step was already executed when `handle_step` was called. We just need to
|
||||
// lookup the transaction receipt in this case and continue on.
|
||||
Method::Deployer => deployment_receipts
|
||||
.remove(&step.instance)
|
||||
.context("Failed to find deployment receipt for constructor call"),
|
||||
.context("Failed to find deployment receipt for constructor call")
|
||||
.map(|receipt| receipt.transaction_hash),
|
||||
Method::Fallback | Method::FunctionName(_) => {
|
||||
let tx = step
|
||||
.as_transaction(self.resolver.as_ref(), self.default_resolution_context())
|
||||
.await?;
|
||||
self.execute_transaction(tx).await
|
||||
Ok(self.execute_transaction(tx).await?.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,15 +395,19 @@ where
|
||||
async fn handle_function_call_variable_assignment(
|
||||
&mut self,
|
||||
step: &FunctionCallStep,
|
||||
tracing_result: &CallFrame,
|
||||
tx_hash: TxHash,
|
||||
) -> Result<()> {
|
||||
let Some(ref assignments) = step.variable_assignments else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Handling the return data variable assignments.
|
||||
let callframe = OnceCell::new();
|
||||
for (variable_name, output_word) in assignments.return_data.iter().zip(
|
||||
tracing_result
|
||||
callframe
|
||||
.get_or_try_init(|| self.handle_function_call_call_frame_tracing(tx_hash))
|
||||
.await
|
||||
.context("Failed to get the callframe trace for transaction")?
|
||||
.output
|
||||
.as_ref()
|
||||
.unwrap_or_default()
|
||||
@@ -547,7 +529,6 @@ where
|
||||
skip_all,
|
||||
fields(
|
||||
driver_id = self.driver_id,
|
||||
platform_identifier = %self.platform_information.platform.platform_identifier(),
|
||||
%contract_instance,
|
||||
%deployer
|
||||
),
|
||||
@@ -590,7 +571,6 @@ where
|
||||
skip_all,
|
||||
fields(
|
||||
driver_id = self.driver_id,
|
||||
platform_identifier = %self.platform_information.platform.platform_identifier(),
|
||||
%contract_instance,
|
||||
%deployer
|
||||
),
|
||||
@@ -660,7 +640,11 @@ where
|
||||
TransactionBuilder::<Ethereum>::with_deploy_code(tx, code)
|
||||
};
|
||||
|
||||
let receipt = match self.execute_transaction(tx).await {
|
||||
let receipt = match self
|
||||
.execute_transaction(tx)
|
||||
.and_then(|(_, receipt_fut)| receipt_fut)
|
||||
.await
|
||||
{
|
||||
Ok(receipt) => receipt,
|
||||
Err(error) => {
|
||||
tracing::error!(?error, "Contract deployment transaction failed.");
|
||||
@@ -734,7 +718,7 @@ where
|
||||
async fn execute_transaction(
|
||||
&self,
|
||||
transaction: TransactionRequest,
|
||||
) -> anyhow::Result<TransactionReceipt> {
|
||||
) -> anyhow::Result<(TxHash, impl Future<Output = Result<TransactionReceipt>>)> {
|
||||
let node = self.platform_information.node;
|
||||
let transaction_hash = node
|
||||
.submit_transaction(transaction)
|
||||
@@ -747,24 +731,28 @@ where
|
||||
.send(WatcherEvent::SubmittedTransaction { transaction_hash })
|
||||
.context("Failed to send the transaction hash to the watcher")?;
|
||||
|
||||
info!("Starting to poll for transaction receipt");
|
||||
poll(
|
||||
Duration::from_secs(30 * 60),
|
||||
PollingWaitBehavior::Constant(Duration::from_secs(1)),
|
||||
|| {
|
||||
async move {
|
||||
match node.get_receipt(transaction_hash).await {
|
||||
Ok(receipt) => {
|
||||
info!("Polling succeeded, receipt found");
|
||||
Ok(ControlFlow::Break(receipt))
|
||||
Ok((transaction_hash, async move {
|
||||
info!("Starting to poll for transaction receipt");
|
||||
poll(
|
||||
Duration::from_secs(30 * 60),
|
||||
PollingWaitBehavior::Constant(Duration::from_secs(1)),
|
||||
|| {
|
||||
async move {
|
||||
match node.get_receipt(transaction_hash).await {
|
||||
Ok(receipt) => {
|
||||
info!("Polling succeeded, receipt found");
|
||||
Ok(ControlFlow::Break(receipt))
|
||||
}
|
||||
Err(_) => Ok(ControlFlow::Continue(())),
|
||||
}
|
||||
Err(_) => Ok(ControlFlow::Continue(())),
|
||||
}
|
||||
}
|
||||
.instrument(info_span!("Polling for receipt"))
|
||||
},
|
||||
)
|
||||
.await
|
||||
.instrument(info_span!("Polling for receipt"))
|
||||
},
|
||||
)
|
||||
.instrument(info_span!("Polling for receipt", %transaction_hash))
|
||||
.await
|
||||
.inspect(|_| info!("Found the transaction receipt"))
|
||||
}))
|
||||
}
|
||||
// endregion:Transaction Execution
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use revive_dt_common::types::PrivateKeyAllocator;
|
||||
use revive_dt_core::Platform;
|
||||
use revive_dt_format::steps::{Step, StepIdx, StepPath};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{error, info, info_span, instrument, warn};
|
||||
use tracing::{Instrument, error, info, info_span, instrument, warn};
|
||||
|
||||
use revive_dt_config::{BenchmarkingContext, Context};
|
||||
use revive_dt_report::Reporter;
|
||||
@@ -86,6 +86,7 @@ pub async fn handle_differential_benchmarks(
|
||||
&full_context,
|
||||
metadata_files.iter(),
|
||||
&platforms_and_nodes,
|
||||
None,
|
||||
reporter.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -159,12 +160,15 @@ pub async fn handle_differential_benchmarks(
|
||||
|
||||
futures::future::try_join(
|
||||
watcher.run(),
|
||||
driver.execute_all().inspect(|_| {
|
||||
info!("All transactions submitted - driver completed execution");
|
||||
watcher_tx
|
||||
.send(WatcherEvent::AllTransactionsSubmitted)
|
||||
.unwrap()
|
||||
}),
|
||||
driver
|
||||
.execute_all()
|
||||
.instrument(info_span!("Executing Benchmarks", %platform_identifier))
|
||||
.inspect(|_| {
|
||||
info!("All transactions submitted - driver completed execution");
|
||||
watcher_tx
|
||||
.send(WatcherEvent::AllTransactionsSubmitted)
|
||||
.unwrap()
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.context("Failed to run the driver and executor")
|
||||
|
||||
@@ -104,6 +104,12 @@ impl Watcher {
|
||||
async move {
|
||||
let mut mined_blocks_information = Vec::new();
|
||||
|
||||
// region:TEMPORARY
|
||||
eprintln!("Watcher information for {}", self.platform_identifier);
|
||||
eprintln!(
|
||||
"block_number,block_timestamp,mined_gas,block_gas_limit,tx_count,ref_time,max_ref_time,proof_size,max_proof_size"
|
||||
);
|
||||
// endregion:TEMPORARY
|
||||
while let Some(block) = blocks_information_stream.next().await {
|
||||
// If the block number is equal to or less than the last block before the
|
||||
// repetition then we ignore it and continue on to the next block.
|
||||
@@ -118,8 +124,9 @@ impl Watcher {
|
||||
}
|
||||
|
||||
info!(
|
||||
remaining_transactions = watch_for_transaction_hashes.read().await.len(),
|
||||
block_number = block.block_number,
|
||||
block_tx_count = block.transaction_hashes.len(),
|
||||
remaining_transactions = watch_for_transaction_hashes.read().await.len(),
|
||||
"Observed a block"
|
||||
);
|
||||
|
||||
@@ -131,6 +138,24 @@ impl Watcher {
|
||||
watch_for_transaction_hashes.remove(tx_hash);
|
||||
}
|
||||
|
||||
// region:TEMPORARY
|
||||
// TODO: The following core is TEMPORARY and will be removed once we have proper
|
||||
// reporting in place and then it can be removed. This serves as as way of doing
|
||||
// some very simple reporting for the time being.
|
||||
eprintln!(
|
||||
"\"{}\",\"{}\",\"{}\",\"{}\",\"{}\",\"{}\",\"{}\",\"{}\",\"{}\"",
|
||||
block.block_number,
|
||||
block.block_timestamp,
|
||||
block.mined_gas,
|
||||
block.block_gas_limit,
|
||||
block.transaction_hashes.len(),
|
||||
block.ref_time,
|
||||
block.max_ref_time,
|
||||
block.proof_size,
|
||||
block.max_proof_size,
|
||||
);
|
||||
// endregion:TEMPORARY
|
||||
|
||||
mined_blocks_information.push(block);
|
||||
}
|
||||
|
||||
@@ -139,41 +164,10 @@ impl Watcher {
|
||||
}
|
||||
};
|
||||
|
||||
let (_, mined_blocks_information) =
|
||||
let (_, _) =
|
||||
futures::future::join(watcher_event_watching_task, block_information_watching_task)
|
||||
.await;
|
||||
|
||||
// region:TEMPORARY
|
||||
{
|
||||
// TODO: The following core is TEMPORARY and will be removed once we have proper
|
||||
// reporting in place and then it can be removed. This serves as as way of doing some
|
||||
// very simple reporting for the time being.
|
||||
use std::io::Write;
|
||||
|
||||
let mut stderr = std::io::stderr().lock();
|
||||
writeln!(
|
||||
stderr,
|
||||
"Watcher information for {}",
|
||||
self.platform_identifier
|
||||
)?;
|
||||
writeln!(
|
||||
stderr,
|
||||
"block_number,block_timestamp,mined_gas,block_gas_limit,tx_count"
|
||||
)?;
|
||||
for block in mined_blocks_information {
|
||||
writeln!(
|
||||
stderr,
|
||||
"{},{},{},{},{}",
|
||||
block.block_number,
|
||||
block.block_timestamp,
|
||||
block.mined_gas,
|
||||
block.block_gas_limit,
|
||||
block.transaction_hashes.len()
|
||||
)?
|
||||
}
|
||||
}
|
||||
// endregion:TEMPORARY
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,15 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use ansi_term::{ANSIStrings, Color};
|
||||
use anyhow::Context as _;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use revive_dt_common::types::PrivateKeyAllocator;
|
||||
use revive_dt_common::{cached_fs::read_to_string, types::PrivateKeyAllocator};
|
||||
use revive_dt_core::Platform;
|
||||
use tokio::sync::{Mutex, RwLock, Semaphore};
|
||||
use tracing::{Instrument, error, info, info_span, instrument};
|
||||
|
||||
use revive_dt_config::{Context, TestExecutionContext};
|
||||
use revive_dt_config::{Context, OutputFormat, TestExecutionContext};
|
||||
use revive_dt_report::{Reporter, ReporterEvent, TestCaseStatus};
|
||||
|
||||
use crate::{
|
||||
@@ -71,11 +72,20 @@ pub async fn handle_differential_tests(
|
||||
info!("Spawned the platform nodes");
|
||||
|
||||
// Preparing test definitions.
|
||||
let only_execute_failed_tests = match context.ignore_success_configuration.path.as_ref() {
|
||||
Some(path) => {
|
||||
let report = read_to_string(path)
|
||||
.context("Failed to read the report file to ignore the succeeding test cases")?;
|
||||
Some(serde_json::from_str(&report).context("Failed to deserialize report")?)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let full_context = Context::Test(Box::new(context.clone()));
|
||||
let test_definitions = create_test_definitions_stream(
|
||||
&full_context,
|
||||
metadata_files.iter(),
|
||||
&platforms_and_nodes,
|
||||
only_execute_failed_tests.as_ref(),
|
||||
reporter.clone(),
|
||||
)
|
||||
.await
|
||||
@@ -176,7 +186,7 @@ pub async fn handle_differential_tests(
|
||||
.report_completion_event()
|
||||
.expect("Can't fail")
|
||||
});
|
||||
let cli_reporting_task = start_cli_reporting_task(reporter);
|
||||
let cli_reporting_task = start_cli_reporting_task(context.output_format, reporter);
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
loop {
|
||||
@@ -196,21 +206,15 @@ pub async fn handle_differential_tests(
|
||||
}
|
||||
|
||||
#[allow(irrefutable_let_patterns, clippy::uninlined_format_args)]
|
||||
async fn start_cli_reporting_task(reporter: Reporter) {
|
||||
async fn start_cli_reporting_task(output_format: OutputFormat, reporter: Reporter) {
|
||||
let mut aggregator_events_rx = reporter.subscribe().await.expect("Can't fail");
|
||||
drop(reporter);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
const GREEN: &str = "\x1B[32m";
|
||||
const RED: &str = "\x1B[31m";
|
||||
const GREY: &str = "\x1B[90m";
|
||||
const COLOR_RESET: &str = "\x1B[0m";
|
||||
const BOLD: &str = "\x1B[1m";
|
||||
const BOLD_RESET: &str = "\x1B[22m";
|
||||
|
||||
let mut number_of_successes = 0;
|
||||
let mut number_of_failures = 0;
|
||||
let mut global_success_count = 0;
|
||||
let mut global_failure_count = 0;
|
||||
let mut global_ignore_count = 0;
|
||||
|
||||
let mut buf = BufWriter::new(stderr());
|
||||
while let Ok(event) = aggregator_events_rx.recv().await {
|
||||
@@ -223,55 +227,132 @@ async fn start_cli_reporting_task(reporter: Reporter) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let _ = writeln!(buf, "{} - {}", mode, metadata_file_path.display());
|
||||
for (case_idx, case_status) in case_status.into_iter() {
|
||||
let _ = write!(buf, "\tCase Index {case_idx:>3}: ");
|
||||
let _ = match case_status {
|
||||
TestCaseStatus::Succeeded { steps_executed } => {
|
||||
number_of_successes += 1;
|
||||
writeln!(
|
||||
buf,
|
||||
"{}{}Case Succeeded{} - Steps Executed: {}{}",
|
||||
GREEN, BOLD, BOLD_RESET, steps_executed, COLOR_RESET
|
||||
)
|
||||
match output_format {
|
||||
OutputFormat::Legacy => {
|
||||
let _ = writeln!(buf, "{} - {}", mode, metadata_file_path.display());
|
||||
for (case_idx, case_status) in case_status.into_iter() {
|
||||
let _ = write!(buf, "\tCase Index {case_idx:>3}: ");
|
||||
let _ = match case_status {
|
||||
TestCaseStatus::Succeeded { steps_executed } => {
|
||||
global_success_count += 1;
|
||||
writeln!(
|
||||
buf,
|
||||
"{}",
|
||||
ANSIStrings(&[
|
||||
Color::Green.bold().paint("Case Succeeded"),
|
||||
Color::Green
|
||||
.paint(format!(" - Steps Executed: {steps_executed}")),
|
||||
])
|
||||
)
|
||||
}
|
||||
TestCaseStatus::Failed { reason } => {
|
||||
global_failure_count += 1;
|
||||
writeln!(
|
||||
buf,
|
||||
"{}",
|
||||
ANSIStrings(&[
|
||||
Color::Red.bold().paint("Case Failed"),
|
||||
Color::Red.paint(format!(" - Reason: {}", reason.trim())),
|
||||
])
|
||||
)
|
||||
}
|
||||
TestCaseStatus::Ignored { reason, .. } => {
|
||||
global_ignore_count += 1;
|
||||
writeln!(
|
||||
buf,
|
||||
"{}",
|
||||
ANSIStrings(&[
|
||||
Color::Yellow.bold().paint("Case Ignored"),
|
||||
Color::Yellow.paint(format!(" - Reason: {}", reason.trim())),
|
||||
])
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
TestCaseStatus::Failed { reason } => {
|
||||
number_of_failures += 1;
|
||||
writeln!(
|
||||
buf,
|
||||
"{}{}Case Failed{} - Reason: {}{}",
|
||||
RED,
|
||||
BOLD,
|
||||
BOLD_RESET,
|
||||
reason.trim(),
|
||||
COLOR_RESET,
|
||||
)
|
||||
}
|
||||
TestCaseStatus::Ignored { reason, .. } => writeln!(
|
||||
let _ = writeln!(buf);
|
||||
}
|
||||
OutputFormat::CargoTestLike => {
|
||||
writeln!(
|
||||
buf,
|
||||
"{}{}Case Ignored{} - Reason: {}{}",
|
||||
GREY,
|
||||
BOLD,
|
||||
BOLD_RESET,
|
||||
reason.trim(),
|
||||
COLOR_RESET,
|
||||
),
|
||||
};
|
||||
"\t{} {} - {}\n",
|
||||
Color::Green.paint("Running"),
|
||||
metadata_file_path.display(),
|
||||
mode
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut success_count = 0;
|
||||
let mut failure_count = 0;
|
||||
let mut ignored_count = 0;
|
||||
writeln!(buf, "running {} tests", case_status.len()).unwrap();
|
||||
for (case_idx, case_result) in case_status.iter() {
|
||||
let status = match case_result {
|
||||
TestCaseStatus::Succeeded { .. } => {
|
||||
success_count += 1;
|
||||
global_success_count += 1;
|
||||
Color::Green.paint("ok")
|
||||
}
|
||||
TestCaseStatus::Failed { reason } => {
|
||||
failure_count += 1;
|
||||
global_failure_count += 1;
|
||||
Color::Red.paint(format!("FAILED, {reason}"))
|
||||
}
|
||||
TestCaseStatus::Ignored { reason, .. } => {
|
||||
ignored_count += 1;
|
||||
global_ignore_count += 1;
|
||||
Color::Yellow.paint(format!("ignored, {reason:?}"))
|
||||
}
|
||||
};
|
||||
writeln!(buf, "test case_idx_{} ... {}", case_idx, status).unwrap();
|
||||
}
|
||||
writeln!(buf).unwrap();
|
||||
|
||||
let status = if failure_count > 0 {
|
||||
Color::Red.paint("FAILED")
|
||||
} else {
|
||||
Color::Green.paint("ok")
|
||||
};
|
||||
writeln!(
|
||||
buf,
|
||||
"test result: {}. {} passed; {} failed; {} ignored",
|
||||
status, success_count, failure_count, ignored_count,
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(buf).unwrap();
|
||||
|
||||
buf = tokio::task::spawn_blocking(move || {
|
||||
buf.flush().unwrap();
|
||||
buf
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
let _ = writeln!(buf);
|
||||
}
|
||||
|
||||
// Summary at the end.
|
||||
let _ = writeln!(
|
||||
buf,
|
||||
"{} cases: {}{}{} cases succeeded, {}{}{} cases failed in {} seconds",
|
||||
number_of_successes + number_of_failures,
|
||||
GREEN,
|
||||
number_of_successes,
|
||||
COLOR_RESET,
|
||||
RED,
|
||||
number_of_failures,
|
||||
COLOR_RESET,
|
||||
start.elapsed().as_secs()
|
||||
);
|
||||
match output_format {
|
||||
OutputFormat::Legacy => {
|
||||
writeln!(
|
||||
buf,
|
||||
"{} cases: {} cases succeeded, {} cases failed in {} seconds",
|
||||
global_success_count + global_failure_count + global_ignore_count,
|
||||
Color::Green.paint(global_success_count.to_string()),
|
||||
Color::Red.paint(global_failure_count.to_string()),
|
||||
start.elapsed().as_secs()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
OutputFormat::CargoTestLike => {
|
||||
writeln!(
|
||||
buf,
|
||||
"run finished. {} passed; {} failed; {} ignored; finished in {}s",
|
||||
global_success_count,
|
||||
global_failure_count,
|
||||
global_ignore_count,
|
||||
start.elapsed().as_secs()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,8 @@ use std::{borrow::Cow, path::Path};
|
||||
use futures::{Stream, StreamExt, stream};
|
||||
use indexmap::{IndexMap, indexmap};
|
||||
use revive_dt_common::iterators::EitherIter;
|
||||
use revive_dt_common::types::PlatformIdentifier;
|
||||
use revive_dt_common::types::{ParsedMode, PlatformIdentifier};
|
||||
use revive_dt_config::Context;
|
||||
use revive_dt_format::mode::ParsedMode;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use revive_dt_compiler::Mode;
|
||||
@@ -17,7 +16,7 @@ use revive_dt_format::{
|
||||
metadata::MetadataFile,
|
||||
};
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
use revive_dt_report::{ExecutionSpecificReporter, Reporter};
|
||||
use revive_dt_report::{ExecutionSpecificReporter, Report, Reporter, TestCaseStatus};
|
||||
use revive_dt_report::{TestSpecificReporter, TestSpecifier};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
@@ -30,6 +29,7 @@ pub async fn create_test_definitions_stream<'a>(
|
||||
context: &Context,
|
||||
metadata_files: impl IntoIterator<Item = &'a MetadataFile>,
|
||||
platforms_and_nodes: &'a BTreeMap<PlatformIdentifier, (&dyn Platform, NodePool)>,
|
||||
only_execute_failed_tests: Option<&Report>,
|
||||
reporter: Reporter,
|
||||
) -> impl Stream<Item = TestDefinition<'a>> {
|
||||
stream::iter(
|
||||
@@ -140,7 +140,7 @@ pub async fn create_test_definitions_stream<'a>(
|
||||
)
|
||||
// Filter out the test cases which are incompatible or that can't run in the current setup.
|
||||
.filter_map(move |test| async move {
|
||||
match test.check_compatibility() {
|
||||
match test.check_compatibility(only_execute_failed_tests) {
|
||||
Ok(()) => Some(test),
|
||||
Err((reason, additional_information)) => {
|
||||
debug!(
|
||||
@@ -200,12 +200,16 @@ pub struct TestDefinition<'a> {
|
||||
|
||||
impl<'a> TestDefinition<'a> {
|
||||
/// Checks if this test can be ran with the current configuration.
|
||||
pub fn check_compatibility(&self) -> TestCheckFunctionResult {
|
||||
pub fn check_compatibility(
|
||||
&self,
|
||||
only_execute_failed_tests: Option<&Report>,
|
||||
) -> TestCheckFunctionResult {
|
||||
self.check_metadata_file_ignored()?;
|
||||
self.check_case_file_ignored()?;
|
||||
self.check_target_compatibility()?;
|
||||
self.check_evm_version_compatibility()?;
|
||||
self.check_compiler_compatibility()?;
|
||||
self.check_ignore_succeeded(only_execute_failed_tests)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -313,6 +317,36 @@ impl<'a> TestDefinition<'a> {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the test case should be executed or not based on the passed report and whether the
|
||||
/// user has instructed the tool to ignore the already succeeding test cases.
|
||||
fn check_ignore_succeeded(
|
||||
&self,
|
||||
only_execute_failed_tests: Option<&Report>,
|
||||
) -> TestCheckFunctionResult {
|
||||
let Some(report) = only_execute_failed_tests else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let test_case_status = report
|
||||
.test_case_information
|
||||
.get(&(self.metadata_file_path.to_path_buf().into()))
|
||||
.and_then(|obj| obj.get(&self.mode))
|
||||
.and_then(|obj| obj.get(&self.case_idx))
|
||||
.and_then(|obj| obj.status.as_ref());
|
||||
|
||||
match test_case_status {
|
||||
Some(TestCaseStatus::Failed { .. }) => Ok(()),
|
||||
Some(TestCaseStatus::Ignored { .. }) => Err((
|
||||
"Ignored since it was ignored in a previous run",
|
||||
indexmap! {},
|
||||
)),
|
||||
Some(TestCaseStatus::Succeeded { .. }) => {
|
||||
Err(("Ignored since it succeeded in a prior run", indexmap! {}))
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestPlatformInformation<'a> {
|
||||
|
||||
+18
-10
@@ -16,7 +16,7 @@ use revive_dt_config::*;
|
||||
use revive_dt_node::{
|
||||
Node, node_implementations::geth::GethNode,
|
||||
node_implementations::lighthouse_geth::LighthouseGethNode,
|
||||
node_implementations::substrate::SubstrateNode, node_implementations::zombienet::ZombieNode,
|
||||
node_implementations::substrate::SubstrateNode, node_implementations::zombienet::ZombienetNode,
|
||||
};
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
use tracing::info;
|
||||
@@ -184,6 +184,7 @@ impl Platform for KitchensinkPolkavmResolcPlatform {
|
||||
let node = SubstrateNode::new(
|
||||
kitchensink_path,
|
||||
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
|
||||
None,
|
||||
context,
|
||||
);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
@@ -236,6 +237,7 @@ impl Platform for KitchensinkRevmSolcPlatform {
|
||||
let node = SubstrateNode::new(
|
||||
kitchensink_path,
|
||||
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
|
||||
None,
|
||||
context,
|
||||
);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
@@ -280,14 +282,17 @@ impl Platform for ReviveDevNodePolkavmResolcPlatform {
|
||||
context: Context,
|
||||
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||
let revive_dev_node_path = AsRef::<ReviveDevNodeConfiguration>::as_ref(&context)
|
||||
.path
|
||||
.clone();
|
||||
let revive_dev_node_configuration = AsRef::<ReviveDevNodeConfiguration>::as_ref(&context);
|
||||
|
||||
let revive_dev_node_path = revive_dev_node_configuration.path.clone();
|
||||
let revive_dev_node_consensus = revive_dev_node_configuration.consensus.clone();
|
||||
|
||||
let genesis = genesis_configuration.genesis()?.clone();
|
||||
Ok(thread::spawn(move || {
|
||||
let node = SubstrateNode::new(
|
||||
revive_dev_node_path,
|
||||
SubstrateNode::REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND,
|
||||
Some(revive_dev_node_consensus),
|
||||
context,
|
||||
);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
@@ -332,14 +337,17 @@ impl Platform for ReviveDevNodeRevmSolcPlatform {
|
||||
context: Context,
|
||||
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||
let revive_dev_node_path = AsRef::<ReviveDevNodeConfiguration>::as_ref(&context)
|
||||
.path
|
||||
.clone();
|
||||
let revive_dev_node_configuration = AsRef::<ReviveDevNodeConfiguration>::as_ref(&context);
|
||||
|
||||
let revive_dev_node_path = revive_dev_node_configuration.path.clone();
|
||||
let revive_dev_node_consensus = revive_dev_node_configuration.consensus.clone();
|
||||
|
||||
let genesis = genesis_configuration.genesis()?.clone();
|
||||
Ok(thread::spawn(move || {
|
||||
let node = SubstrateNode::new(
|
||||
revive_dev_node_path,
|
||||
SubstrateNode::REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND,
|
||||
Some(revive_dev_node_consensus),
|
||||
context,
|
||||
);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
@@ -389,7 +397,7 @@ impl Platform for ZombienetPolkavmResolcPlatform {
|
||||
.clone();
|
||||
let genesis = genesis_configuration.genesis()?.clone();
|
||||
Ok(thread::spawn(move || {
|
||||
let node = ZombieNode::new(polkadot_parachain_path, context);
|
||||
let node = ZombienetNode::new(polkadot_parachain_path, context);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
Ok(Box::new(node) as Box<_>)
|
||||
}))
|
||||
@@ -401,7 +409,7 @@ impl Platform for ZombienetPolkavmResolcPlatform {
|
||||
version: Option<VersionOrRequirement>,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||
Box::pin(async move {
|
||||
let compiler = Solc::new(context, version).await;
|
||||
let compiler = Resolc::new(context, version).await;
|
||||
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||
})
|
||||
}
|
||||
@@ -437,7 +445,7 @@ impl Platform for ZombienetRevmSolcPlatform {
|
||||
.clone();
|
||||
let genesis = genesis_configuration.genesis()?.clone();
|
||||
Ok(thread::spawn(move || {
|
||||
let node = ZombieNode::new(polkadot_parachain_path, context);
|
||||
let node = ZombienetNode::new(polkadot_parachain_path, context);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
Ok(Box::new(node) as Box<_>)
|
||||
}))
|
||||
|
||||
@@ -16,7 +16,6 @@ revive-common = { workspace = true }
|
||||
alloy = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_dt_common::{macros::define_wrapper_type, types::Mode};
|
||||
use revive_dt_common::{
|
||||
macros::define_wrapper_type,
|
||||
types::{Mode, ParsedMode},
|
||||
};
|
||||
|
||||
use crate::{mode::ParsedMode, steps::*};
|
||||
use crate::steps::*;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
|
||||
pub struct Case {
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
pub mod case;
|
||||
pub mod corpus;
|
||||
pub mod metadata;
|
||||
pub mod mode;
|
||||
pub mod steps;
|
||||
pub mod traits;
|
||||
|
||||
@@ -16,11 +16,11 @@ use revive_dt_common::{
|
||||
cached_fs::read_to_string,
|
||||
iterators::FilesWithExtensionIterator,
|
||||
macros::define_wrapper_type,
|
||||
types::{Mode, VmIdentifier},
|
||||
types::{Mode, ParsedMode, VmIdentifier},
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
use crate::{case::Case, mode::ParsedMode};
|
||||
use crate::case::Case;
|
||||
|
||||
pub const METADATA_FILE_EXTENSION: &str = "json";
|
||||
pub const SOLIDITY_CASE_FILE_EXTENSION: &str = "sol";
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
use anyhow::Context as _;
|
||||
use regex::Regex;
|
||||
use revive_dt_common::iterators::EitherIter;
|
||||
use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// This represents a mode that has been parsed from test metadata.
|
||||
///
|
||||
/// Mode strings can take the following form (in pseudo-regex):
|
||||
///
|
||||
/// ```text
|
||||
/// [YEILV][+-]? (M[0123sz])? <semver>?
|
||||
/// ```
|
||||
///
|
||||
/// We can parse valid mode strings into [`ParsedMode`] using [`ParsedMode::from_str`].
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub struct ParsedMode {
|
||||
pub pipeline: Option<ModePipeline>,
|
||||
pub optimize_flag: Option<bool>,
|
||||
pub optimize_setting: Option<ModeOptimizerSetting>,
|
||||
pub version: Option<semver::VersionReq>,
|
||||
}
|
||||
|
||||
impl FromStr for ParsedMode {
|
||||
type Err = anyhow::Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
static REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?x)
|
||||
^
|
||||
(?:(?P<pipeline>[YEILV])(?P<optimize_flag>[+-])?)? # Pipeline to use eg Y, E+, E-
|
||||
\s*
|
||||
(?P<optimize_setting>M[a-zA-Z0-9])? # Optimize setting eg M0, Ms, Mz
|
||||
\s*
|
||||
(?P<version>[>=<]*\d+(?:\.\d+)*)? # Optional semver version eg >=0.8.0, 0.7, <0.8
|
||||
$
|
||||
").unwrap()
|
||||
});
|
||||
|
||||
let Some(caps) = REGEX.captures(s) else {
|
||||
anyhow::bail!("Cannot parse mode '{s}' from string");
|
||||
};
|
||||
|
||||
let pipeline = match caps.name("pipeline") {
|
||||
Some(m) => Some(
|
||||
ModePipeline::from_str(m.as_str())
|
||||
.context("Failed to parse mode pipeline from string")?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let optimize_flag = caps.name("optimize_flag").map(|m| m.as_str() == "+");
|
||||
|
||||
let optimize_setting = match caps.name("optimize_setting") {
|
||||
Some(m) => Some(
|
||||
ModeOptimizerSetting::from_str(m.as_str())
|
||||
.context("Failed to parse optimizer setting from string")?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let version = match caps.name("version") {
|
||||
Some(m) => Some(
|
||||
semver::VersionReq::parse(m.as_str())
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"Cannot parse the version requirement '{}': {e}",
|
||||
m.as_str()
|
||||
)
|
||||
})
|
||||
.context("Failed to parse semver requirement from mode string")?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(ParsedMode {
|
||||
pipeline,
|
||||
optimize_flag,
|
||||
optimize_setting,
|
||||
version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParsedMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut has_written = false;
|
||||
|
||||
if let Some(pipeline) = self.pipeline {
|
||||
pipeline.fmt(f)?;
|
||||
if let Some(optimize_flag) = self.optimize_flag {
|
||||
f.write_str(if optimize_flag { "+" } else { "-" })?;
|
||||
}
|
||||
has_written = true;
|
||||
}
|
||||
|
||||
if let Some(optimize_setting) = self.optimize_setting {
|
||||
if has_written {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
optimize_setting.fmt(f)?;
|
||||
has_written = true;
|
||||
}
|
||||
|
||||
if let Some(version) = &self.version {
|
||||
if has_written {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
version.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParsedMode> for String {
|
||||
fn from(parsed_mode: ParsedMode) -> Self {
|
||||
parsed_mode.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ParsedMode {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
ParsedMode::from_str(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParsedMode {
|
||||
/// This takes a [`ParsedMode`] and expands it into a list of [`Mode`]s that we should try.
|
||||
pub fn to_modes(&self) -> impl Iterator<Item = Mode> {
|
||||
let pipeline_iter = self.pipeline.as_ref().map_or_else(
|
||||
|| EitherIter::A(ModePipeline::test_cases()),
|
||||
|p| EitherIter::B(std::iter::once(*p)),
|
||||
);
|
||||
|
||||
let optimize_flag_setting = self.optimize_flag.map(|flag| {
|
||||
if flag {
|
||||
ModeOptimizerSetting::M3
|
||||
} else {
|
||||
ModeOptimizerSetting::M0
|
||||
}
|
||||
});
|
||||
|
||||
let optimize_flag_iter = match optimize_flag_setting {
|
||||
Some(setting) => EitherIter::A(std::iter::once(setting)),
|
||||
None => EitherIter::B(ModeOptimizerSetting::test_cases()),
|
||||
};
|
||||
|
||||
let optimize_settings_iter = self.optimize_setting.as_ref().map_or_else(
|
||||
|| EitherIter::A(optimize_flag_iter),
|
||||
|s| EitherIter::B(std::iter::once(*s)),
|
||||
);
|
||||
|
||||
pipeline_iter.flat_map(move |pipeline| {
|
||||
optimize_settings_iter
|
||||
.clone()
|
||||
.map(move |optimize_setting| Mode {
|
||||
pipeline,
|
||||
optimize_setting,
|
||||
version: self.version.clone(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a set of [`Mode`]s that correspond to the given [`ParsedMode`]s.
|
||||
/// This avoids any duplicate entries.
|
||||
pub fn many_to_modes<'a>(
|
||||
parsed: impl Iterator<Item = &'a ParsedMode>,
|
||||
) -> impl Iterator<Item = Mode> {
|
||||
let modes: HashSet<_> = parsed.flat_map(|p| p.to_modes()).collect();
|
||||
modes.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parsed_mode_from_str() {
|
||||
let strings = vec![
|
||||
("Mz", "Mz"),
|
||||
("Y", "Y"),
|
||||
("Y+", "Y+"),
|
||||
("Y-", "Y-"),
|
||||
("E", "E"),
|
||||
("E+", "E+"),
|
||||
("E-", "E-"),
|
||||
("Y M0", "Y M0"),
|
||||
("Y M1", "Y M1"),
|
||||
("Y M2", "Y M2"),
|
||||
("Y M3", "Y M3"),
|
||||
("Y Ms", "Y Ms"),
|
||||
("Y Mz", "Y Mz"),
|
||||
("E M0", "E M0"),
|
||||
("E M1", "E M1"),
|
||||
("E M2", "E M2"),
|
||||
("E M3", "E M3"),
|
||||
("E Ms", "E Ms"),
|
||||
("E Mz", "E Mz"),
|
||||
// When stringifying semver again, 0.8.0 becomes ^0.8.0 (same meaning)
|
||||
("Y 0.8.0", "Y ^0.8.0"),
|
||||
("E+ 0.8.0", "E+ ^0.8.0"),
|
||||
("Y M3 >=0.8.0", "Y M3 >=0.8.0"),
|
||||
("E Mz <0.7.0", "E Mz <0.7.0"),
|
||||
// We can parse +- _and_ M1/M2 but the latter takes priority.
|
||||
("Y+ M1 0.8.0", "Y+ M1 ^0.8.0"),
|
||||
("E- M2 0.7.0", "E- M2 ^0.7.0"),
|
||||
// We don't see this in the wild but it is parsed.
|
||||
("<=0.8", "<=0.8"),
|
||||
];
|
||||
|
||||
for (actual, expected) in strings {
|
||||
let parsed = ParsedMode::from_str(actual)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
|
||||
assert_eq!(
|
||||
expected,
|
||||
parsed.to_string(),
|
||||
"Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parsed_mode_to_test_modes() {
|
||||
let strings = vec![
|
||||
("Mz", vec!["Y Mz", "E Mz"]),
|
||||
("Y", vec!["Y M0", "Y M3"]),
|
||||
("E", vec!["E M0", "E M3"]),
|
||||
("Y+", vec!["Y M3"]),
|
||||
("Y-", vec!["Y M0"]),
|
||||
("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]),
|
||||
(
|
||||
"<=0.8",
|
||||
vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"],
|
||||
),
|
||||
];
|
||||
|
||||
for (actual, expected) in strings {
|
||||
let parsed = ParsedMode::from_str(actual)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse mode string '{actual}'"));
|
||||
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();
|
||||
|
||||
assert_eq!(
|
||||
expected_set, actual_set,
|
||||
"Mode string '{actual}' did not expand to '{expected_set:?}': got '{actual_set:?}'"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use alloy::network::Ethereum;
|
||||
use alloy::primitives::{Address, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256};
|
||||
use alloy::providers::DynProvider;
|
||||
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
|
||||
use alloy::rpc::types::{EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest};
|
||||
use anyhow::Result;
|
||||
@@ -74,6 +76,9 @@ pub trait EthereumNode {
|
||||
+ '_,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn provider(&self)
|
||||
-> Pin<Box<dyn Future<Output = anyhow::Result<DynProvider<Ethereum>>> + '_>>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@@ -92,4 +97,16 @@ pub struct MinedBlockInformation {
|
||||
|
||||
/// The hashes of the transactions that were mined as part of the block.
|
||||
pub transaction_hashes: Vec<TxHash>,
|
||||
|
||||
/// The ref time for substrate based chains.
|
||||
pub ref_time: u128,
|
||||
|
||||
/// The max ref time for substrate based chains.
|
||||
pub max_ref_time: u64,
|
||||
|
||||
/// The proof size for substrate based chains.
|
||||
pub proof_size: u128,
|
||||
|
||||
/// The max proof size for substrate based chains.
|
||||
pub max_proof_size: u64,
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ serde_yaml_ng = { workspace = true }
|
||||
|
||||
sp-core = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
subxt = { workspace = true }
|
||||
zombienet-sdk = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -32,7 +32,7 @@ use alloy::{
|
||||
},
|
||||
};
|
||||
use anyhow::Context as _;
|
||||
use futures::{Stream, StreamExt};
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use revive_common::EVMVersion;
|
||||
use tokio::sync::OnceCell;
|
||||
use tracing::{Instrument, error, instrument};
|
||||
@@ -535,6 +535,10 @@ impl EthereumNode for GethNode {
|
||||
.as_hashes()
|
||||
.expect("Must be hashes")
|
||||
.to_vec(),
|
||||
ref_time: 0,
|
||||
max_ref_time: 0,
|
||||
proof_size: 0,
|
||||
max_proof_size: 0,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -542,6 +546,16 @@ impl EthereumNode for GethNode {
|
||||
as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>)
|
||||
})
|
||||
}
|
||||
|
||||
fn provider(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<alloy::providers::DynProvider<Ethereum>>> + '_>>
|
||||
{
|
||||
Box::pin(
|
||||
self.provider()
|
||||
.map(|provider| provider.map(|provider| provider.erased())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GethNodeResolver {
|
||||
|
||||
@@ -43,7 +43,7 @@ use alloy::{
|
||||
},
|
||||
};
|
||||
use anyhow::Context as _;
|
||||
use futures::{Stream, StreamExt};
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use revive_common::EVMVersion;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_with::serde_as;
|
||||
@@ -222,6 +222,7 @@ impl LighthouseGethNode {
|
||||
"--ws.port=8546".to_string(),
|
||||
"--ws.api=eth,net,web3,txpool,engine".to_string(),
|
||||
"--ws.origins=*".to_string(),
|
||||
"--miner.gaslimit=30000000".to_string(),
|
||||
],
|
||||
consensus_layer_extra_parameters: vec![
|
||||
"--disable-quic".to_string(),
|
||||
@@ -247,6 +248,8 @@ impl LighthouseGethNode {
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
serde_json::to_string(&map).unwrap()
|
||||
},
|
||||
gas_limit: 30_000_000,
|
||||
genesis_gaslimit: 30_000_000,
|
||||
},
|
||||
wait_for_finalization: false,
|
||||
port_publisher: Some(PortPublisherParameters {
|
||||
@@ -754,6 +757,10 @@ impl EthereumNode for LighthouseGethNode {
|
||||
.as_hashes()
|
||||
.expect("Must be hashes")
|
||||
.to_vec(),
|
||||
ref_time: 0,
|
||||
max_ref_time: 0,
|
||||
proof_size: 0,
|
||||
max_proof_size: 0,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -761,6 +768,16 @@ impl EthereumNode for LighthouseGethNode {
|
||||
as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>)
|
||||
})
|
||||
}
|
||||
|
||||
fn provider(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<alloy::providers::DynProvider<Ethereum>>> + '_>>
|
||||
{
|
||||
Box::pin(
|
||||
self.http_provider()
|
||||
.map(|provider| provider.map(|provider| provider.erased())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LighthouseGethNodeResolver<F: TxFiller<Ethereum>, P: Provider<Ethereum>> {
|
||||
@@ -1035,6 +1052,8 @@ struct NetworkParameters {
|
||||
pub num_validator_keys_per_node: u64,
|
||||
|
||||
pub genesis_delay: u64,
|
||||
pub genesis_gaslimit: u64,
|
||||
pub gas_limit: u64,
|
||||
|
||||
pub prefunded_accounts: String,
|
||||
}
|
||||
@@ -1131,6 +1150,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Ignored since they take a long time to run"]
|
||||
async fn node_mines_simple_transfer_transaction_and_returns_receipt() {
|
||||
// Arrange
|
||||
let (context, node) = new_node();
|
||||
|
||||
@@ -11,17 +11,10 @@ use std::{
|
||||
};
|
||||
|
||||
use alloy::{
|
||||
consensus::{BlockHeader, TxEnvelope},
|
||||
eips::BlockNumberOrTag,
|
||||
genesis::{Genesis, GenesisAccount},
|
||||
network::{
|
||||
Ethereum, EthereumWallet, Network, NetworkWallet, TransactionBuilder,
|
||||
TransactionBuilderError, UnbuiltTransactionError,
|
||||
},
|
||||
primitives::{
|
||||
Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, StorageKey,
|
||||
TxHash, U256,
|
||||
},
|
||||
network::{Ethereum, EthereumWallet, NetworkWallet},
|
||||
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, StorageKey, TxHash, U256},
|
||||
providers::{
|
||||
Provider,
|
||||
ext::DebugApi,
|
||||
@@ -29,24 +22,23 @@ use alloy::{
|
||||
},
|
||||
rpc::types::{
|
||||
EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest,
|
||||
eth::{Block, Header, Transaction},
|
||||
trace::geth::{
|
||||
DiffMode, GethDebugTracingOptions, GethTrace, PreStateConfig, PreStateFrame,
|
||||
},
|
||||
},
|
||||
};
|
||||
use anyhow::Context as _;
|
||||
use futures::{Stream, StreamExt};
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use revive_common::EVMVersion;
|
||||
use revive_dt_common::fs::clear_directory;
|
||||
use revive_dt_format::traits::ResolverApi;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value as JsonValue, json};
|
||||
use sp_core::crypto::Ss58Codec;
|
||||
use sp_runtime::AccountId32;
|
||||
|
||||
use revive_dt_config::*;
|
||||
use revive_dt_node_interaction::{EthereumNode, MinedBlockInformation};
|
||||
use subxt::{OnlineClient, SubstrateConfig};
|
||||
use tokio::sync::OnceCell;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -79,7 +71,8 @@ pub struct SubstrateNode {
|
||||
eth_proxy_process: Option<Process>,
|
||||
wallet: Arc<EthereumWallet>,
|
||||
nonce_manager: CachedNonceManager,
|
||||
provider: OnceCell<ConcreteProvider<ReviveNetwork, Arc<EthereumWallet>>>,
|
||||
provider: OnceCell<ConcreteProvider<Ethereum, Arc<EthereumWallet>>>,
|
||||
consensus: Option<String>,
|
||||
}
|
||||
|
||||
impl SubstrateNode {
|
||||
@@ -102,6 +95,7 @@ impl SubstrateNode {
|
||||
pub fn new(
|
||||
node_path: PathBuf,
|
||||
export_chainspec_command: &str,
|
||||
consensus: Option<String>,
|
||||
context: impl AsRef<WorkingDirectoryConfiguration>
|
||||
+ AsRef<EthRpcConfiguration>
|
||||
+ AsRef<WalletConfiguration>,
|
||||
@@ -131,6 +125,7 @@ impl SubstrateNode {
|
||||
wallet: wallet.clone(),
|
||||
nonce_manager: Default::default(),
|
||||
provider: Default::default(),
|
||||
consensus,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +223,7 @@ impl SubstrateNode {
|
||||
self.logs_directory.as_path(),
|
||||
self.node_binary.as_path(),
|
||||
|command, stdout_file, stderr_file| {
|
||||
command
|
||||
let cmd = command
|
||||
.arg("--dev")
|
||||
.arg("--chain")
|
||||
.arg(chainspec_path)
|
||||
@@ -245,9 +240,16 @@ impl SubstrateNode {
|
||||
.arg("all")
|
||||
.arg("--rpc-max-connections")
|
||||
.arg(u32::MAX.to_string())
|
||||
.arg("--pool-limit")
|
||||
.arg(u32::MAX.to_string())
|
||||
.arg("--pool-kbytes")
|
||||
.arg(u32::MAX.to_string())
|
||||
.env("RUST_LOG", Self::SUBSTRATE_LOG_ENV)
|
||||
.stdout(stdout_file)
|
||||
.stderr(stderr_file);
|
||||
if let Some(consensus) = self.consensus.as_ref() {
|
||||
cmd.arg("--consensus").arg(consensus.clone());
|
||||
}
|
||||
},
|
||||
ProcessReadinessWaitBehavior::TimeBoundedWaitFunction {
|
||||
max_wait_duration: Duration::from_secs(30),
|
||||
@@ -342,12 +344,10 @@ impl SubstrateNode {
|
||||
Ok(String::from_utf8_lossy(&output).trim().to_string())
|
||||
}
|
||||
|
||||
async fn provider(
|
||||
&self,
|
||||
) -> anyhow::Result<ConcreteProvider<ReviveNetwork, Arc<EthereumWallet>>> {
|
||||
async fn provider(&self) -> anyhow::Result<ConcreteProvider<Ethereum, Arc<EthereumWallet>>> {
|
||||
self.provider
|
||||
.get_or_try_init(|| async move {
|
||||
construct_concurrency_limited_provider::<ReviveNetwork, _>(
|
||||
construct_concurrency_limited_provider::<Ethereum, _>(
|
||||
self.rpc_url.as_str(),
|
||||
FallbackGasFiller::new(u64::MAX, 5_000_000_000, 1_000_000_000),
|
||||
ChainIdFiller::new(Some(CHAIN_ID)),
|
||||
@@ -508,44 +508,97 @@ impl EthereumNode for SubstrateNode {
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(async move {
|
||||
let provider = self
|
||||
.provider()
|
||||
.await
|
||||
.context("Failed to create the provider for block subscription")?;
|
||||
let mut block_subscription = provider
|
||||
.watch_full_blocks()
|
||||
.await
|
||||
.context("Failed to create the blocks stream")?;
|
||||
block_subscription.set_channel_size(0xFFFF);
|
||||
block_subscription.set_poll_interval(Duration::from_secs(1));
|
||||
let block_stream = block_subscription.into_stream();
|
||||
#[subxt::subxt(runtime_metadata_path = "../../assets/revive_metadata.scale")]
|
||||
pub mod revive {}
|
||||
|
||||
let mined_block_information_stream = block_stream.filter_map(|block| async {
|
||||
let block = block.ok()?;
|
||||
Some(MinedBlockInformation {
|
||||
block_number: block.number(),
|
||||
block_timestamp: block.header.timestamp,
|
||||
mined_gas: block.header.gas_used as _,
|
||||
block_gas_limit: block.header.gas_limit,
|
||||
transaction_hashes: block
|
||||
.transactions
|
||||
.into_hashes()
|
||||
.as_hashes()
|
||||
.expect("Must be hashes")
|
||||
.to_vec(),
|
||||
})
|
||||
Box::pin(async move {
|
||||
let substrate_rpc_port = Self::BASE_SUBSTRATE_RPC_PORT + self.id as u16;
|
||||
let substrate_rpc_url = format!("ws://127.0.0.1:{substrate_rpc_port}");
|
||||
let api = OnlineClient::<SubstrateConfig>::from_url(substrate_rpc_url)
|
||||
.await
|
||||
.context("Failed to create subxt rpc client")?;
|
||||
let provider = self.provider().await.context("Failed to create provider")?;
|
||||
|
||||
let block_stream = api
|
||||
.blocks()
|
||||
.subscribe_all()
|
||||
.await
|
||||
.context("Failed to subscribe to blocks")?;
|
||||
|
||||
let mined_block_information_stream = block_stream.filter_map(move |block| {
|
||||
let api = api.clone();
|
||||
let provider = provider.clone();
|
||||
|
||||
async move {
|
||||
let substrate_block = block.ok()?;
|
||||
let revive_block = provider
|
||||
.get_block_by_number(
|
||||
BlockNumberOrTag::Number(substrate_block.number() as _),
|
||||
)
|
||||
.await
|
||||
.expect("TODO: Remove")
|
||||
.expect("TODO: Remove");
|
||||
|
||||
let used = api
|
||||
.storage()
|
||||
.at(substrate_block.reference())
|
||||
.fetch_or_default(&revive::storage().system().block_weight())
|
||||
.await
|
||||
.expect("TODO: Remove");
|
||||
|
||||
let block_ref_time = (used.normal.ref_time as u128)
|
||||
+ (used.operational.ref_time as u128)
|
||||
+ (used.mandatory.ref_time as u128);
|
||||
let block_proof_size = (used.normal.proof_size as u128)
|
||||
+ (used.operational.proof_size as u128)
|
||||
+ (used.mandatory.proof_size as u128);
|
||||
|
||||
let limits = api
|
||||
.constants()
|
||||
.at(&revive::constants().system().block_weights())
|
||||
.expect("TODO: Remove");
|
||||
|
||||
let max_ref_time = limits.max_block.ref_time;
|
||||
let max_proof_size = limits.max_block.proof_size;
|
||||
|
||||
Some(MinedBlockInformation {
|
||||
block_number: substrate_block.number() as _,
|
||||
block_timestamp: revive_block.header.timestamp,
|
||||
mined_gas: revive_block.header.gas_used as _,
|
||||
block_gas_limit: revive_block.header.gas_limit as _,
|
||||
transaction_hashes: revive_block
|
||||
.transactions
|
||||
.into_hashes()
|
||||
.as_hashes()
|
||||
.expect("Must be hashes")
|
||||
.to_vec(),
|
||||
ref_time: block_ref_time,
|
||||
max_ref_time,
|
||||
proof_size: block_proof_size,
|
||||
max_proof_size,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Box::pin(mined_block_information_stream)
|
||||
as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>)
|
||||
})
|
||||
}
|
||||
|
||||
fn provider(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<alloy::providers::DynProvider<Ethereum>>> + '_>>
|
||||
{
|
||||
Box::pin(
|
||||
self.provider()
|
||||
.map(|provider| provider.map(|provider| provider.erased())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubstrateNodeResolver {
|
||||
id: u32,
|
||||
provider: ConcreteProvider<ReviveNetwork, Arc<EthereumWallet>>,
|
||||
provider: ConcreteProvider<Ethereum, Arc<EthereumWallet>>,
|
||||
}
|
||||
|
||||
impl ResolverApi for SubstrateNodeResolver {
|
||||
@@ -709,430 +762,6 @@ impl Drop for SubstrateNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ReviveNetwork;
|
||||
|
||||
impl Network for ReviveNetwork {
|
||||
type TxType = <Ethereum as Network>::TxType;
|
||||
|
||||
type TxEnvelope = <Ethereum as Network>::TxEnvelope;
|
||||
|
||||
type UnsignedTx = <Ethereum as Network>::UnsignedTx;
|
||||
|
||||
type ReceiptEnvelope = <Ethereum as Network>::ReceiptEnvelope;
|
||||
|
||||
type Header = ReviveHeader;
|
||||
|
||||
type TransactionRequest = <Ethereum as Network>::TransactionRequest;
|
||||
|
||||
type TransactionResponse = <Ethereum as Network>::TransactionResponse;
|
||||
|
||||
type ReceiptResponse = <Ethereum as Network>::ReceiptResponse;
|
||||
|
||||
type HeaderResponse = Header<ReviveHeader>;
|
||||
|
||||
type BlockResponse = Block<Transaction<TxEnvelope>, Header<ReviveHeader>>;
|
||||
}
|
||||
|
||||
impl TransactionBuilder<ReviveNetwork> for <Ethereum as Network>::TransactionRequest {
|
||||
fn chain_id(&self) -> Option<alloy::primitives::ChainId> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::chain_id(self)
|
||||
}
|
||||
|
||||
fn set_chain_id(&mut self, chain_id: alloy::primitives::ChainId) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_chain_id(
|
||||
self, chain_id,
|
||||
)
|
||||
}
|
||||
|
||||
fn nonce(&self) -> Option<u64> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::nonce(self)
|
||||
}
|
||||
|
||||
fn set_nonce(&mut self, nonce: u64) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_nonce(
|
||||
self, nonce,
|
||||
)
|
||||
}
|
||||
|
||||
fn take_nonce(&mut self) -> Option<u64> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::take_nonce(
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn input(&self) -> Option<&alloy::primitives::Bytes> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::input(self)
|
||||
}
|
||||
|
||||
fn set_input<T: Into<alloy::primitives::Bytes>>(&mut self, input: T) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_input(
|
||||
self, input,
|
||||
)
|
||||
}
|
||||
|
||||
fn from(&self) -> Option<Address> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::from(self)
|
||||
}
|
||||
|
||||
fn set_from(&mut self, from: Address) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_from(
|
||||
self, from,
|
||||
)
|
||||
}
|
||||
|
||||
fn kind(&self) -> Option<alloy::primitives::TxKind> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::kind(self)
|
||||
}
|
||||
|
||||
fn clear_kind(&mut self) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::clear_kind(
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn set_kind(&mut self, kind: alloy::primitives::TxKind) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_kind(
|
||||
self, kind,
|
||||
)
|
||||
}
|
||||
|
||||
fn value(&self) -> Option<alloy::primitives::U256> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::value(self)
|
||||
}
|
||||
|
||||
fn set_value(&mut self, value: alloy::primitives::U256) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_value(
|
||||
self, value,
|
||||
)
|
||||
}
|
||||
|
||||
fn gas_price(&self) -> Option<u128> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::gas_price(self)
|
||||
}
|
||||
|
||||
fn set_gas_price(&mut self, gas_price: u128) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_gas_price(
|
||||
self, gas_price,
|
||||
)
|
||||
}
|
||||
|
||||
fn max_fee_per_gas(&self) -> Option<u128> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::max_fee_per_gas(
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_max_fee_per_gas(
|
||||
self, max_fee_per_gas
|
||||
)
|
||||
}
|
||||
|
||||
fn max_priority_fee_per_gas(&self) -> Option<u128> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::max_priority_fee_per_gas(
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_max_priority_fee_per_gas(
|
||||
self, max_priority_fee_per_gas
|
||||
)
|
||||
}
|
||||
|
||||
fn gas_limit(&self) -> Option<u64> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::gas_limit(self)
|
||||
}
|
||||
|
||||
fn set_gas_limit(&mut self, gas_limit: u64) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_gas_limit(
|
||||
self, gas_limit,
|
||||
)
|
||||
}
|
||||
|
||||
fn access_list(&self) -> Option<&alloy::rpc::types::AccessList> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::access_list(
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn set_access_list(&mut self, access_list: alloy::rpc::types::AccessList) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::set_access_list(
|
||||
self,
|
||||
access_list,
|
||||
)
|
||||
}
|
||||
|
||||
fn complete_type(
|
||||
&self,
|
||||
ty: <ReviveNetwork as Network>::TxType,
|
||||
) -> Result<(), Vec<&'static str>> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::complete_type(
|
||||
self, ty,
|
||||
)
|
||||
}
|
||||
|
||||
fn can_submit(&self) -> bool {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::can_submit(
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn can_build(&self) -> bool {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::can_build(self)
|
||||
}
|
||||
|
||||
fn output_tx_type(&self) -> <ReviveNetwork as Network>::TxType {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::output_tx_type(
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn output_tx_type_checked(&self) -> Option<<ReviveNetwork as Network>::TxType> {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::output_tx_type_checked(
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn prep_for_submission(&mut self) {
|
||||
<<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::prep_for_submission(
|
||||
self,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_unsigned(
|
||||
self,
|
||||
) -> alloy::network::BuildResult<<ReviveNetwork as Network>::UnsignedTx, ReviveNetwork> {
|
||||
let result = <<Ethereum as Network>::TransactionRequest as TransactionBuilder<Ethereum>>::build_unsigned(
|
||||
self,
|
||||
);
|
||||
match result {
|
||||
Ok(unsigned_tx) => Ok(unsigned_tx),
|
||||
Err(UnbuiltTransactionError { request, error }) => {
|
||||
Err(UnbuiltTransactionError::<ReviveNetwork> {
|
||||
request,
|
||||
error: match error {
|
||||
TransactionBuilderError::InvalidTransactionRequest(tx_type, items) => {
|
||||
TransactionBuilderError::InvalidTransactionRequest(tx_type, items)
|
||||
}
|
||||
TransactionBuilderError::UnsupportedSignatureType => {
|
||||
TransactionBuilderError::UnsupportedSignatureType
|
||||
}
|
||||
TransactionBuilderError::Signer(error) => {
|
||||
TransactionBuilderError::Signer(error)
|
||||
}
|
||||
TransactionBuilderError::Custom(error) => {
|
||||
TransactionBuilderError::Custom(error)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn build<W: alloy::network::NetworkWallet<ReviveNetwork>>(
|
||||
self,
|
||||
wallet: &W,
|
||||
) -> Result<<ReviveNetwork as Network>::TxEnvelope, TransactionBuilderError<ReviveNetwork>>
|
||||
{
|
||||
Ok(wallet.sign_request(self).await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReviveHeader {
|
||||
/// The Keccak 256-bit hash of the parent
|
||||
/// block’s header, in its entirety; formally Hp.
|
||||
pub parent_hash: B256,
|
||||
/// The Keccak 256-bit hash of the ommers list portion of this block; formally Ho.
|
||||
#[serde(rename = "sha3Uncles", alias = "ommersHash")]
|
||||
pub ommers_hash: B256,
|
||||
/// The 160-bit address to which all fees collected from the successful mining of this block
|
||||
/// be transferred; formally Hc.
|
||||
#[serde(rename = "miner", alias = "beneficiary")]
|
||||
pub beneficiary: Address,
|
||||
/// The Keccak 256-bit hash of the root node of the state trie, after all transactions are
|
||||
/// executed and finalisations applied; formally Hr.
|
||||
pub state_root: B256,
|
||||
/// The Keccak 256-bit hash of the root node of the trie structure populated with each
|
||||
/// transaction in the transactions list portion of the block; formally Ht.
|
||||
pub transactions_root: B256,
|
||||
/// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts
|
||||
/// of each transaction in the transactions list portion of the block; formally He.
|
||||
pub receipts_root: B256,
|
||||
/// The Bloom filter composed from indexable information (logger address and log topics)
|
||||
/// contained in each log entry from the receipt of each transaction in the transactions list;
|
||||
/// formally Hb.
|
||||
pub logs_bloom: Bloom,
|
||||
/// A scalar value corresponding to the difficulty level of this block. This can be calculated
|
||||
/// from the previous block’s difficulty level and the timestamp; formally Hd.
|
||||
pub difficulty: U256,
|
||||
/// A scalar value equal to the number of ancestor blocks. The genesis block has a number of
|
||||
/// zero; formally Hi.
|
||||
#[serde(with = "alloy::serde::quantity")]
|
||||
pub number: BlockNumber,
|
||||
/// A scalar value equal to the current limit of gas expenditure per block; formally Hl.
|
||||
// This is the main difference over the Ethereum network implementation. We use u128 here and
|
||||
// not u64.
|
||||
#[serde(with = "alloy::serde::quantity")]
|
||||
pub gas_limit: u128,
|
||||
/// A scalar value equal to the total gas used in transactions in this block; formally Hg.
|
||||
#[serde(with = "alloy::serde::quantity")]
|
||||
pub gas_used: u64,
|
||||
/// A scalar value equal to the reasonable output of Unix’s time() at this block’s inception;
|
||||
/// formally Hs.
|
||||
#[serde(with = "alloy::serde::quantity")]
|
||||
pub timestamp: u64,
|
||||
/// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or
|
||||
/// fewer; formally Hx.
|
||||
pub extra_data: Bytes,
|
||||
/// A 256-bit hash which, combined with the
|
||||
/// nonce, proves that a sufficient amount of computation has been carried out on this block;
|
||||
/// formally Hm.
|
||||
pub mix_hash: B256,
|
||||
/// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of
|
||||
/// computation has been carried out on this block; formally Hn.
|
||||
pub nonce: B64,
|
||||
/// A scalar representing EIP1559 base fee which can move up or down each block according
|
||||
/// to a formula which is a function of gas used in parent block and gas target
|
||||
/// (block gas limit divided by elasticity multiplier) of parent block.
|
||||
/// The algorithm results in the base fee per gas increasing when blocks are
|
||||
/// above the gas target, and decreasing when blocks are below the gas target. The base fee per
|
||||
/// gas is burned.
|
||||
#[serde(
|
||||
default,
|
||||
with = "alloy::serde::quantity::opt",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub base_fee_per_gas: Option<u64>,
|
||||
/// The Keccak 256-bit hash of the withdrawals list portion of this block.
|
||||
/// <https://eips.ethereum.org/EIPS/eip-4895>
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub withdrawals_root: Option<B256>,
|
||||
/// The total amount of blob gas consumed by the transactions within the block, added in
|
||||
/// EIP-4844.
|
||||
#[serde(
|
||||
default,
|
||||
with = "alloy::serde::quantity::opt",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub blob_gas_used: Option<u64>,
|
||||
/// A running total of blob gas consumed in excess of the target, prior to the block. Blocks
|
||||
/// with above-target blob gas consumption increase this value, blocks with below-target blob
|
||||
/// gas consumption decrease it (bounded at 0). This was added in EIP-4844.
|
||||
#[serde(
|
||||
default,
|
||||
with = "alloy::serde::quantity::opt",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub excess_blob_gas: Option<u64>,
|
||||
/// The hash of the parent beacon block's root is included in execution blocks, as proposed by
|
||||
/// EIP-4788.
|
||||
///
|
||||
/// This enables trust-minimized access to consensus state, supporting staking pools, bridges,
|
||||
/// and more.
|
||||
///
|
||||
/// The beacon roots contract handles root storage, enhancing Ethereum's functionalities.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub parent_beacon_block_root: Option<B256>,
|
||||
/// The Keccak 256-bit hash of the an RLP encoded list with each
|
||||
/// [EIP-7685] request in the block body.
|
||||
///
|
||||
/// [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub requests_hash: Option<B256>,
|
||||
}
|
||||
|
||||
impl BlockHeader for ReviveHeader {
|
||||
fn parent_hash(&self) -> B256 {
|
||||
self.parent_hash
|
||||
}
|
||||
|
||||
fn ommers_hash(&self) -> B256 {
|
||||
self.ommers_hash
|
||||
}
|
||||
|
||||
fn beneficiary(&self) -> Address {
|
||||
self.beneficiary
|
||||
}
|
||||
|
||||
fn state_root(&self) -> B256 {
|
||||
self.state_root
|
||||
}
|
||||
|
||||
fn transactions_root(&self) -> B256 {
|
||||
self.transactions_root
|
||||
}
|
||||
|
||||
fn receipts_root(&self) -> B256 {
|
||||
self.receipts_root
|
||||
}
|
||||
|
||||
fn withdrawals_root(&self) -> Option<B256> {
|
||||
self.withdrawals_root
|
||||
}
|
||||
|
||||
fn logs_bloom(&self) -> Bloom {
|
||||
self.logs_bloom
|
||||
}
|
||||
|
||||
fn difficulty(&self) -> U256 {
|
||||
self.difficulty
|
||||
}
|
||||
|
||||
fn number(&self) -> BlockNumber {
|
||||
self.number
|
||||
}
|
||||
|
||||
// There's sadly nothing that we can do about this. We're required to implement this trait on
|
||||
// any type that represents a header and the gas limit type used here is a u64.
|
||||
fn gas_limit(&self) -> u64 {
|
||||
self.gas_limit.try_into().unwrap_or(u64::MAX)
|
||||
}
|
||||
|
||||
fn gas_used(&self) -> u64 {
|
||||
self.gas_used
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> u64 {
|
||||
self.timestamp
|
||||
}
|
||||
|
||||
fn mix_hash(&self) -> Option<B256> {
|
||||
Some(self.mix_hash)
|
||||
}
|
||||
|
||||
fn nonce(&self) -> Option<B64> {
|
||||
Some(self.nonce)
|
||||
}
|
||||
|
||||
fn base_fee_per_gas(&self) -> Option<u64> {
|
||||
self.base_fee_per_gas
|
||||
}
|
||||
|
||||
fn blob_gas_used(&self) -> Option<u64> {
|
||||
self.blob_gas_used
|
||||
}
|
||||
|
||||
fn excess_blob_gas(&self) -> Option<u64> {
|
||||
self.excess_blob_gas
|
||||
}
|
||||
|
||||
fn parent_beacon_block_root(&self) -> Option<B256> {
|
||||
self.parent_beacon_block_root
|
||||
}
|
||||
|
||||
fn requests_hash(&self) -> Option<B256> {
|
||||
self.requests_hash
|
||||
}
|
||||
|
||||
fn extra_data(&self) -> &Bytes {
|
||||
&self.extra_data
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloy::rpc::types::TransactionRequest;
|
||||
@@ -1170,6 +799,7 @@ mod tests {
|
||||
let mut node = SubstrateNode::new(
|
||||
context.kitchensink_configuration.path.clone(),
|
||||
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
|
||||
None,
|
||||
&context,
|
||||
);
|
||||
node.init(context.genesis_configuration.genesis().unwrap().clone())
|
||||
@@ -1235,6 +865,7 @@ mod tests {
|
||||
let mut dummy_node = SubstrateNode::new(
|
||||
context.kitchensink_configuration.path.clone(),
|
||||
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
|
||||
None,
|
||||
&context,
|
||||
);
|
||||
|
||||
@@ -1287,6 +918,7 @@ mod tests {
|
||||
let node = SubstrateNode::new(
|
||||
context.kitchensink_configuration.path.clone(),
|
||||
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
|
||||
None,
|
||||
&context,
|
||||
);
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ use alloy::{
|
||||
};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use futures::{Stream, StreamExt};
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use revive_common::EVMVersion;
|
||||
use revive_dt_common::fs::clear_directory;
|
||||
use revive_dt_config::*;
|
||||
@@ -64,6 +64,7 @@ use revive_dt_node_interaction::{EthereumNode, MinedBlockInformation};
|
||||
use serde_json::{Value as JsonValue, json};
|
||||
use sp_core::crypto::Ss58Codec;
|
||||
use sp_runtime::AccountId32;
|
||||
use subxt::{OnlineClient, SubstrateConfig};
|
||||
use tokio::sync::OnceCell;
|
||||
use tracing::instrument;
|
||||
use zombienet_sdk::{LocalFileSystem, NetworkConfigBuilder, NetworkConfigExt};
|
||||
@@ -72,17 +73,19 @@ use crate::{
|
||||
Node,
|
||||
constants::INITIAL_BALANCE,
|
||||
helpers::{Process, ProcessReadinessWaitBehavior},
|
||||
node_implementations::substrate::ReviveNetwork,
|
||||
provider_utils::{ConcreteProvider, FallbackGasFiller, construct_concurrency_limited_provider},
|
||||
provider_utils::{
|
||||
ConcreteProvider, FallbackGasFiller, construct_concurrency_limited_provider,
|
||||
execute_transaction,
|
||||
},
|
||||
};
|
||||
|
||||
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
/// A Zombienet network where collator is `polkadot-parachain` node with `eth-rpc`
|
||||
/// [`ZombieNode`] abstracts away the details of managing the zombienet network and provides
|
||||
/// an interface to interact with the parachain's Ethereum RPC.
|
||||
/// A Zombienet network where collator is `polkadot-parachain` node with `eth-rpc` [`ZombieNode`]
|
||||
/// abstracts away the details of managing the zombienet network and provides an interface to
|
||||
/// interact with the parachain's Ethereum RPC.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ZombieNode {
|
||||
pub struct ZombienetNode {
|
||||
/* Node Identifier */
|
||||
id: u32,
|
||||
connection_string: String,
|
||||
@@ -107,10 +110,10 @@ pub struct ZombieNode {
|
||||
wallet: Arc<EthereumWallet>,
|
||||
nonce_manager: CachedNonceManager,
|
||||
|
||||
provider: OnceCell<ConcreteProvider<ReviveNetwork, Arc<EthereumWallet>>>,
|
||||
provider: OnceCell<ConcreteProvider<Ethereum, Arc<EthereumWallet>>>,
|
||||
}
|
||||
|
||||
impl ZombieNode {
|
||||
impl ZombienetNode {
|
||||
const BASE_DIRECTORY: &str = "zombienet";
|
||||
const DATA_DIRECTORY: &str = "data";
|
||||
const LOGS_DIRECTORY: &str = "logs";
|
||||
@@ -119,6 +122,8 @@ impl ZombieNode {
|
||||
const PARACHAIN_ID: u32 = 100;
|
||||
const ETH_RPC_BASE_PORT: u16 = 8545;
|
||||
|
||||
const PROXY_LOG_ENV: &str = "info,eth-rpc=debug";
|
||||
|
||||
const ETH_RPC_READY_MARKER: &str = "Running JSON-RPC server";
|
||||
|
||||
const EXPORT_CHAINSPEC_COMMAND: &str = "build-spec";
|
||||
@@ -178,25 +183,35 @@ impl ZombieNode {
|
||||
let node_rpc_port = Self::NODE_BASE_RPC_PORT + self.id as u16;
|
||||
|
||||
let network_config = NetworkConfigBuilder::new()
|
||||
.with_relaychain(|r| {
|
||||
r.with_chain("westend-local")
|
||||
.with_relaychain(|relay_chain| {
|
||||
relay_chain
|
||||
.with_chain("westend-local")
|
||||
.with_default_command("polkadot")
|
||||
.with_node(|node| node.with_name("alice"))
|
||||
.with_node(|node| node.with_name("bob"))
|
||||
})
|
||||
.with_global_settings(|g| g.with_base_dir(&self.base_directory))
|
||||
.with_parachain(|p| {
|
||||
p.with_id(Self::PARACHAIN_ID)
|
||||
.with_chain_spec_path(template_chainspec_path.to_str().unwrap())
|
||||
.with_global_settings(|global_settings| {
|
||||
// global_settings.with_base_dir(&self.base_directory)
|
||||
global_settings
|
||||
})
|
||||
.with_parachain(|parachain| {
|
||||
parachain
|
||||
.with_id(Self::PARACHAIN_ID)
|
||||
.with_chain_spec_path(template_chainspec_path.to_path_buf())
|
||||
.with_chain("asset-hub-westend-local")
|
||||
.with_collator(|n| {
|
||||
n.with_name("Collator")
|
||||
.with_collator(|node_config| {
|
||||
node_config
|
||||
.with_name("Collator")
|
||||
.with_command(polkadot_parachain_path)
|
||||
.with_rpc_port(node_rpc_port)
|
||||
.with_args(vec![
|
||||
("--pool-limit", u32::MAX.to_string().as_str()).into(),
|
||||
("--pool-kbytes", u32::MAX.to_string().as_str()).into(),
|
||||
])
|
||||
})
|
||||
})
|
||||
.build()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to build zombienet network config: {e:?}"))?;
|
||||
.map_err(|err| anyhow::anyhow!("Failed to build zombienet network config: {err:?}"))?;
|
||||
|
||||
self.node_rpc_port = Some(node_rpc_port);
|
||||
self.network_config = Some(network_config);
|
||||
@@ -210,6 +225,9 @@ impl ZombieNode {
|
||||
.clone()
|
||||
.context("Node not initialized, call init() first")?;
|
||||
|
||||
// TODO: Look into the possibility of removing this in the future, perhaps by reintroducing
|
||||
// the blocking runtime abstraction and making it available to the entire program so that we
|
||||
// don't need to be spawning multiple different runtimes.
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let network = rt.block_on(async {
|
||||
network_config
|
||||
@@ -237,6 +255,7 @@ impl ZombieNode {
|
||||
.arg(u32::MAX.to_string())
|
||||
.arg("--rpc-port")
|
||||
.arg(eth_rpc_port.to_string())
|
||||
.env("RUST_LOG", Self::PROXY_LOG_ENV)
|
||||
.stdout(stdout_file)
|
||||
.stderr(stderr_file);
|
||||
},
|
||||
@@ -272,12 +291,13 @@ impl ZombieNode {
|
||||
template_chainspec_path: PathBuf,
|
||||
mut genesis: Genesis,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut cmd: Command = std::process::Command::new(&self.polkadot_parachain_path);
|
||||
cmd.arg(Self::EXPORT_CHAINSPEC_COMMAND)
|
||||
let output = Command::new(self.polkadot_parachain_path.as_path())
|
||||
.arg(Self::EXPORT_CHAINSPEC_COMMAND)
|
||||
.arg("--chain")
|
||||
.arg("asset-hub-westend-local");
|
||||
|
||||
let output = cmd.output().context("Failed to export the chain-spec")?;
|
||||
.arg("asset-hub-westend-local")
|
||||
.env_remove("RUST_LOG")
|
||||
.output()
|
||||
.context("Failed to export the chainspec of the chain")?;
|
||||
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
@@ -378,14 +398,12 @@ impl ZombieNode {
|
||||
Ok(String::from_utf8_lossy(&output).trim().to_string())
|
||||
}
|
||||
|
||||
async fn provider(
|
||||
&self,
|
||||
) -> anyhow::Result<ConcreteProvider<ReviveNetwork, Arc<EthereumWallet>>> {
|
||||
async fn provider(&self) -> anyhow::Result<ConcreteProvider<Ethereum, Arc<EthereumWallet>>> {
|
||||
self.provider
|
||||
.get_or_try_init(|| async move {
|
||||
construct_concurrency_limited_provider::<ReviveNetwork, _>(
|
||||
construct_concurrency_limited_provider::<Ethereum, _>(
|
||||
self.connection_string.as_str(),
|
||||
FallbackGasFiller::new(250_000_000, 5_000_000_000, 1_000_000_000),
|
||||
FallbackGasFiller::new(u64::MAX, 5_000_000_000, 1_000_000_000),
|
||||
ChainIdFiller::default(), // TODO: use CHAIN_ID constant
|
||||
NonceFiller::new(self.nonce_manager.clone()),
|
||||
self.wallet.clone(),
|
||||
@@ -398,7 +416,7 @@ impl ZombieNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl EthereumNode for ZombieNode {
|
||||
impl EthereumNode for ZombienetNode {
|
||||
fn pre_transactions(&mut self) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + '_>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
@@ -448,17 +466,11 @@ impl EthereumNode for ZombieNode {
|
||||
transaction: alloy::rpc::types::TransactionRequest,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<TransactionReceipt>> + '_>> {
|
||||
Box::pin(async move {
|
||||
let receipt = self
|
||||
let provider = self
|
||||
.provider()
|
||||
.await
|
||||
.context("Failed to create provider for transaction submission")?
|
||||
.send_transaction(transaction)
|
||||
.await
|
||||
.context("Failed to submit transaction to proxy")?
|
||||
.get_receipt()
|
||||
.await
|
||||
.context("Failed to fetch transaction receipt from proxy")?;
|
||||
Ok(receipt)
|
||||
.context("Failed to create the provider")?;
|
||||
execute_transaction(provider, transaction).await
|
||||
})
|
||||
}
|
||||
|
||||
@@ -552,49 +564,99 @@ impl EthereumNode for ZombieNode {
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(async move {
|
||||
let provider = self
|
||||
.provider()
|
||||
.await
|
||||
.context("Failed to create the provider for block subscription")?;
|
||||
let mut block_subscription = provider
|
||||
.watch_full_blocks()
|
||||
.await
|
||||
.context("Failed to create the blocks stream")?;
|
||||
block_subscription.set_channel_size(0xFFFF);
|
||||
block_subscription.set_poll_interval(Duration::from_secs(1));
|
||||
let block_stream = block_subscription.into_stream();
|
||||
#[subxt::subxt(runtime_metadata_path = "../../assets/revive_metadata.scale")]
|
||||
pub mod revive {}
|
||||
|
||||
let mined_block_information_stream = block_stream.filter_map(|block| async {
|
||||
let block = block.ok()?;
|
||||
Some(MinedBlockInformation {
|
||||
block_number: block.number(),
|
||||
block_timestamp: block.header.timestamp,
|
||||
mined_gas: block.header.gas_used as _,
|
||||
block_gas_limit: block.header.gas_limit,
|
||||
transaction_hashes: block
|
||||
.transactions
|
||||
.into_hashes()
|
||||
.as_hashes()
|
||||
.expect("Must be hashes")
|
||||
.to_vec(),
|
||||
})
|
||||
Box::pin(async move {
|
||||
let substrate_rpc_url = format!("ws://127.0.0.1:{}", self.node_rpc_port.unwrap());
|
||||
let api = OnlineClient::<SubstrateConfig>::from_url(substrate_rpc_url)
|
||||
.await
|
||||
.context("Failed to create subxt rpc client")?;
|
||||
let provider = self.provider().await.context("Failed to create provider")?;
|
||||
|
||||
let block_stream = api
|
||||
.blocks()
|
||||
.subscribe_all()
|
||||
.await
|
||||
.context("Failed to subscribe to blocks")?;
|
||||
|
||||
let mined_block_information_stream = block_stream.filter_map(move |block| {
|
||||
let api = api.clone();
|
||||
let provider = provider.clone();
|
||||
|
||||
async move {
|
||||
let substrate_block = block.ok()?;
|
||||
let revive_block = provider
|
||||
.get_block_by_number(
|
||||
BlockNumberOrTag::Number(substrate_block.number() as _),
|
||||
)
|
||||
.await
|
||||
.expect("TODO: Remove")
|
||||
.expect("TODO: Remove");
|
||||
|
||||
let used = api
|
||||
.storage()
|
||||
.at(substrate_block.reference())
|
||||
.fetch_or_default(&revive::storage().system().block_weight())
|
||||
.await
|
||||
.expect("TODO: Remove");
|
||||
|
||||
let block_ref_time = (used.normal.ref_time as u128)
|
||||
+ (used.operational.ref_time as u128)
|
||||
+ (used.mandatory.ref_time as u128);
|
||||
let block_proof_size = (used.normal.proof_size as u128)
|
||||
+ (used.operational.proof_size as u128)
|
||||
+ (used.mandatory.proof_size as u128);
|
||||
|
||||
let limits = api
|
||||
.constants()
|
||||
.at(&revive::constants().system().block_weights())
|
||||
.expect("TODO: Remove");
|
||||
|
||||
let max_ref_time = limits.max_block.ref_time;
|
||||
let max_proof_size = limits.max_block.proof_size;
|
||||
|
||||
Some(MinedBlockInformation {
|
||||
block_number: substrate_block.number() as _,
|
||||
block_timestamp: revive_block.header.timestamp,
|
||||
mined_gas: revive_block.header.gas_used as _,
|
||||
block_gas_limit: revive_block.header.gas_limit as _,
|
||||
transaction_hashes: revive_block
|
||||
.transactions
|
||||
.into_hashes()
|
||||
.as_hashes()
|
||||
.expect("Must be hashes")
|
||||
.to_vec(),
|
||||
ref_time: block_ref_time,
|
||||
max_ref_time,
|
||||
proof_size: block_proof_size,
|
||||
max_proof_size,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Box::pin(mined_block_information_stream)
|
||||
as Pin<Box<dyn Stream<Item = MinedBlockInformation>>>)
|
||||
})
|
||||
}
|
||||
|
||||
fn provider(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<alloy::providers::DynProvider<Ethereum>>> + '_>>
|
||||
{
|
||||
Box::pin(
|
||||
self.provider()
|
||||
.map(|provider| provider.map(|provider| provider.erased())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ZombieNodeResolver<F: TxFiller<ReviveNetwork>, P: Provider<ReviveNetwork>> {
|
||||
pub struct ZombieNodeResolver<F: TxFiller<Ethereum>, P: Provider<Ethereum>> {
|
||||
id: u32,
|
||||
provider: FillProvider<F, P, ReviveNetwork>,
|
||||
provider: FillProvider<F, P, Ethereum>,
|
||||
}
|
||||
|
||||
impl<F: TxFiller<ReviveNetwork>, P: Provider<ReviveNetwork>> ResolverApi
|
||||
for ZombieNodeResolver<F, P>
|
||||
{
|
||||
impl<F: TxFiller<Ethereum>, P: Provider<Ethereum>> ResolverApi for ZombieNodeResolver<F, P> {
|
||||
#[instrument(level = "info", skip_all, fields(zombie_node_id = self.id))]
|
||||
fn chain_id(
|
||||
&self,
|
||||
@@ -717,7 +779,7 @@ impl<F: TxFiller<ReviveNetwork>, P: Provider<ReviveNetwork>> ResolverApi
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for ZombieNode {
|
||||
impl Node for ZombienetNode {
|
||||
fn shutdown(&mut self) -> anyhow::Result<()> {
|
||||
// Kill the eth_rpc process
|
||||
drop(self.eth_rpc_process.take());
|
||||
@@ -762,7 +824,7 @@ impl Node for ZombieNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ZombieNode {
|
||||
impl Drop for ZombienetNode {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.shutdown();
|
||||
}
|
||||
@@ -786,9 +848,9 @@ mod tests {
|
||||
TestExecutionContext::default()
|
||||
}
|
||||
|
||||
pub async fn new_node() -> (TestExecutionContext, ZombieNode) {
|
||||
pub async fn new_node() -> (TestExecutionContext, ZombienetNode) {
|
||||
let context = test_config();
|
||||
let mut node = ZombieNode::new(
|
||||
let mut node = ZombienetNode::new(
|
||||
context.polkadot_parachain_configuration.path.clone(),
|
||||
&context,
|
||||
);
|
||||
@@ -806,8 +868,9 @@ mod tests {
|
||||
(context, node)
|
||||
}
|
||||
|
||||
pub async fn shared_state() -> &'static (TestExecutionContext, Arc<ZombieNode>) {
|
||||
static NODE: OnceCell<(TestExecutionContext, Arc<ZombieNode>)> = OnceCell::const_new();
|
||||
pub async fn shared_state() -> &'static (TestExecutionContext, Arc<ZombienetNode>) {
|
||||
static NODE: OnceCell<(TestExecutionContext, Arc<ZombienetNode>)> =
|
||||
OnceCell::const_new();
|
||||
|
||||
NODE.get_or_init(|| async {
|
||||
let (context, node) = new_node().await;
|
||||
@@ -816,13 +879,14 @@ mod tests {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn shared_node() -> &'static Arc<ZombieNode> {
|
||||
pub async fn shared_node() -> &'static Arc<ZombienetNode> {
|
||||
&shared_state().await.1
|
||||
}
|
||||
}
|
||||
use utils::{new_node, test_config};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Ignored for the time being"]
|
||||
async fn test_transfer_transaction_should_return_receipt() {
|
||||
let (ctx, node) = new_node().await;
|
||||
|
||||
@@ -856,7 +920,7 @@ mod tests {
|
||||
"#;
|
||||
|
||||
let context = test_config();
|
||||
let mut node = ZombieNode::new(
|
||||
let mut node = ZombienetNode::new(
|
||||
context.polkadot_parachain_configuration.path.clone(),
|
||||
&context,
|
||||
);
|
||||
@@ -866,17 +930,19 @@ mod tests {
|
||||
.expect("init failed");
|
||||
|
||||
// Check that the patched chainspec file was generated
|
||||
let final_chainspec_path = node.base_directory.join(ZombieNode::CHAIN_SPEC_JSON_FILE);
|
||||
let final_chainspec_path = node
|
||||
.base_directory
|
||||
.join(ZombienetNode::CHAIN_SPEC_JSON_FILE);
|
||||
assert!(final_chainspec_path.exists(), "Chainspec file should exist");
|
||||
|
||||
let contents =
|
||||
std::fs::read_to_string(&final_chainspec_path).expect("Failed to read chainspec");
|
||||
|
||||
// Validate that the Polkadot addresses derived from the Ethereum addresses are in the file
|
||||
let first_eth_addr = ZombieNode::eth_to_polkadot_address(
|
||||
let first_eth_addr = ZombienetNode::eth_to_polkadot_address(
|
||||
&"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap(),
|
||||
);
|
||||
let second_eth_addr = ZombieNode::eth_to_polkadot_address(
|
||||
let second_eth_addr = ZombienetNode::eth_to_polkadot_address(
|
||||
&"Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2".parse().unwrap(),
|
||||
);
|
||||
|
||||
@@ -904,7 +970,7 @@ mod tests {
|
||||
"#;
|
||||
|
||||
let context = test_config();
|
||||
let node = ZombieNode::new(
|
||||
let node = ZombienetNode::new(
|
||||
context.polkadot_parachain_configuration.path.clone(),
|
||||
&context,
|
||||
);
|
||||
@@ -940,7 +1006,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for eth_addr in eth_addresses {
|
||||
let ss58 = ZombieNode::eth_to_polkadot_address(ð_addr.parse().unwrap());
|
||||
let ss58 = ZombienetNode::eth_to_polkadot_address(ð_addr.parse().unwrap());
|
||||
|
||||
println!("Ethereum: {eth_addr} -> Polkadot SS58: {ss58}");
|
||||
}
|
||||
@@ -968,7 +1034,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for (eth_addr, expected_ss58) in cases {
|
||||
let result = ZombieNode::eth_to_polkadot_address(ð_addr.parse().unwrap());
|
||||
let result = ZombienetNode::eth_to_polkadot_address(ð_addr.parse().unwrap());
|
||||
assert_eq!(
|
||||
result, expected_ss58,
|
||||
"Mismatch for Ethereum address {eth_addr}"
|
||||
@@ -980,7 +1046,7 @@ mod tests {
|
||||
fn eth_rpc_version_works() {
|
||||
// Arrange
|
||||
let context = test_config();
|
||||
let node = ZombieNode::new(
|
||||
let node = ZombienetNode::new(
|
||||
context.polkadot_parachain_configuration.path.clone(),
|
||||
&context,
|
||||
);
|
||||
@@ -999,7 +1065,7 @@ mod tests {
|
||||
fn version_works() {
|
||||
// Arrange
|
||||
let context = test_config();
|
||||
let node = ZombieNode::new(
|
||||
let node = ZombienetNode::new(
|
||||
context.polkadot_parachain_configuration.path.clone(),
|
||||
&context,
|
||||
);
|
||||
|
||||
+15
-4
@@ -7,6 +7,10 @@ use alloy::{
|
||||
transports::TransportResult,
|
||||
};
|
||||
|
||||
// Percentage padding applied to estimated gas (e.g. 120 = 20% padding)
|
||||
const GAS_ESTIMATE_PADDING_NUMERATOR: u64 = 120;
|
||||
const GAS_ESTIMATE_PADDING_DENOMINATOR: u64 = 100;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FallbackGasFiller {
|
||||
inner: GasFiller,
|
||||
@@ -56,8 +60,6 @@ where
|
||||
provider: &P,
|
||||
tx: &<N as Network>::TransactionRequest,
|
||||
) -> TransportResult<Self::Fillable> {
|
||||
// Try to fetch GasFiller’s “fillable” (gas_price, base_fee, estimate_gas, …)
|
||||
// If it errors (i.e. tx would revert under eth_estimateGas), swallow it.
|
||||
match self.inner.prepare(provider, tx).await {
|
||||
Ok(fill) => Ok(Some(fill)),
|
||||
Err(_) => Ok(None),
|
||||
@@ -70,8 +72,17 @@ where
|
||||
mut tx: alloy::providers::SendableTx<N>,
|
||||
) -> TransportResult<SendableTx<N>> {
|
||||
if let Some(fill) = fillable {
|
||||
// our inner GasFiller succeeded — use it
|
||||
self.inner.fill(fill, tx).await
|
||||
let mut tx = self.inner.fill(fill, tx).await?;
|
||||
if let Some(builder) = tx.as_mut_builder() {
|
||||
if let Some(estimated) = builder.gas_limit() {
|
||||
let padded = estimated
|
||||
.checked_mul(GAS_ESTIMATE_PADDING_NUMERATOR)
|
||||
.and_then(|v| v.checked_div(GAS_ESTIMATE_PADDING_DENOMINATOR))
|
||||
.unwrap_or(u64::MAX);
|
||||
builder.set_gas_limit(padded);
|
||||
}
|
||||
}
|
||||
Ok(tx)
|
||||
} else {
|
||||
if let Some(builder) = tx.as_mut_builder() {
|
||||
builder.set_gas_limit(self.default_gas_limit);
|
||||
@@ -1,7 +1,7 @@
|
||||
mod concurrency_limiter;
|
||||
mod fallback_gas_provider;
|
||||
mod fallback_gas_filler;
|
||||
mod provider;
|
||||
|
||||
pub use concurrency_limiter::*;
|
||||
pub use fallback_gas_provider::*;
|
||||
pub use fallback_gas_filler::*;
|
||||
pub use provider::*;
|
||||
|
||||
@@ -10,7 +10,7 @@ use alloy::{
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use revive_dt_common::futures::{PollingWaitBehavior, poll};
|
||||
use tracing::debug;
|
||||
use tracing::{Instrument, debug, info, info_span};
|
||||
|
||||
use crate::provider_utils::{ConcurrencyLimiterLayer, FallbackGasFiller};
|
||||
|
||||
@@ -44,7 +44,7 @@ where
|
||||
// requests at any point of time and no more than that. This is done in an effort to stabilize
|
||||
// the framework from some of the interment issues that we've been seeing related to RPC calls.
|
||||
static GLOBAL_CONCURRENCY_LIMITER_LAYER: LazyLock<ConcurrencyLimiterLayer> =
|
||||
LazyLock::new(|| ConcurrencyLimiterLayer::new(10));
|
||||
LazyLock::new(|| ConcurrencyLimiterLayer::new(1000));
|
||||
|
||||
let client = ClientBuilder::default()
|
||||
.layer(GLOBAL_CONCURRENCY_LIMITER_LAYER.clone())
|
||||
@@ -117,12 +117,16 @@ where
|
||||
|
||||
async move {
|
||||
match provider.get_transaction_receipt(tx_hash).await {
|
||||
Ok(Some(receipt)) => Ok(ControlFlow::Break(receipt)),
|
||||
Ok(Some(receipt)) => {
|
||||
info!("Found the transaction receipt");
|
||||
Ok(ControlFlow::Break(receipt))
|
||||
}
|
||||
_ => Ok(ControlFlow::Continue(())),
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.instrument(info_span!("Polling for receipt", %tx_hash))
|
||||
.await
|
||||
.context(format!("Polling for receipt failed for {tx_hash}"))
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use revive_dt_compiler::{CompilerInput, CompilerOutput, Mode};
|
||||
use revive_dt_config::Context;
|
||||
use revive_dt_format::{case::CaseIdx, corpus::Corpus, metadata::ContractInstance};
|
||||
use semver::Version;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DisplayFromStr, serde_as};
|
||||
use tokio::sync::{
|
||||
broadcast::{Sender, channel},
|
||||
@@ -415,7 +415,7 @@ impl ReportAggregator {
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Report {
|
||||
/// The context that the tool was started up with.
|
||||
pub context: Context,
|
||||
@@ -440,7 +440,7 @@ impl Report {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Default)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct TestCaseReport {
|
||||
/// Information on the status of the test case and whether it succeeded, failed, or was ignored.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -451,7 +451,7 @@ pub struct TestCaseReport {
|
||||
|
||||
/// Information related to the status of the test. Could be that the test succeeded, failed, or that
|
||||
/// it was ignored.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "status")]
|
||||
pub enum TestCaseStatus {
|
||||
/// The test case succeeded.
|
||||
@@ -475,7 +475,7 @@ pub enum TestCaseStatus {
|
||||
}
|
||||
|
||||
/// Information related to the platform node that's being used to execute the step.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct TestCaseNodeInformation {
|
||||
/// The ID of the node that this case is being executed on.
|
||||
pub id: usize,
|
||||
@@ -486,7 +486,7 @@ pub struct TestCaseNodeInformation {
|
||||
}
|
||||
|
||||
/// Execution information tied to the platform.
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ExecutionInformation {
|
||||
/// Information related to the node assigned to this test case.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -506,7 +506,7 @@ pub struct ExecutionInformation {
|
||||
}
|
||||
|
||||
/// Information related to compilation
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "status")]
|
||||
pub enum CompilationStatus {
|
||||
/// The compilation was successful.
|
||||
|
||||
+4
-4
@@ -76,7 +76,7 @@ cat > "$CORPUS_FILE" << EOF
|
||||
{
|
||||
"name": "MatterLabs Solidity Simple, Complex, and Semantic Tests",
|
||||
"paths": [
|
||||
"$(realpath "$TEST_REPO_DIR/fixtures/solidity/simple")"
|
||||
"$(realpath "$TEST_REPO_DIR/fixtures/solidity")"
|
||||
]
|
||||
}
|
||||
EOF
|
||||
@@ -93,17 +93,17 @@ echo ""
|
||||
# Run the tool
|
||||
cargo build --release;
|
||||
RUST_LOG="info,alloy_pubsub::service=error" ./target/release/retester test \
|
||||
--platform revive-dev-node-revm-solc \
|
||||
--platform revive-dev-node-polkavm-resolc \
|
||||
--corpus "$CORPUS_FILE" \
|
||||
--working-directory "$WORKDIR" \
|
||||
--concurrency.number-of-nodes 10 \
|
||||
--concurrency.number-of-threads 5 \
|
||||
--concurrency.number-of-concurrent-tasks 1000 \
|
||||
--concurrency.number-of-concurrent-tasks 500 \
|
||||
--wallet.additional-keys 100000 \
|
||||
--kitchensink.path "$SUBSTRATE_NODE_BIN" \
|
||||
--revive-dev-node.path "$REVIVE_DEV_NODE_BIN" \
|
||||
--eth-rpc.path "$ETH_RPC_BIN" \
|
||||
> logs.log \
|
||||
2> output.log
|
||||
2> output.log
|
||||
|
||||
echo -e "${GREEN}=== Test run completed! ===${NC}"
|
||||
Reference in New Issue
Block a user