Replace infra with the dyn infra

This commit is contained in:
Omar Abdulla
2025-09-18 19:59:52 +03:00
parent 92fc7894c0
commit 496bc9a0ec
21 changed files with 556 additions and 693 deletions
Generated
+4
View File
@@ -4471,6 +4471,7 @@ dependencies = [
"clap", "clap",
"moka", "moka",
"once_cell", "once_cell",
"schemars 1.0.4",
"semver 1.0.26", "semver 1.0.26",
"serde", "serde",
"strum", "strum",
@@ -4505,6 +4506,7 @@ dependencies = [
"alloy", "alloy",
"anyhow", "anyhow",
"clap", "clap",
"revive-dt-common",
"semver 1.0.26", "semver 1.0.26",
"serde", "serde",
"serde_json", "serde_json",
@@ -4586,6 +4588,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"alloy", "alloy",
"anyhow", "anyhow",
"revive-common",
"revive-dt-common",
"revive-dt-format", "revive-dt-format",
] ]
+1
View File
@@ -15,6 +15,7 @@ moka = { workspace = true, features = ["sync"] }
once_cell = { workspace = true } once_cell = { workspace = true }
semver = { workspace = true } semver = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
schemars = { workspace = true }
strum = { workspace = true } strum = { workspace = true }
tokio = { workspace = true, default-features = false, features = ["time"] } tokio = { workspace = true, default-features = false, features = ["time"] }
+10 -7
View File
@@ -1,4 +1,5 @@
use clap::ValueEnum; use clap::ValueEnum;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::{AsRefStr, Display, EnumString, IntoStaticStr}; use strum::{AsRefStr, Display, EnumString, IntoStaticStr};
@@ -19,6 +20,7 @@ use strum::{AsRefStr, Display, EnumString, IntoStaticStr};
Display, Display,
AsRefStr, AsRefStr,
IntoStaticStr, IntoStaticStr,
JsonSchema,
)] )]
#[serde(rename = "kebab-case")] #[serde(rename = "kebab-case")]
#[strum(serialize_all = "kebab-case")] #[strum(serialize_all = "kebab-case")]
@@ -52,9 +54,8 @@ pub enum PlatformIdentifier {
Display, Display,
AsRefStr, AsRefStr,
IntoStaticStr, IntoStaticStr,
JsonSchema,
)] )]
#[serde(rename = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum CompilerIdentifier { pub enum CompilerIdentifier {
/// The solc compiler. /// The solc compiler.
Solc, Solc,
@@ -79,9 +80,8 @@ pub enum CompilerIdentifier {
Display, Display,
AsRefStr, AsRefStr,
IntoStaticStr, IntoStaticStr,
JsonSchema,
)] )]
#[serde(rename = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum NodeIdentifier { pub enum NodeIdentifier {
/// The go-ethereum node implementation. /// The go-ethereum node implementation.
Geth, Geth,
@@ -108,12 +108,15 @@ pub enum NodeIdentifier {
Display, Display,
AsRefStr, AsRefStr,
IntoStaticStr, IntoStaticStr,
JsonSchema,
)] )]
#[serde(rename = "kebab-case")] #[serde(rename = "lowercase")]
#[strum(serialize_all = "kebab-case")] #[strum(serialize_all = "lowercase")]
pub enum VmIdentifier { pub enum VmIdentifier {
/// The ethereum virtual machine. /// The ethereum virtual machine.
Evm, Evm,
/// The EraVM virtual machine.
EraVM,
/// Polkadot's PolaVM Risc-v based virtual machine. /// Polkadot's PolaVM Risc-v based virtual machine.
Polkavm, PolkaVM,
} }
+2
View File
@@ -9,6 +9,8 @@ repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
[dependencies] [dependencies]
revive-dt-common = { workspace = true }
alloy = { workspace = true } alloy = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
clap = { workspace = true } clap = { workspace = true }
+4 -7
View File
@@ -18,6 +18,7 @@ use alloy::{
signers::local::PrivateKeySigner, signers::local::PrivateKeySigner,
}; };
use clap::{Parser, ValueEnum, ValueHint}; use clap::{Parser, ValueEnum, ValueHint};
use revive_dt_common::types::PlatformIdentifier;
use semver::Version; use semver::Version;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use strum::{AsRefStr, Display, EnumString, IntoStaticStr}; use strum::{AsRefStr, Display, EnumString, IntoStaticStr};
@@ -165,13 +166,9 @@ pub struct ExecutionContext {
)] )]
pub working_directory: WorkingDirectoryConfiguration, pub working_directory: WorkingDirectoryConfiguration,
/// The differential testing leader node implementation. /// The set of platforms that the differential tests should run on.
#[arg(short, long = "leader", default_value_t = TestingPlatform::Geth)] #[arg(short = 'p', long = "platform")]
pub leader: TestingPlatform, pub platforms: Vec<PlatformIdentifier>,
/// The differential testing follower node implementation.
#[arg(short, long = "follower", default_value_t = TestingPlatform::Kitchensink)]
pub follower: TestingPlatform,
/// A list of test corpus JSON files to be tested. /// A list of test corpus JSON files to be tested.
#[arg(long = "corpus", short)] #[arg(long = "corpus", short)]
+15 -17
View File
@@ -9,9 +9,9 @@ use std::{
}; };
use futures::FutureExt; use futures::FutureExt;
use revive_dt_common::iterators::FilesWithExtensionIterator; use revive_dt_common::{iterators::FilesWithExtensionIterator, types::CompilerIdentifier};
use revive_dt_compiler::{Compiler, CompilerOutput, Mode, SolidityCompiler}; use revive_dt_compiler::{Compiler, CompilerOutput, DynSolidityCompiler, Mode};
use revive_dt_config::TestingPlatform; use revive_dt_core::DynPlatform;
use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata}; use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata};
use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address}; use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address};
@@ -22,8 +22,6 @@ use serde::{Deserialize, Serialize};
use tokio::sync::{Mutex, RwLock}; use tokio::sync::{Mutex, RwLock};
use tracing::{Instrument, debug, debug_span, instrument}; use tracing::{Instrument, debug, debug_span, instrument};
use crate::Platform;
pub struct CachedCompiler<'a> { pub struct CachedCompiler<'a> {
/// The cache that stores the compiled contracts. /// The cache that stores the compiled contracts.
artifacts_cache: ArtifactsCache, artifacts_cache: ArtifactsCache,
@@ -57,21 +55,22 @@ impl<'a> CachedCompiler<'a> {
fields( fields(
metadata_file_path = %metadata_file_path.display(), metadata_file_path = %metadata_file_path.display(),
%mode, %mode,
platform = P::config_id().to_string() platform = %platform.platform_identifier()
), ),
err err
)] )]
pub async fn compile_contracts<P: Platform>( pub async fn compile_contracts(
&self, &self,
metadata: &'a Metadata, metadata: &'a Metadata,
metadata_file_path: &'a Path, metadata_file_path: &'a Path,
mode: Cow<'a, Mode>, mode: Cow<'a, Mode>,
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>, deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
compiler: &P::Compiler, compiler: &dyn DynSolidityCompiler,
platform: &dyn DynPlatform,
reporter: &ExecutionSpecificReporter, reporter: &ExecutionSpecificReporter,
) -> Result<CompilerOutput> { ) -> Result<CompilerOutput> {
let cache_key = CacheKey { let cache_key = CacheKey {
platform_key: P::config_id(), compiler_identifier: platform.compiler_identifier(),
compiler_version: compiler.version().clone(), compiler_version: compiler.version().clone(),
metadata_file_path, metadata_file_path,
solc_mode: mode.clone(), solc_mode: mode.clone(),
@@ -79,7 +78,7 @@ impl<'a> CachedCompiler<'a> {
let compilation_callback = || { let compilation_callback = || {
async move { async move {
compile_contracts::<P>( compile_contracts(
metadata metadata
.directory() .directory()
.context("Failed to get metadata directory while preparing compilation")?, .context("Failed to get metadata directory while preparing compilation")?,
@@ -96,7 +95,7 @@ impl<'a> CachedCompiler<'a> {
} }
.instrument(debug_span!( .instrument(debug_span!(
"Running compilation for the cache key", "Running compilation for the cache key",
cache_key.platform_key = %cache_key.platform_key, cache_key.compiler_identifier = %cache_key.compiler_identifier,
cache_key.compiler_version = %cache_key.compiler_version, cache_key.compiler_version = %cache_key.compiler_version,
cache_key.metadata_file_path = %cache_key.metadata_file_path.display(), cache_key.metadata_file_path = %cache_key.metadata_file_path.display(),
cache_key.solc_mode = %cache_key.solc_mode, cache_key.solc_mode = %cache_key.solc_mode,
@@ -179,12 +178,12 @@ impl<'a> CachedCompiler<'a> {
} }
} }
async fn compile_contracts<P: Platform>( async fn compile_contracts(
metadata_directory: impl AsRef<Path>, metadata_directory: impl AsRef<Path>,
mut files_to_compile: impl Iterator<Item = PathBuf>, mut files_to_compile: impl Iterator<Item = PathBuf>,
mode: &Mode, mode: &Mode,
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>, deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
compiler: &P::Compiler, compiler: &dyn DynSolidityCompiler,
reporter: &ExecutionSpecificReporter, reporter: &ExecutionSpecificReporter,
) -> Result<CompilerOutput> { ) -> Result<CompilerOutput> {
let all_sources_in_dir = FilesWithExtensionIterator::new(metadata_directory.as_ref()) let all_sources_in_dir = FilesWithExtensionIterator::new(metadata_directory.as_ref())
@@ -218,7 +217,7 @@ async fn compile_contracts<P: Platform>(
}); });
let input = compilation.input().clone(); let input = compilation.input().clone();
let output = compilation.try_build(compiler).await; let output = compilation.dyn_try_build(compiler).await;
match (output.as_ref(), deployed_libraries.is_some()) { match (output.as_ref(), deployed_libraries.is_some()) {
(Ok(output), true) => { (Ok(output), true) => {
@@ -332,9 +331,8 @@ impl ArtifactsCache {
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
struct CacheKey<'a> { struct CacheKey<'a> {
/// The platform name that this artifact was compiled for. For example, this could be EVM or /// The identifier of the used compiler.
/// PVM. compiler_identifier: CompilerIdentifier,
platform_key: &'a TestingPlatform,
/// The version of the compiler that was used to compile the artifacts. /// The version of the compiler that was used to compile the artifacts.
compiler_version: Version, compiler_version: Version,
+77 -89
View File
@@ -1,7 +1,6 @@
//! The test driver handles the compilation and execution of the test cases. //! The test driver handles the compilation and execution of the test cases.
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData;
use std::path::PathBuf; use std::path::PathBuf;
use alloy::consensus::EMPTY_ROOT_HASH; use alloy::consensus::EMPTY_ROOT_HASH;
@@ -19,8 +18,9 @@ use alloy::{
rpc::types::{TransactionRequest, trace::geth::DiffMode}, rpc::types::{TransactionRequest, trace::geth::DiffMode},
}; };
use anyhow::Context as _; use anyhow::Context as _;
use futures::TryStreamExt; use futures::{TryStreamExt, future::try_join_all};
use indexmap::IndexMap; use indexmap::IndexMap;
use revive_dt_common::types::PlatformIdentifier;
use revive_dt_format::traits::{ResolutionContext, ResolverApi}; use revive_dt_format::traits::{ResolutionContext, ResolverApi};
use revive_dt_report::ExecutionSpecificReporter; use revive_dt_report::ExecutionSpecificReporter;
use semver::Version; use semver::Version;
@@ -36,9 +36,7 @@ use revive_dt_node_interaction::EthereumNode;
use tokio::try_join; use tokio::try_join;
use tracing::{Instrument, info, info_span, instrument}; use tracing::{Instrument, info, info_span, instrument};
use crate::Platform; pub struct CaseState {
pub struct CaseState<T: Platform> {
/// A map of all of the compiled contracts for the given metadata file. /// A map of all of the compiled contracts for the given metadata file.
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>, compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
@@ -54,14 +52,9 @@ pub struct CaseState<T: Platform> {
/// The execution reporter. /// The execution reporter.
execution_reporter: ExecutionSpecificReporter, execution_reporter: ExecutionSpecificReporter,
phantom: PhantomData<T>,
} }
impl<T> CaseState<T> impl CaseState {
where
T: Platform,
{
pub fn new( pub fn new(
compiler_version: Version, compiler_version: Version,
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>, compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
@@ -74,7 +67,6 @@ where
variables: Default::default(), variables: Default::default(),
compiler_version, compiler_version,
execution_reporter, execution_reporter,
phantom: PhantomData,
} }
} }
@@ -82,7 +74,7 @@ where
&mut self, &mut self,
metadata: &Metadata, metadata: &Metadata,
step: &Step, step: &Step,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<StepOutput> { ) -> anyhow::Result<StepOutput> {
match step { match step {
Step::FunctionCall(input) => { Step::FunctionCall(input) => {
@@ -113,8 +105,10 @@ where
&mut self, &mut self,
metadata: &Metadata, metadata: &Metadata,
input: &Input, input: &Input,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> { ) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
let resolver = node.resolver().await?;
let deployment_receipts = self let deployment_receipts = self
.handle_input_contract_deployment(metadata, input, node) .handle_input_contract_deployment(metadata, input, node)
.await .await
@@ -130,7 +124,12 @@ where
self.handle_input_variable_assignment(input, &tracing_result) self.handle_input_variable_assignment(input, &tracing_result)
.context("Failed to assign variables from callframe output")?; .context("Failed to assign variables from callframe output")?;
let (_, (geth_trace, diff_mode)) = try_join!( let (_, (geth_trace, diff_mode)) = try_join!(
self.handle_input_expectations(input, &execution_receipt, node, &tracing_result), self.handle_input_expectations(
input,
&execution_receipt,
resolver.as_ref(),
&tracing_result
),
self.handle_input_diff(execution_receipt.transaction_hash, node) self.handle_input_diff(execution_receipt.transaction_hash, node)
) )
.context("Failed while evaluating expectations and diffs in parallel")?; .context("Failed while evaluating expectations and diffs in parallel")?;
@@ -142,7 +141,7 @@ where
&mut self, &mut self,
metadata: &Metadata, metadata: &Metadata,
balance_assertion: &BalanceAssertion, balance_assertion: &BalanceAssertion,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node) self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node)
.await .await
@@ -158,7 +157,7 @@ where
&mut self, &mut self,
metadata: &Metadata, metadata: &Metadata,
storage_empty: &StorageEmptyAssertion, storage_empty: &StorageEmptyAssertion,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
self.handle_storage_empty_assertion_contract_deployment(metadata, storage_empty, node) self.handle_storage_empty_assertion_contract_deployment(metadata, storage_empty, node)
.await .await
@@ -175,7 +174,7 @@ where
&mut self, &mut self,
metadata: &Metadata, metadata: &Metadata,
input: &Input, input: &Input,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> { ) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new(); let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
for instance in input.find_all_contract_instances().into_iter() { for instance in input.find_all_contract_instances().into_iter() {
@@ -220,7 +219,7 @@ where
&mut self, &mut self,
input: &Input, input: &Input,
mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>, mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<TransactionReceipt> { ) -> anyhow::Result<TransactionReceipt> {
match input.method { match input.method {
// This input was already executed when `handle_input` was called. We just need to // This input was already executed when `handle_input` was called. We just need to
@@ -229,8 +228,9 @@ where
.remove(&input.instance) .remove(&input.instance)
.context("Failed to find deployment receipt for constructor call"), .context("Failed to find deployment receipt for constructor call"),
Method::Fallback | Method::FunctionName(_) => { Method::Fallback | Method::FunctionName(_) => {
let resolver = node.resolver().await?;
let tx = match input let tx = match input
.legacy_transaction(node, self.default_resolution_context()) .legacy_transaction(resolver.as_ref(), self.default_resolution_context())
.await .await
{ {
Ok(tx) => tx, Ok(tx) => tx,
@@ -251,7 +251,7 @@ where
async fn handle_input_call_frame_tracing( async fn handle_input_call_frame_tracing(
&self, &self,
tx_hash: TxHash, tx_hash: TxHash,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<CallFrame> { ) -> anyhow::Result<CallFrame> {
node.trace_transaction( node.trace_transaction(
tx_hash, tx_hash,
@@ -314,7 +314,7 @@ where
&self, &self,
input: &Input, input: &Input,
execution_receipt: &TransactionReceipt, execution_receipt: &TransactionReceipt,
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
tracing_result: &CallFrame, tracing_result: &CallFrame,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
// Resolving the `input.expected` into a series of expectations that we can then assert on. // Resolving the `input.expected` into a series of expectations that we can then assert on.
@@ -362,7 +362,7 @@ where
async fn handle_input_expectation_item( async fn handle_input_expectation_item(
&self, &self,
execution_receipt: &TransactionReceipt, execution_receipt: &TransactionReceipt,
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
expectation: ExpectedOutput, expectation: ExpectedOutput,
tracing_result: &CallFrame, tracing_result: &CallFrame,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@@ -508,7 +508,7 @@ where
async fn handle_input_diff( async fn handle_input_diff(
&self, &self,
tx_hash: TxHash, tx_hash: TxHash,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<(GethTrace, DiffMode)> { ) -> anyhow::Result<(GethTrace, DiffMode)> {
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig { let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
diff_mode: Some(true), diff_mode: Some(true),
@@ -533,7 +533,7 @@ where
&mut self, &mut self,
metadata: &Metadata, metadata: &Metadata,
balance_assertion: &BalanceAssertion, balance_assertion: &BalanceAssertion,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let Some(instance) = balance_assertion let Some(instance) = balance_assertion
.address .address
@@ -562,11 +562,12 @@ where
expected_balance: amount, expected_balance: amount,
.. ..
}: &BalanceAssertion, }: &BalanceAssertion,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let resolver = node.resolver().await?;
let address = Address::from_slice( let address = Address::from_slice(
Calldata::new_compound([address_string]) Calldata::new_compound([address_string])
.calldata(node, self.default_resolution_context()) .calldata(resolver.as_ref(), self.default_resolution_context())
.await? .await?
.get(12..32) .get(12..32)
.expect("Can't fail"), .expect("Can't fail"),
@@ -595,7 +596,7 @@ where
&mut self, &mut self,
metadata: &Metadata, metadata: &Metadata,
storage_empty_assertion: &StorageEmptyAssertion, storage_empty_assertion: &StorageEmptyAssertion,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let Some(instance) = storage_empty_assertion let Some(instance) = storage_empty_assertion
.address .address
@@ -624,11 +625,12 @@ where
is_storage_empty, is_storage_empty,
.. ..
}: &StorageEmptyAssertion, }: &StorageEmptyAssertion,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let resolver = node.resolver().await?;
let address = Address::from_slice( let address = Address::from_slice(
Calldata::new_compound([address_string]) Calldata::new_compound([address_string])
.calldata(node, self.default_resolution_context()) .calldata(resolver.as_ref(), self.default_resolution_context())
.await? .await?
.get(12..32) .get(12..32)
.expect("Can't fail"), .expect("Can't fail"),
@@ -667,7 +669,7 @@ where
deployer: Address, deployer: Address,
calldata: Option<&Calldata>, calldata: Option<&Calldata>,
value: Option<EtherValue>, value: Option<EtherValue>,
node: &T::Blockchain, node: &dyn EthereumNode,
) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> { ) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
if let Some((_, address, abi)) = self.deployed_contracts.get(contract_instance) { if let Some((_, address, abi)) = self.deployed_contracts.get(contract_instance) {
return Ok((*address, abi.clone(), None)); return Ok((*address, abi.clone(), None));
@@ -710,8 +712,9 @@ where
}; };
if let Some(calldata) = calldata { if let Some(calldata) = calldata {
let resolver = node.resolver().await?;
let calldata = calldata let calldata = calldata
.calldata(node, self.default_resolution_context()) .calldata(resolver.as_ref(), self.default_resolution_context())
.await?; .await?;
code.extend(calldata); code.extend(calldata);
} }
@@ -728,11 +731,7 @@ where
let receipt = match node.execute_transaction(tx).await { let receipt = match node.execute_transaction(tx).await {
Ok(receipt) => receipt, Ok(receipt) => receipt,
Err(error) => { Err(error) => {
tracing::error!( tracing::error!(?error, "Contract deployment transaction failed.");
node = std::any::type_name::<T>(),
?error,
"Contract deployment transaction failed."
);
return Err(error); return Err(error);
} }
}; };
@@ -763,36 +762,23 @@ where
} }
} }
pub struct CaseDriver<'a, Leader: Platform, Follower: Platform> { pub struct CaseDriver<'a> {
metadata: &'a Metadata, metadata: &'a Metadata,
case: &'a Case, case: &'a Case,
leader_node: &'a Leader::Blockchain, platform_state: Vec<(&'a dyn EthereumNode, PlatformIdentifier, CaseState)>,
follower_node: &'a Follower::Blockchain,
leader_state: CaseState<Leader>,
follower_state: CaseState<Follower>,
} }
impl<'a, L, F> CaseDriver<'a, L, F> impl<'a> CaseDriver<'a> {
where
L: Platform,
F: Platform,
{
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
metadata: &'a Metadata, metadata: &'a Metadata,
case: &'a Case, case: &'a Case,
leader_node: &'a L::Blockchain, platform_state: Vec<(&'a dyn EthereumNode, PlatformIdentifier, CaseState)>,
follower_node: &'a F::Blockchain, ) -> CaseDriver<'a> {
leader_state: CaseState<L>,
follower_state: CaseState<F>,
) -> CaseDriver<'a, L, F> {
Self { Self {
metadata, metadata,
case, case,
leader_node, platform_state,
follower_node,
leader_state,
follower_state,
} }
} }
@@ -805,42 +791,44 @@ where
.enumerate() .enumerate()
.map(|(idx, v)| (StepIdx::new(idx), v)) .map(|(idx, v)| (StepIdx::new(idx), v))
{ {
let (leader_step_output, follower_step_output) = try_join!( // Run this step concurrently across all platforms; short-circuit on first failure
self.leader_state let metadata = self.metadata;
.handle_step(self.metadata, &step, self.leader_node) let step_futs =
.instrument(info_span!( self.platform_state
"Handling Step", .iter_mut()
%step_idx, .map(|(node, platform_id, case_state)| {
target = "Leader", let platform_id = *platform_id;
)), let node_ref = *node;
self.follower_state let step_clone = step.clone();
.handle_step(self.metadata, &step, self.follower_node) let span = info_span!(
.instrument(info_span!( "Handling Step",
"Handling Step", %step_idx,
%step_idx, platform = %platform_id,
target = "Follower", );
)) async move {
)?; case_state
.handle_step(metadata, &step_clone, node_ref)
.await
.map_err(|e| (platform_id, e))
}
.instrument(span)
});
match (leader_step_output, follower_step_output) { match try_join_all(step_futs).await {
(StepOutput::FunctionCall(..), StepOutput::FunctionCall(..)) => { Ok(_outputs) => {
// TODO: We need to actually work out how/if we will compare the diff between // All platforms succeeded for this step
// the leader and the follower. The diffs are almost guaranteed to be different steps_executed += 1;
// from leader and follower and therefore without an actual strategy for this }
// we have something that's guaranteed to fail. Even a simple call to some Err((platform_id, error)) => {
// contract will produce two non-equal diffs because on the leader the contract tracing::error!(
// has address X and on the follower it has address Y. On the leader contract X %step_idx,
// contains address A in the state and on the follower it contains address B. So platform = %platform_id,
// this isn't exactly a straightforward thing to do and I'm not even sure that ?error,
// it's possible to do. Once we have an actual strategy for doing the diffs we "Step failed on platform",
// will implement it here. Until then, this remains empty. );
return Err(error);
} }
(StepOutput::BalanceAssertion, StepOutput::BalanceAssertion) => {}
(StepOutput::StorageEmptyAssertion, StepOutput::StorageEmptyAssertion) => {}
_ => unreachable!("The two step outputs can not be of a different kind"),
} }
steps_executed += 1;
} }
Ok(steps_executed) Ok(steps_executed)
+27 -2
View File
@@ -103,6 +103,7 @@ pub trait DynPlatform {
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn DynSolidityCompiler>>>>>; ) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn DynSolidityCompiler>>>>>;
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct GethEvmSolcPlatform; pub struct GethEvmSolcPlatform;
impl DynPlatform for GethEvmSolcPlatform { impl DynPlatform for GethEvmSolcPlatform {
@@ -147,6 +148,7 @@ impl DynPlatform for GethEvmSolcPlatform {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct KitchensinkPolkavmResolcPlatform; pub struct KitchensinkPolkavmResolcPlatform;
impl DynPlatform for KitchensinkPolkavmResolcPlatform { impl DynPlatform for KitchensinkPolkavmResolcPlatform {
@@ -159,7 +161,7 @@ impl DynPlatform for KitchensinkPolkavmResolcPlatform {
} }
fn vm_identifier(&self) -> VmIdentifier { fn vm_identifier(&self) -> VmIdentifier {
VmIdentifier::Polkavm VmIdentifier::PolkaVM
} }
fn compiler_identifier(&self) -> CompilerIdentifier { fn compiler_identifier(&self) -> CompilerIdentifier {
@@ -198,6 +200,7 @@ impl DynPlatform for KitchensinkPolkavmResolcPlatform {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct KitchensinkRevmSolcPlatform; pub struct KitchensinkRevmSolcPlatform;
impl DynPlatform for KitchensinkRevmSolcPlatform { impl DynPlatform for KitchensinkRevmSolcPlatform {
@@ -249,6 +252,7 @@ impl DynPlatform for KitchensinkRevmSolcPlatform {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct ReviveDevNodePolkavmResolcPlatform; pub struct ReviveDevNodePolkavmResolcPlatform;
impl DynPlatform for ReviveDevNodePolkavmResolcPlatform { impl DynPlatform for ReviveDevNodePolkavmResolcPlatform {
@@ -261,7 +265,7 @@ impl DynPlatform for ReviveDevNodePolkavmResolcPlatform {
} }
fn vm_identifier(&self) -> VmIdentifier { fn vm_identifier(&self) -> VmIdentifier {
VmIdentifier::Polkavm VmIdentifier::PolkaVM
} }
fn compiler_identifier(&self) -> CompilerIdentifier { fn compiler_identifier(&self) -> CompilerIdentifier {
@@ -300,6 +304,7 @@ impl DynPlatform for ReviveDevNodePolkavmResolcPlatform {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct ReviveDevNodeRevmSolcPlatform; pub struct ReviveDevNodeRevmSolcPlatform;
impl DynPlatform for ReviveDevNodeRevmSolcPlatform { impl DynPlatform for ReviveDevNodeRevmSolcPlatform {
@@ -371,6 +376,26 @@ impl From<PlatformIdentifier> for Box<dyn DynPlatform> {
} }
} }
impl From<PlatformIdentifier> for &dyn DynPlatform {
fn from(value: PlatformIdentifier) -> Self {
match value {
PlatformIdentifier::GethEvmSolc => &GethEvmSolcPlatform as &dyn DynPlatform,
PlatformIdentifier::KitchensinkPolkavmResolc => {
&KitchensinkPolkavmResolcPlatform as &dyn DynPlatform
}
PlatformIdentifier::KitchensinkRevmSolc => {
&KitchensinkRevmSolcPlatform as &dyn DynPlatform
}
PlatformIdentifier::ReviveDevNodePolkavmResolc => {
&ReviveDevNodePolkavmResolcPlatform as &dyn DynPlatform
}
PlatformIdentifier::ReviveDevNodeRevmSolc => {
&ReviveDevNodeRevmSolcPlatform as &dyn DynPlatform
}
}
}
}
fn spawn_node<T: Node + EthereumNode + Send + Sync>( fn spawn_node<T: Node + EthereumNode + Send + Sync>(
mut node: T, mut node: T,
genesis: Genesis, genesis: Genesis,
+285 -342
View File
@@ -1,8 +1,9 @@
mod cached_compiler; mod cached_compiler;
mod pool;
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::{BTreeMap, HashMap}, collections::{BTreeSet, HashMap},
io::{BufWriter, Write, stderr}, io::{BufWriter, Write, stderr},
path::Path, path::Path,
sync::Arc, sync::Arc,
@@ -20,20 +21,19 @@ use futures::{Stream, StreamExt};
use indexmap::{IndexMap, indexmap}; use indexmap::{IndexMap, indexmap};
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use revive_dt_report::{ use revive_dt_report::{
NodeDesignation, ReportAggregator, Reporter, ReporterEvent, TestCaseStatus, ExecutionSpecificReporter, ReportAggregator, Reporter, ReporterEvent, TestCaseStatus,
TestSpecificReporter, TestSpecifier, TestSpecificReporter, TestSpecifier,
}; };
use schemars::schema_for; use schemars::schema_for;
use serde_json::{Value, json}; use serde_json::{Value, json};
use tokio::try_join;
use tracing::{debug, error, info, info_span, instrument}; use tracing::{debug, error, info, info_span, instrument};
use tracing_subscriber::{EnvFilter, FmtSubscriber}; use tracing_subscriber::{EnvFilter, FmtSubscriber};
use revive_dt_common::{iterators::EitherIter, types::Mode}; use revive_dt_common::{iterators::EitherIter, types::Mode};
use revive_dt_compiler::{CompilerOutput, SolidityCompiler}; use revive_dt_compiler::DynSolidityCompiler;
use revive_dt_config::{Context, *}; use revive_dt_config::{Context, *};
use revive_dt_core::{ use revive_dt_core::{
Geth, Kitchensink, Platform, DynPlatform,
driver::{CaseDriver, CaseState}, driver::{CaseDriver, CaseState},
}; };
use revive_dt_format::{ use revive_dt_format::{
@@ -43,9 +43,9 @@ use revive_dt_format::{
metadata::{ContractPathAndIdent, Metadata, MetadataFile}, metadata::{ContractPathAndIdent, Metadata, MetadataFile},
mode::ParsedMode, mode::ParsedMode,
}; };
use revive_dt_node::{Node, pool::NodePool};
use crate::cached_compiler::CachedCompiler; use crate::cached_compiler::CachedCompiler;
use crate::pool::NodePool;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let (writer, _guard) = tracing_appender::non_blocking::NonBlockingBuilder::default() let (writer, _guard) = tracing_appender::non_blocking::NonBlockingBuilder::default()
@@ -133,32 +133,28 @@ fn collect_corpora(
Ok(corpora) Ok(corpora)
} }
async fn run_driver<L, F>( async fn run_driver(
context: ExecutionContext, context: ExecutionContext,
metadata_files: &[MetadataFile], metadata_files: &[MetadataFile],
reporter: Reporter, reporter: Reporter,
report_aggregator_task: impl Future<Output = anyhow::Result<()>>, report_aggregator_task: impl Future<Output = anyhow::Result<()>>,
) -> anyhow::Result<()> platforms: Vec<&dyn DynPlatform>,
where ) -> anyhow::Result<()> {
L: Platform, let mut nodes = Vec::<(&dyn DynPlatform, NodePool)>::new();
F: Platform, for platform in platforms.into_iter() {
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static, let pool = NodePool::new(Context::ExecuteTests(Box::new(context.clone())), platform)
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static, .context("Failed to initialize follower node pool")?;
{ nodes.push((platform, pool));
let leader_nodes = NodePool::<L::Blockchain>::new(context.clone()) }
.context("Failed to initialize leader node pool")?;
let follower_nodes = NodePool::<F::Blockchain>::new(context.clone())
.context("Failed to initialize follower node pool")?;
let tests_stream = tests_stream( let tests_stream = tests_stream(
&context, &context,
metadata_files.iter(), metadata_files.iter(),
&leader_nodes, nodes.as_slice(),
&follower_nodes,
reporter.clone(), reporter.clone(),
) )
.await; .await;
let driver_task = start_driver_task::<L, F>(&context, tests_stream) let driver_task = start_driver_task(&context, tests_stream)
.await .await
.context("Failed to start driver task")?; .context("Failed to start driver task")?;
let cli_reporting_task = start_cli_reporting_task(reporter); let cli_reporting_task = start_cli_reporting_task(reporter);
@@ -169,19 +165,12 @@ where
Ok(()) Ok(())
} }
async fn tests_stream<'a, L, F>( async fn tests_stream<'a>(
args: &ExecutionContext, args: &ExecutionContext,
metadata_files: impl IntoIterator<Item = &'a MetadataFile> + Clone, metadata_files: impl IntoIterator<Item = &'a MetadataFile> + Clone,
leader_node_pool: &'a NodePool<L::Blockchain>, nodes: &'a [(&dyn DynPlatform, NodePool)],
follower_node_pool: &'a NodePool<F::Blockchain>,
reporter: Reporter, reporter: Reporter,
) -> impl Stream<Item = Test<'a, L, F>> ) -> impl Stream<Item = Test<'a>> {
where
L: Platform,
F: Platform,
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
{
let tests = metadata_files let tests = metadata_files
.into_iter() .into_iter()
.flat_map(|metadata_file| { .flat_map(|metadata_file| {
@@ -231,35 +220,36 @@ where
stream::iter(tests.into_iter()) stream::iter(tests.into_iter())
.filter_map( .filter_map(
move |(metadata_file, case_idx, case, mode, reporter)| async move { move |(metadata_file, case_idx, case, mode, reporter)| async move {
let leader_compiler = <L::Compiler as SolidityCompiler>::new( let mut platforms = Vec::new();
args, for (platform, node_pool) in nodes.iter() {
mode.version.clone().map(Into::into), let node = node_pool.round_robbin();
) let compiler = platform
.await .new_compiler(
.inspect_err(|err| error!(?err, "Failed to instantiate the leader compiler")) Context::ExecuteTests(Box::new(args.clone())),
.ok()?; mode.version.clone().map(Into::into),
)
.await
.inspect_err(|err| {
error!(
?err,
platform_identifier = %platform.platform_identifier(),
"Failed to instantiate the compiler"
)
})
.ok()?;
let follower_compiler = <F::Compiler as SolidityCompiler>::new( let reporter = reporter
args, .execution_specific_reporter(node.id(), platform.platform_identifier());
mode.version.clone().map(Into::into), platforms.push((*platform, node, compiler, reporter));
) }
.await
.inspect_err(|err| error!(?err, "Failed to instantiate the follower compiler"))
.ok()?;
let leader_node = leader_node_pool.round_robbin(); Some(Test {
let follower_node = follower_node_pool.round_robbin();
Some(Test::<L, F> {
metadata: metadata_file, metadata: metadata_file,
metadata_file_path: metadata_file.metadata_file_path.as_path(), metadata_file_path: metadata_file.metadata_file_path.as_path(),
mode: mode.clone(), mode: mode.clone(),
case_idx: CaseIdx::new(case_idx), case_idx: CaseIdx::new(case_idx),
case, case,
leader_node, platforms,
follower_node,
leader_compiler,
follower_compiler,
reporter, reporter,
}) })
}, },
@@ -293,18 +283,10 @@ where
}) })
} }
async fn start_driver_task<'a, L, F>( async fn start_driver_task<'a>(
context: &ExecutionContext, context: &ExecutionContext,
tests: impl Stream<Item = Test<'a, L, F>>, tests: impl Stream<Item = Test<'a>>,
) -> anyhow::Result<impl Future<Output = ()>> ) -> anyhow::Result<impl Future<Output = ()>> {
where
L: Platform,
F: Platform,
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
L::Compiler: 'a,
F::Compiler: 'a,
{
info!("Starting driver task"); info!("Starting driver task");
let cached_compiler = Arc::new( let cached_compiler = Arc::new(
@@ -327,23 +309,18 @@ where
let cached_compiler = cached_compiler.clone(); let cached_compiler = cached_compiler.clone();
async move { async move {
test.reporter for (platform, node, _, _) in test.platforms.iter() {
.report_leader_node_assigned_event( test.reporter
test.leader_node.id(), .report_node_assigned_event(
*L::config_id(), node.id(),
test.leader_node.connection_string(), platform.platform_identifier(),
) node.connection_string(),
.expect("Can't fail"); )
test.reporter .expect("Can't fail");
.report_follower_node_assigned_event( }
test.follower_node.id(),
*F::config_id(),
test.follower_node.connection_string(),
)
.expect("Can't fail");
let reporter = test.reporter.clone(); let reporter = test.reporter.clone();
let result = handle_case_driver::<L, F>(test, cached_compiler).await; let result = handle_case_driver(&test, cached_compiler).await;
match result { match result {
Ok(steps_executed) => reporter Ok(steps_executed) => reporter
@@ -449,230 +426,174 @@ async fn start_cli_reporting_task(reporter: Reporter) {
mode = %test.mode, mode = %test.mode,
case_idx = %test.case_idx, case_idx = %test.case_idx,
case_name = test.case.name.as_deref().unwrap_or("Unnamed Case"), case_name = test.case.name.as_deref().unwrap_or("Unnamed Case"),
leader_node = test.leader_node.id(),
follower_node = test.follower_node.id(),
) )
)] )]
async fn handle_case_driver<'a, L, F>( async fn handle_case_driver<'a>(
test: Test<'a, L, F>, test: &Test<'a>,
cached_compiler: Arc<CachedCompiler<'a>>, cached_compiler: Arc<CachedCompiler<'a>>,
) -> anyhow::Result<usize> ) -> anyhow::Result<usize> {
where let platform_state = stream::iter(test.platforms.iter())
L: Platform, // Compiling the pre-link contracts.
F: Platform, .filter_map(|(platform, node, compiler, reporter)| {
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static, let cached_compiler = cached_compiler.clone();
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
L::Compiler: 'a,
F::Compiler: 'a,
{
let leader_reporter = test
.reporter
.execution_specific_reporter(test.leader_node.id(), NodeDesignation::Leader);
let follower_reporter = test
.reporter
.execution_specific_reporter(test.follower_node.id(), NodeDesignation::Follower);
let ( async move {
CompilerOutput { let compiler_output = cached_compiler
contracts: leader_pre_link_contracts, .compile_contracts(
}, test.metadata,
CompilerOutput { test.metadata_file_path,
contracts: follower_pre_link_contracts, test.mode.clone(),
}, None,
) = try_join!( compiler.as_ref(),
cached_compiler.compile_contracts::<L>( *platform,
test.metadata, reporter,
test.metadata_file_path, )
test.mode.clone(), .await
None, .inspect_err(|err| {
&test.leader_compiler, error!(
&leader_reporter, %err,
), platform_identifier = %platform.platform_identifier(),
cached_compiler.compile_contracts::<F>( "Pre-linking compilation failed"
test.metadata, )
test.metadata_file_path, })
test.mode.clone(), .ok()?;
None, Some((test, platform, node, compiler, reporter, compiler_output))
&test.follower_compiler,
&follower_reporter
)
)
.context("Failed to compile pre-link contracts for leader/follower in parallel")?;
let mut leader_deployed_libraries = None::<HashMap<_, _>>;
let mut follower_deployed_libraries = None::<HashMap<_, _>>;
let mut contract_sources = test
.metadata
.contract_sources()
.context("Failed to retrieve contract sources from metadata")?;
for library_instance in test
.metadata
.libraries
.iter()
.flatten()
.flat_map(|(_, map)| map.values())
{
debug!(%library_instance, "Deploying Library Instance");
let ContractPathAndIdent {
contract_source_path: library_source_path,
contract_ident: library_ident,
} = contract_sources
.remove(library_instance)
.context("Failed to find the contract source")?;
let (leader_code, leader_abi) = leader_pre_link_contracts
.get(&library_source_path)
.and_then(|contracts| contracts.get(library_ident.as_str()))
.context("Declared library was not compiled")?;
let (follower_code, follower_abi) = follower_pre_link_contracts
.get(&library_source_path)
.and_then(|contracts| contracts.get(library_ident.as_str()))
.context("Declared library was not compiled")?;
let leader_code = match alloy::hex::decode(leader_code) {
Ok(code) => code,
Err(error) => {
anyhow::bail!("Failed to hex-decode the byte code {}", error)
} }
}; })
let follower_code = match alloy::hex::decode(follower_code) { // Deploying the libraries for the platform.
Ok(code) => code, .filter_map(
Err(error) => { |(test, platform, node, compiler, reporter, compiler_output)| async move {
anyhow::bail!("Failed to hex-decode the byte code {}", error) let mut deployed_libraries = None::<HashMap<_, _>>;
} let mut contract_sources = test
}; .metadata
.contract_sources()
.inspect_err(|err| {
error!(
%err,
platform_identifier = %platform.platform_identifier(),
"Failed to retrieve contract sources from metadata"
)
})
.ok()?;
for library_instance in test
.metadata
.libraries
.iter()
.flatten()
.flat_map(|(_, map)| map.values())
{
debug!(%library_instance, "Deploying Library Instance");
// Getting the deployer address from the cases themselves. This is to ensure that we're let ContractPathAndIdent {
// doing the deployments from different accounts and therefore we're not slowed down by contract_source_path: library_source_path,
// the nonce. contract_ident: library_ident,
let deployer_address = test } = contract_sources.remove(library_instance)?;
.case
.steps
.iter()
.filter_map(|step| match step {
Step::FunctionCall(input) => Some(input.caller),
Step::BalanceAssertion(..) => None,
Step::StorageEmptyAssertion(..) => None,
})
.next()
.unwrap_or(Input::default_caller());
let leader_tx = TransactionBuilder::<Ethereum>::with_deploy_code(
TransactionRequest::default().from(deployer_address),
leader_code,
);
let follower_tx = TransactionBuilder::<Ethereum>::with_deploy_code(
TransactionRequest::default().from(deployer_address),
follower_code,
);
let (leader_receipt, follower_receipt) = try_join!( let (code, leader_abi) = compiler_output
test.leader_node.execute_transaction(leader_tx), .contracts
test.follower_node.execute_transaction(follower_tx) .get(&library_source_path)
)?; .and_then(|contracts| contracts.get(library_ident.as_str()))?;
debug!( let code = alloy::hex::decode(code).ok()?;
?library_instance,
library_address = ?leader_receipt.contract_address,
"Deployed library to leader"
);
debug!(
?library_instance,
library_address = ?follower_receipt.contract_address,
"Deployed library to follower"
);
let leader_library_address = leader_receipt // Getting the deployer address from the cases themselves. This is to ensure
.contract_address // that we're doing the deployments from different accounts and therefore we're
.context("Contract deployment didn't return an address")?; // not slowed down by the nonce.
let follower_library_address = follower_receipt let deployer_address = test
.contract_address .case
.context("Contract deployment didn't return an address")?; .steps
.iter()
.filter_map(|step| match step {
Step::FunctionCall(input) => Some(input.caller),
Step::BalanceAssertion(..) => None,
Step::StorageEmptyAssertion(..) => None,
})
.next()
.unwrap_or(Input::default_caller());
let tx = TransactionBuilder::<Ethereum>::with_deploy_code(
TransactionRequest::default().from(deployer_address),
code,
);
let receipt = node
.execute_transaction(tx)
.await
.inspect_err(|err| {
error!(
%err,
%library_instance,
platform_identifier = %platform.platform_identifier(),
"Failed to deploy the library"
)
})
.ok()?;
leader_deployed_libraries.get_or_insert_default().insert( debug!(
library_instance.clone(), ?library_instance,
( platform_identifier = %platform.platform_identifier(),
library_ident.clone(), "Deployed library"
leader_library_address, );
leader_abi.clone(),
),
);
follower_deployed_libraries.get_or_insert_default().insert(
library_instance.clone(),
(
library_ident,
follower_library_address,
follower_abi.clone(),
),
);
}
if let Some(ref leader_deployed_libraries) = leader_deployed_libraries {
leader_reporter.report_libraries_deployed_event(
leader_deployed_libraries
.clone()
.into_iter()
.map(|(key, (_, address, _))| (key, address))
.collect::<BTreeMap<_, _>>(),
)?;
}
if let Some(ref follower_deployed_libraries) = follower_deployed_libraries {
follower_reporter.report_libraries_deployed_event(
follower_deployed_libraries
.clone()
.into_iter()
.map(|(key, (_, address, _))| (key, address))
.collect::<BTreeMap<_, _>>(),
)?;
}
let ( let library_address = receipt.contract_address?;
CompilerOutput {
contracts: leader_post_link_contracts, deployed_libraries.get_or_insert_default().insert(
}, library_instance.clone(),
CompilerOutput { (library_ident.clone(), library_address, leader_abi.clone()),
contracts: follower_post_link_contracts, );
}, }
) = try_join!(
cached_compiler.compile_contracts::<L>( Some((
test.metadata, test,
test.metadata_file_path, platform,
test.mode.clone(), node,
leader_deployed_libraries.as_ref(), compiler,
&test.leader_compiler, reporter,
&leader_reporter, compiler_output,
), deployed_libraries,
cached_compiler.compile_contracts::<F>( ))
test.metadata, },
test.metadata_file_path,
test.mode.clone(),
follower_deployed_libraries.as_ref(),
&test.follower_compiler,
&follower_reporter
) )
) // Compiling the post-link contracts.
.context("Failed to compile post-link contracts for leader/follower in parallel")?; .filter_map(
|(test, platform, node, compiler, reporter, _, deployed_libraries)| {
let cached_compiler = cached_compiler.clone();
let leader_state = CaseState::<L>::new( async move {
test.leader_compiler.version().clone(), let compiler_output = cached_compiler
leader_post_link_contracts, .compile_contracts(
leader_deployed_libraries.unwrap_or_default(), test.metadata,
leader_reporter, test.metadata_file_path,
); test.mode.clone(),
let follower_state = CaseState::<F>::new( deployed_libraries.as_ref(),
test.follower_compiler.version().clone(), compiler.as_ref(),
follower_post_link_contracts, *platform,
follower_deployed_libraries.unwrap_or_default(), reporter,
follower_reporter, )
); .await
.inspect_err(|err| {
error!(
%err,
platform_identifier = %platform.platform_identifier(),
"Pre-linking compilation failed"
)
})
.ok()?;
let mut driver = CaseDriver::<L, F>::new( let case_state = CaseState::new(
test.metadata, compiler.version().clone(),
test.case, compiler_output.contracts,
test.leader_node, deployed_libraries.unwrap_or_default(),
test.follower_node, reporter.clone(),
leader_state, );
follower_state,
); Some((*node, platform.platform_identifier(), case_state))
}
},
)
// Collect
.collect::<Vec<_>>()
.await;
let mut driver = CaseDriver::new(test.metadata, test.case, platform_state);
driver driver
.execute() .execute()
.await .await
@@ -685,36 +606,38 @@ async fn execute_corpus(
reporter: Reporter, reporter: Reporter,
report_aggregator_task: impl Future<Output = anyhow::Result<()>>, report_aggregator_task: impl Future<Output = anyhow::Result<()>>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match (&context.leader, &context.follower) { let platforms = context
(TestingPlatform::Geth, TestingPlatform::Kitchensink) => { .platforms
run_driver::<Geth, Kitchensink>(context, tests, reporter, report_aggregator_task) .iter()
.await? .copied()
} .collect::<BTreeSet<_>>()
(TestingPlatform::Geth, TestingPlatform::Geth) => { .into_iter()
run_driver::<Geth, Geth>(context, tests, reporter, report_aggregator_task).await? .map(Into::<&dyn DynPlatform>::into)
} .collect::<Vec<_>>();
_ => unimplemented!(),
} run_driver(context, tests, reporter, report_aggregator_task, platforms).await?;
Ok(()) Ok(())
} }
/// this represents a single "test"; a mode, path and collection of cases. /// this represents a single "test"; a mode, path and collection of cases.
#[derive(Clone)] #[allow(clippy::type_complexity)]
struct Test<'a, L: Platform, F: Platform> { struct Test<'a> {
metadata: &'a MetadataFile, metadata: &'a MetadataFile,
metadata_file_path: &'a Path, metadata_file_path: &'a Path,
mode: Cow<'a, Mode>, mode: Cow<'a, Mode>,
case_idx: CaseIdx, case_idx: CaseIdx,
case: &'a Case, case: &'a Case,
leader_node: &'a <L as Platform>::Blockchain, platforms: Vec<(
follower_node: &'a <F as Platform>::Blockchain, &'a dyn DynPlatform,
leader_compiler: L::Compiler, &'a dyn EthereumNode,
follower_compiler: F::Compiler, Box<dyn DynSolidityCompiler>,
ExecutionSpecificReporter,
)>,
reporter: TestSpecificReporter, reporter: TestSpecificReporter,
} }
impl<'a, L: Platform, F: Platform> Test<'a, L, F> { impl<'a> Test<'a> {
/// Checks if this test can be ran with the current configuration. /// Checks if this test can be ran with the current configuration.
pub fn check_compatibility(&self) -> TestCheckFunctionResult { pub fn check_compatibility(&self) -> TestCheckFunctionResult {
self.check_metadata_file_ignored()?; self.check_metadata_file_ignored()?;
@@ -743,24 +666,39 @@ impl<'a, L: Platform, F: Platform> Test<'a, L, F> {
} }
} }
/// Checks if the leader and the follower both support the desired targets in the metadata file. /// Checks if the platforms all support the desired targets in the metadata file.
fn check_target_compatibility(&self) -> TestCheckFunctionResult { fn check_target_compatibility(&self) -> TestCheckFunctionResult {
let leader_support = let mut error_map = indexmap! {
<L::Blockchain as Node>::matches_target(self.metadata.targets.as_deref()); "test_desired_targets" => json!(self.metadata.targets.as_ref()),
let follower_support = };
<F::Blockchain as Node>::matches_target(self.metadata.targets.as_deref()); let mut is_allowed = true;
let is_allowed = leader_support && follower_support; for (platform, ..) in self.platforms.iter() {
let is_allowed_for_platform = match self.metadata.targets.as_ref() {
None => true,
Some(targets) => {
let mut target_matches = false;
for target in targets.iter() {
if &platform.vm_identifier() == target {
target_matches = true;
break;
}
}
target_matches
}
};
is_allowed &= is_allowed_for_platform;
error_map.insert(
platform.platform_identifier().into(),
json!(is_allowed_for_platform),
);
}
if is_allowed { if is_allowed {
Ok(()) Ok(())
} else { } else {
Err(( Err((
"Either the leader or the follower do not support the target desired by the test.", "One of the platforms do do not support the targets allowed by the test.",
indexmap! { error_map,
"test_desired_targets" => json!(self.metadata.targets.as_ref()),
"leader_support" => json!(leader_support),
"follower_support" => json!(follower_support),
},
)) ))
} }
} }
@@ -771,46 +709,51 @@ impl<'a, L: Platform, F: Platform> Test<'a, L, F> {
return Ok(()); return Ok(());
}; };
let leader_support = evm_version_requirement let mut error_map = indexmap! {
.matches(&<L::Blockchain as revive_dt_node::Node>::evm_version()); "test_desired_evm_version" => json!(self.metadata.required_evm_version),
let follower_support = evm_version_requirement };
.matches(&<F::Blockchain as revive_dt_node::Node>::evm_version()); let mut is_allowed = true;
let is_allowed = leader_support && follower_support; for (platform, node, ..) in self.platforms.iter() {
let is_allowed_for_platform = evm_version_requirement.matches(&node.evm_version());
is_allowed &= is_allowed_for_platform;
error_map.insert(
platform.platform_identifier().into(),
json!(is_allowed_for_platform),
);
}
if is_allowed { if is_allowed {
Ok(()) Ok(())
} else { } else {
Err(( Err((
"EVM version is incompatible with either the leader or the follower.", "EVM version is incompatible for the platforms specified",
indexmap! { error_map,
"test_desired_evm_version" => json!(self.metadata.required_evm_version),
"leader_support" => json!(leader_support),
"follower_support" => json!(follower_support),
},
)) ))
} }
} }
/// Checks if the leader and follower compilers support the mode that the test is for. /// Checks if the leader and follower compilers support the mode that the test is for.
fn check_compiler_compatibility(&self) -> TestCheckFunctionResult { fn check_compiler_compatibility(&self) -> TestCheckFunctionResult {
let leader_support = self let mut error_map = indexmap! {
.leader_compiler "test_desired_evm_version" => json!(self.metadata.required_evm_version),
.supports_mode(self.mode.optimize_setting, self.mode.pipeline); };
let follower_support = self let mut is_allowed = true;
.follower_compiler for (platform, _, compiler, ..) in self.platforms.iter() {
.supports_mode(self.mode.optimize_setting, self.mode.pipeline); let is_allowed_for_platform =
let is_allowed = leader_support && follower_support; compiler.supports_mode(self.mode.optimize_setting, self.mode.pipeline);
is_allowed &= is_allowed_for_platform;
error_map.insert(
platform.platform_identifier().into(),
json!(is_allowed_for_platform),
);
}
if is_allowed { if is_allowed {
Ok(()) Ok(())
} else { } else {
Err(( Err((
"Compilers do not support this mode either for the leader or for the follower.", "Compilers do not support this mode either for the provided platforms.",
indexmap! { error_map,
"mode" => json!(self.mode),
"leader_support" => json!(leader_support),
"follower_support" => json!(follower_support),
},
)) ))
} }
} }
+52
View File
@@ -0,0 +1,52 @@
//! This crate implements concurrent handling of testing node.
use std::sync::atomic::{AtomicUsize, Ordering};
use anyhow::Context as _;
use revive_dt_config::*;
use revive_dt_core::DynPlatform;
use revive_dt_node_interaction::EthereumNode;
/// The node pool starts one or more [Node] which then can be accessed
/// in a round robbin fashion.
pub struct NodePool {
next: AtomicUsize,
nodes: Vec<Box<dyn EthereumNode + Send + Sync>>,
}
impl NodePool {
/// Create a new Pool. This will start as many nodes as there are workers in `config`.
pub fn new(context: Context, platform: &dyn DynPlatform) -> anyhow::Result<Self> {
let concurrency_configuration = AsRef::<ConcurrencyConfiguration>::as_ref(&context);
let nodes = concurrency_configuration.number_of_nodes;
let mut handles = Vec::with_capacity(nodes);
for _ in 0..nodes {
let context = context.clone();
handles.push(platform.new_node(context)?);
}
let mut nodes = Vec::with_capacity(nodes);
for handle in handles {
nodes.push(
handle
.join()
.map_err(|error| anyhow::anyhow!("failed to spawn node: {:?}", error))
.context("Failed to join node spawn thread")?
.map_err(|error| anyhow::anyhow!("node failed to spawn: {error}"))
.context("Node failed to spawn")?,
);
}
Ok(Self {
nodes,
next: Default::default(),
})
}
/// Get a handle to the next node.
pub fn round_robbin(&self) -> &dyn EthereumNode {
let current = self.next.fetch_add(1, Ordering::SeqCst) % self.nodes.len();
self.nodes.get(current).unwrap().as_ref()
}
}
+8 -8
View File
@@ -308,7 +308,7 @@ impl Input {
pub async fn encoded_input( pub async fn encoded_input(
&self, &self,
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
context: ResolutionContext<'_>, context: ResolutionContext<'_>,
) -> anyhow::Result<Bytes> { ) -> anyhow::Result<Bytes> {
match self.method { match self.method {
@@ -377,7 +377,7 @@ impl Input {
/// Parse this input into a legacy transaction. /// Parse this input into a legacy transaction.
pub async fn legacy_transaction( pub async fn legacy_transaction(
&self, &self,
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
context: ResolutionContext<'_>, context: ResolutionContext<'_>,
) -> anyhow::Result<TransactionRequest> { ) -> anyhow::Result<TransactionRequest> {
let input_data = self let input_data = self
@@ -466,7 +466,7 @@ impl Calldata {
pub async fn calldata( pub async fn calldata(
&self, &self,
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
context: ResolutionContext<'_>, context: ResolutionContext<'_>,
) -> anyhow::Result<Vec<u8>> { ) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement()); let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
@@ -478,7 +478,7 @@ impl Calldata {
pub async fn calldata_into_slice( pub async fn calldata_into_slice(
&self, &self,
buffer: &mut Vec<u8>, buffer: &mut Vec<u8>,
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
context: ResolutionContext<'_>, context: ResolutionContext<'_>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match self { match self {
@@ -515,7 +515,7 @@ impl Calldata {
pub async fn is_equivalent( pub async fn is_equivalent(
&self, &self,
other: &[u8], other: &[u8],
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
context: ResolutionContext<'_>, context: ResolutionContext<'_>,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
match self { match self {
@@ -557,7 +557,7 @@ impl CalldataItem {
#[instrument(level = "info", skip_all, err)] #[instrument(level = "info", skip_all, err)]
async fn resolve( async fn resolve(
&self, &self,
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
context: ResolutionContext<'_>, context: ResolutionContext<'_>,
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
let mut stack = Vec::<CalldataToken<U256>>::new(); let mut stack = Vec::<CalldataToken<U256>>::new();
@@ -662,7 +662,7 @@ impl<T: AsRef<str>> CalldataToken<T> {
/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146 /// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146
async fn resolve( async fn resolve(
self, self,
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
context: ResolutionContext<'_>, context: ResolutionContext<'_>,
) -> anyhow::Result<CalldataToken<U256>> { ) -> anyhow::Result<CalldataToken<U256>> {
match self { match self {
@@ -1010,7 +1010,7 @@ mod tests {
async fn resolve_calldata_item( async fn resolve_calldata_item(
input: &str, input: &str,
deployed_contracts: &HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
resolver: &impl ResolverApi, resolver: &(impl ResolverApi + ?Sized),
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
let context = ResolutionContext::default().with_deployed_contracts(deployed_contracts); let context = ResolutionContext::default().with_deployed_contracts(deployed_contracts);
CalldataItem::new(input).resolve(resolver, context).await CalldataItem::new(input).resolve(resolver, context).await
+5 -3
View File
@@ -13,8 +13,10 @@ use serde::{Deserialize, Serialize};
use revive_common::EVMVersion; use revive_common::EVMVersion;
use revive_dt_common::{ use revive_dt_common::{
cached_fs::read_to_string, iterators::FilesWithExtensionIterator, macros::define_wrapper_type, cached_fs::read_to_string,
types::Mode, iterators::FilesWithExtensionIterator,
macros::define_wrapper_type,
types::{Mode, VmIdentifier},
}; };
use tracing::error; use tracing::error;
@@ -81,7 +83,7 @@ pub struct Metadata {
/// example, if we wish for the metadata file's cases to only be run on PolkaVM then we'd /// example, if we wish for the metadata file's cases to only be run on PolkaVM then we'd
/// specify a target of "PolkaVM" in here. /// specify a target of "PolkaVM" in here.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub targets: Option<Vec<String>>, pub targets: Option<Vec<VmIdentifier>>,
/// A vector of the test cases and workloads contained within the metadata file. This is their /// A vector of the test cases and workloads contained within the metadata file. This is their
/// primary description. /// primary description.
+3
View File
@@ -9,6 +9,9 @@ repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
[dependencies] [dependencies]
revive-common = { workspace = true }
revive-dt-common = { workspace = true }
revive-dt-format = { workspace = true } revive-dt-format = { workspace = true }
alloy = { workspace = true } alloy = { workspace = true }
+12 -1
View File
@@ -1,16 +1,24 @@
//! This crate implements all node interactions. //! This crate implements all node interactions.
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc;
use alloy::primitives::{Address, StorageKey, TxHash, U256}; use alloy::primitives::{Address, StorageKey, TxHash, U256};
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace}; use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
use alloy::rpc::types::{EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest}; use alloy::rpc::types::{EIP1186AccountProofResponse, TransactionReceipt, TransactionRequest};
use anyhow::Result; use anyhow::Result;
use revive_common::EVMVersion;
use revive_dt_format::traits::ResolverApi; use revive_dt_format::traits::ResolverApi;
/// An interface for all interactions with Ethereum compatible nodes. /// An interface for all interactions with Ethereum compatible nodes.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub trait EthereumNode { pub trait EthereumNode {
fn id(&self) -> usize;
/// Returns the nodes connection string.
fn connection_string(&self) -> &str;
/// Execute the [TransactionRequest] and return a [TransactionReceipt]. /// Execute the [TransactionRequest] and return a [TransactionReceipt].
fn execute_transaction( fn execute_transaction(
&self, &self,
@@ -38,5 +46,8 @@ pub trait EthereumNode {
) -> Pin<Box<dyn Future<Output = Result<EIP1186AccountProofResponse>> + '_>>; ) -> Pin<Box<dyn Future<Output = Result<EIP1186AccountProofResponse>> + '_>>;
/// Returns the resolver that is to use with this ethereum node. /// Returns the resolver that is to use with this ethereum node.
fn resolver(&self) -> Pin<Box<dyn Future<Output = Result<Box<dyn ResolverApi + '_>>> + '_>>; fn resolver(&self) -> Pin<Box<dyn Future<Output = Result<Arc<dyn ResolverApi + '_>>> + '_>>;
/// Returns the EVM version of the node.
fn evm_version(&self) -> EVMVersion;
} }
+14 -23
View File
@@ -327,6 +327,14 @@ impl GethNode {
} }
impl EthereumNode for GethNode { impl EthereumNode for GethNode {
fn id(&self) -> usize {
self.id as _
}
fn connection_string(&self) -> &str {
&self.connection_string
}
#[instrument( #[instrument(
level = "info", level = "info",
skip_all, skip_all,
@@ -498,13 +506,17 @@ impl EthereumNode for GethNode {
// #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] // #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
fn resolver( fn resolver(
&self, &self,
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn ResolverApi + '_>>> + '_>> { ) -> Pin<Box<dyn Future<Output = anyhow::Result<Arc<dyn ResolverApi + '_>>> + '_>> {
Box::pin(async move { Box::pin(async move {
let id = self.id; let id = self.id;
let provider = self.provider().await?; let provider = self.provider().await?;
Ok(Box::new(GethNodeResolver { id, provider }) as Box<dyn ResolverApi>) Ok(Arc::new(GethNodeResolver { id, provider }) as Arc<dyn ResolverApi>)
}) })
} }
fn evm_version(&self) -> EVMVersion {
EVMVersion::Cancun
}
} }
pub struct GethNodeResolver<F: TxFiller<Ethereum>, P: Provider<Ethereum>> { pub struct GethNodeResolver<F: TxFiller<Ethereum>, P: Provider<Ethereum>> {
@@ -788,16 +800,6 @@ impl ResolverApi for GethNode {
} }
impl Node for GethNode { impl Node for GethNode {
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
fn id(&self) -> usize {
self.id as _
}
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
fn connection_string(&self) -> String {
self.connection_string.clone()
}
#[instrument(level = "info", skip_all, fields(geth_node_id = self.id))] #[instrument(level = "info", skip_all, fields(geth_node_id = self.id))]
fn shutdown(&mut self) -> anyhow::Result<()> { fn shutdown(&mut self) -> anyhow::Result<()> {
// Terminate the processes in a graceful manner to allow for the output to be flushed. // Terminate the processes in a graceful manner to allow for the output to be flushed.
@@ -840,17 +842,6 @@ impl Node for GethNode {
.stdout; .stdout;
Ok(String::from_utf8_lossy(&output).into()) Ok(String::from_utf8_lossy(&output).into())
} }
fn matches_target(targets: Option<&[String]>) -> bool {
match targets {
None => true,
Some(targets) => targets.iter().any(|str| str.as_str() == "evm"),
}
}
fn evm_version() -> EVMVersion {
EVMVersion::Cancun
}
} }
impl Drop for GethNode { impl Drop for GethNode {
-15
View File
@@ -1,20 +1,15 @@
//! This crate implements the testing nodes. //! This crate implements the testing nodes.
use alloy::genesis::Genesis; use alloy::genesis::Genesis;
use revive_common::EVMVersion;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
pub mod common; pub mod common;
pub mod constants; pub mod constants;
pub mod geth; pub mod geth;
pub mod pool;
pub mod substrate; pub mod substrate;
/// An abstract interface for testing nodes. /// An abstract interface for testing nodes.
pub trait Node: EthereumNode { pub trait Node: EthereumNode {
/// Returns the identifier of the node.
fn id(&self) -> usize;
/// Spawns a node configured according to the genesis json. /// Spawns a node configured according to the genesis json.
/// ///
/// Blocking until it's ready to accept transactions. /// Blocking until it's ready to accept transactions.
@@ -25,16 +20,6 @@ pub trait Node: EthereumNode {
/// Blocking until it's completely stopped. /// Blocking until it's completely stopped.
fn shutdown(&mut self) -> anyhow::Result<()>; fn shutdown(&mut self) -> anyhow::Result<()>;
/// Returns the nodes connection string.
fn connection_string(&self) -> String;
/// Returns the node version. /// Returns the node version.
fn version(&self) -> anyhow::Result<String>; fn version(&self) -> anyhow::Result<String>;
/// Given a list of targets from the metadata file, this function determines if the metadata
/// file can be ran on this node or not.
fn matches_target(targets: Option<&[String]>) -> bool;
/// Returns the EVM version of the node.
fn evm_version() -> EVMVersion;
} }
-96
View File
@@ -1,96 +0,0 @@
//! This crate implements concurrent handling of testing node.
use std::{
sync::atomic::{AtomicUsize, Ordering},
thread,
};
use alloy::genesis::Genesis;
use anyhow::Context as _;
use revive_dt_config::{
ConcurrencyConfiguration, EthRpcConfiguration, GenesisConfiguration, GethConfiguration,
KitchensinkConfiguration, ReviveDevNodeConfiguration, WalletConfiguration,
WorkingDirectoryConfiguration,
};
use crate::Node;
/// The node pool starts one or more [Node] which then can be accessed
/// in a round robbin fashion.
pub struct NodePool<T> {
next: AtomicUsize,
nodes: Vec<T>,
}
impl<T> NodePool<T>
where
T: Node + Send + 'static,
{
/// Create a new Pool. This will start as many nodes as there are workers in `config`.
pub fn new(
context: impl AsRef<WorkingDirectoryConfiguration>
+ AsRef<ConcurrencyConfiguration>
+ AsRef<GenesisConfiguration>
+ AsRef<WalletConfiguration>
+ AsRef<GethConfiguration>
+ AsRef<KitchensinkConfiguration>
+ AsRef<ReviveDevNodeConfiguration>
+ AsRef<EthRpcConfiguration>
+ Send
+ Sync
+ Clone
+ 'static,
) -> anyhow::Result<Self> {
let concurrency_configuration = AsRef::<ConcurrencyConfiguration>::as_ref(&context);
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
let nodes = concurrency_configuration.number_of_nodes;
let genesis = genesis_configuration.genesis()?;
let mut handles = Vec::with_capacity(nodes);
for _ in 0..nodes {
let context = context.clone();
let genesis = genesis.clone();
handles.push(thread::spawn(move || spawn_node::<T>(context, genesis)));
}
let mut nodes = Vec::with_capacity(nodes);
for handle in handles {
nodes.push(
handle
.join()
.map_err(|error| anyhow::anyhow!("failed to spawn node: {:?}", error))
.context("Failed to join node spawn thread")?
.map_err(|error| anyhow::anyhow!("node failed to spawn: {error}"))
.context("Node failed to spawn")?,
);
}
Ok(Self {
nodes,
next: Default::default(),
})
}
/// Get a handle to the next node.
pub fn round_robbin(&self) -> &T {
let current = self.next.fetch_add(1, Ordering::SeqCst) % self.nodes.len();
self.nodes.get(current).unwrap()
}
}
fn spawn_node<T: Node + Send>(
_: impl AsRef<WorkingDirectoryConfiguration>
+ AsRef<ConcurrencyConfiguration>
+ AsRef<GenesisConfiguration>
+ AsRef<WalletConfiguration>
+ AsRef<GethConfiguration>
+ AsRef<KitchensinkConfiguration>
+ AsRef<ReviveDevNodeConfiguration>
+ AsRef<EthRpcConfiguration>
+ Clone
+ 'static,
_: Genesis,
) -> anyhow::Result<T> {
todo!("Remove");
}
+14 -21
View File
@@ -432,6 +432,14 @@ impl SubstrateNode {
} }
impl EthereumNode for SubstrateNode { impl EthereumNode for SubstrateNode {
fn id(&self) -> usize {
self.id as _
}
fn connection_string(&self) -> &str {
&self.rpc_url
}
fn execute_transaction( fn execute_transaction(
&self, &self,
transaction: alloy::rpc::types::TransactionRequest, transaction: alloy::rpc::types::TransactionRequest,
@@ -520,13 +528,17 @@ impl EthereumNode for SubstrateNode {
fn resolver( fn resolver(
&self, &self,
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn ResolverApi + '_>>> + '_>> { ) -> Pin<Box<dyn Future<Output = anyhow::Result<Arc<dyn ResolverApi + '_>>> + '_>> {
Box::pin(async move { Box::pin(async move {
let id = self.id; let id = self.id;
let provider = self.provider().await?; let provider = self.provider().await?;
Ok(Box::new(SubstrateNodeResolver { id, provider }) as Box<dyn ResolverApi>) Ok(Arc::new(SubstrateNodeResolver { id, provider }) as Arc<dyn ResolverApi>)
}) })
} }
fn evm_version(&self) -> EVMVersion {
EVMVersion::Cancun
}
} }
pub struct SubstrateNodeResolver<F: TxFiller<ReviveNetwork>, P: Provider<ReviveNetwork>> { pub struct SubstrateNodeResolver<F: TxFiller<ReviveNetwork>, P: Provider<ReviveNetwork>> {
@@ -803,14 +815,6 @@ impl ResolverApi for SubstrateNode {
} }
impl Node for SubstrateNode { impl Node for SubstrateNode {
fn id(&self) -> usize {
self.id as _
}
fn connection_string(&self) -> String {
self.rpc_url.clone()
}
fn shutdown(&mut self) -> anyhow::Result<()> { fn shutdown(&mut self) -> anyhow::Result<()> {
// Terminate the processes in a graceful manner to allow for the output to be flushed. // Terminate the processes in a graceful manner to allow for the output to be flushed.
if let Some(mut child) = self.process_proxy.take() { if let Some(mut child) = self.process_proxy.take() {
@@ -854,17 +858,6 @@ impl Node for SubstrateNode {
.stdout; .stdout;
Ok(String::from_utf8_lossy(&output).into()) Ok(String::from_utf8_lossy(&output).into())
} }
fn matches_target(targets: Option<&[String]>) -> bool {
match targets {
None => true,
Some(targets) => targets.iter().any(|str| str.as_str() == "pvm"),
}
}
fn evm_version() -> EVMVersion {
EVMVersion::Cancun
}
} }
impl Drop for SubstrateNode { impl Drop for SubstrateNode {
+15 -37
View File
@@ -11,8 +11,9 @@ use std::{
use alloy_primitives::Address; use alloy_primitives::Address;
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use indexmap::IndexMap; use indexmap::IndexMap;
use revive_dt_common::types::PlatformIdentifier;
use revive_dt_compiler::{CompilerInput, CompilerOutput, Mode}; use revive_dt_compiler::{CompilerInput, CompilerOutput, Mode};
use revive_dt_config::{Context, TestingPlatform}; use revive_dt_config::Context;
use revive_dt_format::{case::CaseIdx, corpus::Corpus, metadata::ContractInstance}; use revive_dt_format::{case::CaseIdx, corpus::Corpus, metadata::ContractInstance};
use semver::Version; use semver::Version;
use serde::Serialize; use serde::Serialize;
@@ -84,11 +85,8 @@ impl ReportAggregator {
RunnerEvent::TestIgnored(event) => { RunnerEvent::TestIgnored(event) => {
self.handle_test_ignored_event(*event); self.handle_test_ignored_event(*event);
} }
RunnerEvent::LeaderNodeAssigned(event) => { RunnerEvent::NodeAssigned(event) => {
self.handle_leader_node_assigned_event(*event); self.handle_node_assigned_event(*event);
}
RunnerEvent::FollowerNodeAssigned(event) => {
self.handle_follower_node_assigned_event(*event);
} }
RunnerEvent::PreLinkContractsCompilationSucceeded(event) => { RunnerEvent::PreLinkContractsCompilationSucceeded(event) => {
self.handle_pre_link_contracts_compilation_succeeded_event(*event) self.handle_pre_link_contracts_compilation_succeeded_event(*event)
@@ -257,28 +255,15 @@ impl ReportAggregator {
let _ = self.listener_tx.send(event); let _ = self.listener_tx.send(event);
} }
fn handle_leader_node_assigned_event(&mut self, event: LeaderNodeAssignedEvent) { fn handle_node_assigned_event(&mut self, event: NodeAssignedEvent) {
let execution_information = self.execution_information(&ExecutionSpecifier { let execution_information = self.execution_information(&ExecutionSpecifier {
test_specifier: event.test_specifier, test_specifier: event.test_specifier,
node_id: event.id, node_id: event.id,
node_designation: NodeDesignation::Leader, platform_identifier: event.platform_identifier,
}); });
execution_information.node = Some(TestCaseNodeInformation { execution_information.node = Some(TestCaseNodeInformation {
id: event.id, id: event.id,
platform: event.platform, platform_identifier: event.platform_identifier,
connection_string: event.connection_string,
});
}
fn handle_follower_node_assigned_event(&mut self, event: FollowerNodeAssignedEvent) {
let execution_information = self.execution_information(&ExecutionSpecifier {
test_specifier: event.test_specifier,
node_id: event.id,
node_designation: NodeDesignation::Follower,
});
execution_information.node = Some(TestCaseNodeInformation {
id: event.id,
platform: event.platform,
connection_string: event.connection_string, connection_string: event.connection_string,
}); });
} }
@@ -413,14 +398,11 @@ impl ReportAggregator {
specifier: &ExecutionSpecifier, specifier: &ExecutionSpecifier,
) -> &mut ExecutionInformation { ) -> &mut ExecutionInformation {
let test_case_report = self.test_case_report(&specifier.test_specifier); let test_case_report = self.test_case_report(&specifier.test_specifier);
match specifier.node_designation { test_case_report
NodeDesignation::Leader => test_case_report .platform_execution
.leader_execution_information .entry(specifier.platform_identifier)
.get_or_insert_default(), .or_default()
NodeDesignation::Follower => test_case_report .get_or_insert_default()
.follower_execution_information
.get_or_insert_default(),
}
} }
} }
@@ -455,12 +437,8 @@ pub struct TestCaseReport {
/// Information on the status of the test case and whether it succeeded, failed, or was ignored. /// Information on the status of the test case and whether it succeeded, failed, or was ignored.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<TestCaseStatus>, pub status: Option<TestCaseStatus>,
/// Information related to the execution on the leader. /// Information related to the execution on one of the platforms.
#[serde(skip_serializing_if = "Option::is_none")] pub platform_execution: BTreeMap<PlatformIdentifier, Option<ExecutionInformation>>,
pub leader_execution_information: Option<ExecutionInformation>,
/// Information related to the execution on the follower.
#[serde(skip_serializing_if = "Option::is_none")]
pub follower_execution_information: Option<ExecutionInformation>,
} }
/// Information related to the status of the test. Could be that the test succeeded, failed, or that /// Information related to the status of the test. Could be that the test succeeded, failed, or that
@@ -494,7 +472,7 @@ pub struct TestCaseNodeInformation {
/// The ID of the node that this case is being executed on. /// The ID of the node that this case is being executed on.
pub id: usize, pub id: usize,
/// The platform of the node. /// The platform of the node.
pub platform: TestingPlatform, pub platform_identifier: PlatformIdentifier,
/// The connection string of the node. /// The connection string of the node.
pub connection_string: String, pub connection_string: String,
} }
+2 -8
View File
@@ -2,7 +2,7 @@
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use revive_dt_common::define_wrapper_type; use revive_dt_common::{define_wrapper_type, types::PlatformIdentifier};
use revive_dt_compiler::Mode; use revive_dt_compiler::Mode;
use revive_dt_format::{case::CaseIdx, input::StepIdx}; use revive_dt_format::{case::CaseIdx, input::StepIdx};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -27,13 +27,7 @@ pub struct TestSpecifier {
pub struct ExecutionSpecifier { pub struct ExecutionSpecifier {
pub test_specifier: Arc<TestSpecifier>, pub test_specifier: Arc<TestSpecifier>,
pub node_id: usize, pub node_id: usize,
pub node_designation: NodeDesignation, pub platform_identifier: PlatformIdentifier,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NodeDesignation {
Leader,
Follower,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
+6 -17
View File
@@ -6,8 +6,8 @@ use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
use alloy_primitives::Address; use alloy_primitives::Address;
use anyhow::Context as _; use anyhow::Context as _;
use indexmap::IndexMap; use indexmap::IndexMap;
use revive_dt_common::types::PlatformIdentifier;
use revive_dt_compiler::{CompilerInput, CompilerOutput}; use revive_dt_compiler::{CompilerInput, CompilerOutput};
use revive_dt_config::TestingPlatform;
use revive_dt_format::metadata::Metadata; use revive_dt_format::metadata::Metadata;
use revive_dt_format::{corpus::Corpus, metadata::ContractInstance}; use revive_dt_format::{corpus::Corpus, metadata::ContractInstance};
use semver::Version; use semver::Version;
@@ -412,14 +412,14 @@ macro_rules! define_event {
pub fn execution_specific_reporter( pub fn execution_specific_reporter(
&self, &self,
node_id: impl Into<usize>, node_id: impl Into<usize>,
node_designation: impl Into<$crate::common::NodeDesignation> platform_identifier: impl Into<PlatformIdentifier>
) -> [< $ident ExecutionSpecificReporter >] { ) -> [< $ident ExecutionSpecificReporter >] {
[< $ident ExecutionSpecificReporter >] { [< $ident ExecutionSpecificReporter >] {
reporter: self.reporter.clone(), reporter: self.reporter.clone(),
execution_specifier: Arc::new($crate::common::ExecutionSpecifier { execution_specifier: Arc::new($crate::common::ExecutionSpecifier {
test_specifier: self.test_specifier.clone(), test_specifier: self.test_specifier.clone(),
node_id: node_id.into(), node_id: node_id.into(),
node_designation: node_designation.into(), platform_identifier: platform_identifier.into(),
}) })
} }
} }
@@ -521,24 +521,13 @@ define_event! {
reason: String, reason: String,
}, },
/// An event emitted when the test case is assigned a leader node. /// An event emitted when the test case is assigned a leader node.
LeaderNodeAssigned { NodeAssigned {
/// A specifier for the test that the assignment is for. /// A specifier for the test that the assignment is for.
test_specifier: Arc<TestSpecifier>, test_specifier: Arc<TestSpecifier>,
/// The ID of the node that this case is being executed on. /// The ID of the node that this case is being executed on.
id: usize, id: usize,
/// The platform of the node. /// The identifier of the platform used.
platform: TestingPlatform, platform_identifier: PlatformIdentifier,
/// The connection string of the node.
connection_string: String,
},
/// An event emitted when the test case is assigned a follower node.
FollowerNodeAssigned {
/// A specifier for the test that the assignment is for.
test_specifier: Arc<TestSpecifier>,
/// The ID of the node that this case is being executed on.
id: usize,
/// The platform of the node.
platform: TestingPlatform,
/// The connection string of the node. /// The connection string of the node.
connection_string: String, connection_string: String,
}, },