mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-06-13 03:31:09 +00:00
Make our traits object safe and implement the ReviveDevNodePolkaVMResolc target. (#159)
* Generate schema for the metadata file * Groundwork for dyn traits * Make the ethereum node trait object compatible * Allow for compilers to be created in the dyn trait * Add more identifiers to the platform * Implement the dyn compiler trait for compilers * Support the dyn compiler in the builder pattern * Introduce a geth platform * Provide a common node implementation for substrate chains * Add all of the platforms that we support * Add a way to convert platform identifier into a platform * Replace infra with the dyn infra * Remoe all references to leader and follower * Remove the old traits * Remove an un-needed dependency * Update the default values for the platforms * Final set of renames * Update the default values of the cli * Update tests
This commit is contained in:
@@ -9,9 +9,9 @@ use std::{
|
||||
};
|
||||
|
||||
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_config::TestingPlatform;
|
||||
use revive_dt_core::Platform;
|
||||
use revive_dt_format::metadata::{ContractIdent, ContractInstance, Metadata};
|
||||
|
||||
use alloy::{hex::ToHexExt, json_abi::JsonAbi, primitives::Address};
|
||||
@@ -22,8 +22,6 @@ use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use tracing::{Instrument, debug, debug_span, instrument};
|
||||
|
||||
use crate::Platform;
|
||||
|
||||
pub struct CachedCompiler<'a> {
|
||||
/// The cache that stores the compiled contracts.
|
||||
artifacts_cache: ArtifactsCache,
|
||||
@@ -57,21 +55,22 @@ impl<'a> CachedCompiler<'a> {
|
||||
fields(
|
||||
metadata_file_path = %metadata_file_path.display(),
|
||||
%mode,
|
||||
platform = P::config_id().to_string()
|
||||
platform = %platform.platform_identifier()
|
||||
),
|
||||
err
|
||||
)]
|
||||
pub async fn compile_contracts<P: Platform>(
|
||||
pub async fn compile_contracts(
|
||||
&self,
|
||||
metadata: &'a Metadata,
|
||||
metadata_file_path: &'a Path,
|
||||
mode: Cow<'a, Mode>,
|
||||
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||
compiler: &P::Compiler,
|
||||
compiler: &dyn SolidityCompiler,
|
||||
platform: &dyn Platform,
|
||||
reporter: &ExecutionSpecificReporter,
|
||||
) -> Result<CompilerOutput> {
|
||||
let cache_key = CacheKey {
|
||||
platform_key: P::config_id(),
|
||||
compiler_identifier: platform.compiler_identifier(),
|
||||
compiler_version: compiler.version().clone(),
|
||||
metadata_file_path,
|
||||
solc_mode: mode.clone(),
|
||||
@@ -79,7 +78,7 @@ impl<'a> CachedCompiler<'a> {
|
||||
|
||||
let compilation_callback = || {
|
||||
async move {
|
||||
compile_contracts::<P>(
|
||||
compile_contracts(
|
||||
metadata
|
||||
.directory()
|
||||
.context("Failed to get metadata directory while preparing compilation")?,
|
||||
@@ -96,7 +95,7 @@ impl<'a> CachedCompiler<'a> {
|
||||
}
|
||||
.instrument(debug_span!(
|
||||
"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.metadata_file_path = %cache_key.metadata_file_path.display(),
|
||||
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>,
|
||||
mut files_to_compile: impl Iterator<Item = PathBuf>,
|
||||
mode: &Mode,
|
||||
deployed_libraries: Option<&HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>>,
|
||||
compiler: &P::Compiler,
|
||||
compiler: &dyn SolidityCompiler,
|
||||
reporter: &ExecutionSpecificReporter,
|
||||
) -> Result<CompilerOutput> {
|
||||
let all_sources_in_dir = FilesWithExtensionIterator::new(metadata_directory.as_ref())
|
||||
@@ -332,9 +331,8 @@ impl ArtifactsCache {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
|
||||
struct CacheKey<'a> {
|
||||
/// The platform name that this artifact was compiled for. For example, this could be EVM or
|
||||
/// PVM.
|
||||
platform_key: &'a TestingPlatform,
|
||||
/// The identifier of the used compiler.
|
||||
compiler_identifier: CompilerIdentifier,
|
||||
|
||||
/// The version of the compiler that was used to compile the artifacts.
|
||||
compiler_version: Version,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
//! The test driver handles the compilation and execution of the test cases.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use alloy::consensus::EMPTY_ROOT_HASH;
|
||||
use alloy::hex;
|
||||
use alloy::json_abi::JsonAbi;
|
||||
use alloy::network::{Ethereum, TransactionBuilder};
|
||||
use alloy::primitives::U256;
|
||||
use alloy::primitives::{TxHash, U256};
|
||||
use alloy::rpc::types::TransactionReceipt;
|
||||
use alloy::rpc::types::trace::geth::{
|
||||
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig, GethDebugTracerType,
|
||||
@@ -19,8 +18,9 @@ use alloy::{
|
||||
rpc::types::{TransactionRequest, trace::geth::DiffMode},
|
||||
};
|
||||
use anyhow::Context as _;
|
||||
use futures::TryStreamExt;
|
||||
use futures::{TryStreamExt, future::try_join_all};
|
||||
use indexmap::IndexMap;
|
||||
use revive_dt_common::types::PlatformIdentifier;
|
||||
use revive_dt_format::traits::{ResolutionContext, ResolverApi};
|
||||
use revive_dt_report::ExecutionSpecificReporter;
|
||||
use semver::Version;
|
||||
@@ -36,9 +36,7 @@ use revive_dt_node_interaction::EthereumNode;
|
||||
use tokio::try_join;
|
||||
use tracing::{Instrument, info, info_span, instrument};
|
||||
|
||||
use crate::Platform;
|
||||
|
||||
pub struct CaseState<T: Platform> {
|
||||
pub struct CaseState {
|
||||
/// A map of all of the compiled contracts for the given metadata file.
|
||||
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||
|
||||
@@ -54,14 +52,9 @@ pub struct CaseState<T: Platform> {
|
||||
|
||||
/// The execution reporter.
|
||||
execution_reporter: ExecutionSpecificReporter,
|
||||
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> CaseState<T>
|
||||
where
|
||||
T: Platform,
|
||||
{
|
||||
impl CaseState {
|
||||
pub fn new(
|
||||
compiler_version: Version,
|
||||
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||
@@ -74,7 +67,6 @@ where
|
||||
variables: Default::default(),
|
||||
compiler_version,
|
||||
execution_reporter,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +74,7 @@ where
|
||||
&mut self,
|
||||
metadata: &Metadata,
|
||||
step: &Step,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<StepOutput> {
|
||||
match step {
|
||||
Step::FunctionCall(input) => {
|
||||
@@ -113,8 +105,10 @@ where
|
||||
&mut self,
|
||||
metadata: &Metadata,
|
||||
input: &Input,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||
let resolver = node.resolver().await?;
|
||||
|
||||
let deployment_receipts = self
|
||||
.handle_input_contract_deployment(metadata, input, node)
|
||||
.await
|
||||
@@ -124,14 +118,19 @@ where
|
||||
.await
|
||||
.context("Failed during transaction execution phase of input handling")?;
|
||||
let tracing_result = self
|
||||
.handle_input_call_frame_tracing(&execution_receipt, node)
|
||||
.handle_input_call_frame_tracing(execution_receipt.transaction_hash, node)
|
||||
.await
|
||||
.context("Failed during callframe tracing phase of input handling")?;
|
||||
self.handle_input_variable_assignment(input, &tracing_result)
|
||||
.context("Failed to assign variables from callframe output")?;
|
||||
let (_, (geth_trace, diff_mode)) = try_join!(
|
||||
self.handle_input_expectations(input, &execution_receipt, node, &tracing_result),
|
||||
self.handle_input_diff(&execution_receipt, node)
|
||||
self.handle_input_expectations(
|
||||
input,
|
||||
&execution_receipt,
|
||||
resolver.as_ref(),
|
||||
&tracing_result
|
||||
),
|
||||
self.handle_input_diff(execution_receipt.transaction_hash, node)
|
||||
)
|
||||
.context("Failed while evaluating expectations and diffs in parallel")?;
|
||||
Ok((execution_receipt, geth_trace, diff_mode))
|
||||
@@ -142,7 +141,7 @@ where
|
||||
&mut self,
|
||||
metadata: &Metadata,
|
||||
balance_assertion: &BalanceAssertion,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<()> {
|
||||
self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node)
|
||||
.await
|
||||
@@ -158,7 +157,7 @@ where
|
||||
&mut self,
|
||||
metadata: &Metadata,
|
||||
storage_empty: &StorageEmptyAssertion,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<()> {
|
||||
self.handle_storage_empty_assertion_contract_deployment(metadata, storage_empty, node)
|
||||
.await
|
||||
@@ -175,7 +174,7 @@ where
|
||||
&mut self,
|
||||
metadata: &Metadata,
|
||||
input: &Input,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
|
||||
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
|
||||
for instance in input.find_all_contract_instances().into_iter() {
|
||||
@@ -220,7 +219,7 @@ where
|
||||
&mut self,
|
||||
input: &Input,
|
||||
mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<TransactionReceipt> {
|
||||
match input.method {
|
||||
// This input was already executed when `handle_input` was called. We just need to
|
||||
@@ -229,8 +228,9 @@ where
|
||||
.remove(&input.instance)
|
||||
.context("Failed to find deployment receipt for constructor call"),
|
||||
Method::Fallback | Method::FunctionName(_) => {
|
||||
let resolver = node.resolver().await?;
|
||||
let tx = match input
|
||||
.legacy_transaction(node, self.default_resolution_context())
|
||||
.legacy_transaction(resolver.as_ref(), self.default_resolution_context())
|
||||
.await
|
||||
{
|
||||
Ok(tx) => tx,
|
||||
@@ -250,11 +250,11 @@ where
|
||||
#[instrument(level = "info", skip_all)]
|
||||
async fn handle_input_call_frame_tracing(
|
||||
&self,
|
||||
execution_receipt: &TransactionReceipt,
|
||||
node: &T::Blockchain,
|
||||
tx_hash: TxHash,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<CallFrame> {
|
||||
node.trace_transaction(
|
||||
execution_receipt,
|
||||
tx_hash,
|
||||
GethDebugTracingOptions {
|
||||
tracer: Some(GethDebugTracerType::BuiltInTracer(
|
||||
GethDebugBuiltInTracerType::CallTracer,
|
||||
@@ -314,7 +314,7 @@ where
|
||||
&self,
|
||||
input: &Input,
|
||||
execution_receipt: &TransactionReceipt,
|
||||
resolver: &impl ResolverApi,
|
||||
resolver: &(impl ResolverApi + ?Sized),
|
||||
tracing_result: &CallFrame,
|
||||
) -> anyhow::Result<()> {
|
||||
// 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(
|
||||
&self,
|
||||
execution_receipt: &TransactionReceipt,
|
||||
resolver: &impl ResolverApi,
|
||||
resolver: &(impl ResolverApi + ?Sized),
|
||||
expectation: ExpectedOutput,
|
||||
tracing_result: &CallFrame,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -507,8 +507,8 @@ where
|
||||
#[instrument(level = "info", skip_all)]
|
||||
async fn handle_input_diff(
|
||||
&self,
|
||||
execution_receipt: &TransactionReceipt,
|
||||
node: &T::Blockchain,
|
||||
tx_hash: TxHash,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<(GethTrace, DiffMode)> {
|
||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||
diff_mode: Some(true),
|
||||
@@ -517,11 +517,11 @@ where
|
||||
});
|
||||
|
||||
let trace = node
|
||||
.trace_transaction(execution_receipt, trace_options)
|
||||
.trace_transaction(tx_hash, trace_options)
|
||||
.await
|
||||
.context("Failed to obtain geth prestate tracer output")?;
|
||||
let diff = node
|
||||
.state_diff(execution_receipt)
|
||||
.state_diff(tx_hash)
|
||||
.await
|
||||
.context("Failed to obtain state diff for transaction")?;
|
||||
|
||||
@@ -533,7 +533,7 @@ where
|
||||
&mut self,
|
||||
metadata: &Metadata,
|
||||
balance_assertion: &BalanceAssertion,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(instance) = balance_assertion
|
||||
.address
|
||||
@@ -562,11 +562,12 @@ where
|
||||
expected_balance: amount,
|
||||
..
|
||||
}: &BalanceAssertion,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<()> {
|
||||
let resolver = node.resolver().await?;
|
||||
let address = Address::from_slice(
|
||||
Calldata::new_compound([address_string])
|
||||
.calldata(node, self.default_resolution_context())
|
||||
.calldata(resolver.as_ref(), self.default_resolution_context())
|
||||
.await?
|
||||
.get(12..32)
|
||||
.expect("Can't fail"),
|
||||
@@ -595,7 +596,7 @@ where
|
||||
&mut self,
|
||||
metadata: &Metadata,
|
||||
storage_empty_assertion: &StorageEmptyAssertion,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(instance) = storage_empty_assertion
|
||||
.address
|
||||
@@ -624,11 +625,12 @@ where
|
||||
is_storage_empty,
|
||||
..
|
||||
}: &StorageEmptyAssertion,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<()> {
|
||||
let resolver = node.resolver().await?;
|
||||
let address = Address::from_slice(
|
||||
Calldata::new_compound([address_string])
|
||||
.calldata(node, self.default_resolution_context())
|
||||
.calldata(resolver.as_ref(), self.default_resolution_context())
|
||||
.await?
|
||||
.get(12..32)
|
||||
.expect("Can't fail"),
|
||||
@@ -667,7 +669,7 @@ where
|
||||
deployer: Address,
|
||||
calldata: Option<&Calldata>,
|
||||
value: Option<EtherValue>,
|
||||
node: &T::Blockchain,
|
||||
node: &dyn EthereumNode,
|
||||
) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
|
||||
if let Some((_, address, abi)) = self.deployed_contracts.get(contract_instance) {
|
||||
return Ok((*address, abi.clone(), None));
|
||||
@@ -710,8 +712,9 @@ where
|
||||
};
|
||||
|
||||
if let Some(calldata) = calldata {
|
||||
let resolver = node.resolver().await?;
|
||||
let calldata = calldata
|
||||
.calldata(node, self.default_resolution_context())
|
||||
.calldata(resolver.as_ref(), self.default_resolution_context())
|
||||
.await?;
|
||||
code.extend(calldata);
|
||||
}
|
||||
@@ -728,11 +731,7 @@ where
|
||||
let receipt = match node.execute_transaction(tx).await {
|
||||
Ok(receipt) => receipt,
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
node = std::any::type_name::<T>(),
|
||||
?error,
|
||||
"Contract deployment transaction failed."
|
||||
);
|
||||
tracing::error!(?error, "Contract deployment transaction failed.");
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
@@ -763,36 +762,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CaseDriver<'a, Leader: Platform, Follower: Platform> {
|
||||
pub struct CaseDriver<'a> {
|
||||
metadata: &'a Metadata,
|
||||
case: &'a Case,
|
||||
leader_node: &'a Leader::Blockchain,
|
||||
follower_node: &'a Follower::Blockchain,
|
||||
leader_state: CaseState<Leader>,
|
||||
follower_state: CaseState<Follower>,
|
||||
platform_state: Vec<(&'a dyn EthereumNode, PlatformIdentifier, CaseState)>,
|
||||
}
|
||||
|
||||
impl<'a, L, F> CaseDriver<'a, L, F>
|
||||
where
|
||||
L: Platform,
|
||||
F: Platform,
|
||||
{
|
||||
impl<'a> CaseDriver<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
metadata: &'a Metadata,
|
||||
case: &'a Case,
|
||||
leader_node: &'a L::Blockchain,
|
||||
follower_node: &'a F::Blockchain,
|
||||
leader_state: CaseState<L>,
|
||||
follower_state: CaseState<F>,
|
||||
) -> CaseDriver<'a, L, F> {
|
||||
platform_state: Vec<(&'a dyn EthereumNode, PlatformIdentifier, CaseState)>,
|
||||
) -> CaseDriver<'a> {
|
||||
Self {
|
||||
metadata,
|
||||
case,
|
||||
leader_node,
|
||||
follower_node,
|
||||
leader_state,
|
||||
follower_state,
|
||||
platform_state,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,42 +791,44 @@ where
|
||||
.enumerate()
|
||||
.map(|(idx, v)| (StepIdx::new(idx), v))
|
||||
{
|
||||
let (leader_step_output, follower_step_output) = try_join!(
|
||||
self.leader_state
|
||||
.handle_step(self.metadata, &step, self.leader_node)
|
||||
.instrument(info_span!(
|
||||
"Handling Step",
|
||||
%step_idx,
|
||||
target = "Leader",
|
||||
)),
|
||||
self.follower_state
|
||||
.handle_step(self.metadata, &step, self.follower_node)
|
||||
.instrument(info_span!(
|
||||
"Handling Step",
|
||||
%step_idx,
|
||||
target = "Follower",
|
||||
))
|
||||
)?;
|
||||
// Run this step concurrently across all platforms; short-circuit on first failure
|
||||
let metadata = self.metadata;
|
||||
let step_futs =
|
||||
self.platform_state
|
||||
.iter_mut()
|
||||
.map(|(node, platform_id, case_state)| {
|
||||
let platform_id = *platform_id;
|
||||
let node_ref = *node;
|
||||
let step_clone = step.clone();
|
||||
let span = info_span!(
|
||||
"Handling Step",
|
||||
%step_idx,
|
||||
platform = %platform_id,
|
||||
);
|
||||
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) {
|
||||
(StepOutput::FunctionCall(..), StepOutput::FunctionCall(..)) => {
|
||||
// TODO: We need to actually work out how/if we will compare the diff between
|
||||
// the leader and the follower. The diffs are almost guaranteed to be different
|
||||
// from leader and follower and therefore without an actual strategy for this
|
||||
// we have something that's guaranteed to fail. Even a simple call to some
|
||||
// contract will produce two non-equal diffs because on the leader the contract
|
||||
// has address X and on the follower it has address Y. On the leader contract X
|
||||
// contains address A in the state and on the follower it contains address B. So
|
||||
// this isn't exactly a straightforward thing to do and I'm not even sure that
|
||||
// it's possible to do. Once we have an actual strategy for doing the diffs we
|
||||
// will implement it here. Until then, this remains empty.
|
||||
match try_join_all(step_futs).await {
|
||||
Ok(_outputs) => {
|
||||
// All platforms succeeded for this step
|
||||
steps_executed += 1;
|
||||
}
|
||||
Err((platform_id, error)) => {
|
||||
tracing::error!(
|
||||
%step_idx,
|
||||
platform = %platform_id,
|
||||
?error,
|
||||
"Step failed on platform",
|
||||
);
|
||||
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)
|
||||
|
||||
+350
-25
@@ -3,45 +3,370 @@
|
||||
//! This crate defines the testing configuration and
|
||||
//! provides a helper utility to execute tests.
|
||||
|
||||
use revive_dt_compiler::{SolidityCompiler, revive_resolc, solc};
|
||||
use revive_dt_config::TestingPlatform;
|
||||
use revive_dt_format::traits::ResolverApi;
|
||||
use revive_dt_node::{Node, geth, kitchensink::KitchensinkNode};
|
||||
use std::{
|
||||
pin::Pin,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use alloy::genesis::Genesis;
|
||||
use anyhow::Context as _;
|
||||
use revive_dt_common::types::*;
|
||||
use revive_dt_compiler::{SolidityCompiler, revive_resolc::Resolc, solc::Solc};
|
||||
use revive_dt_config::*;
|
||||
use revive_dt_node::{Node, geth::GethNode, substrate::SubstrateNode};
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
use tracing::info;
|
||||
|
||||
pub mod driver;
|
||||
|
||||
/// One platform can be tested differentially against another.
|
||||
///
|
||||
/// For this we need a blockchain node implementation and a compiler.
|
||||
/// A trait that describes the interface for the platforms that are supported by the tool.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub trait Platform {
|
||||
type Blockchain: EthereumNode + Node + ResolverApi;
|
||||
type Compiler: SolidityCompiler;
|
||||
/// Returns the identifier of this platform. This is a combination of the node and the compiler
|
||||
/// used.
|
||||
fn platform_identifier(&self) -> PlatformIdentifier;
|
||||
|
||||
/// Returns the matching [TestingPlatform] of the [revive_dt_config::Arguments].
|
||||
fn config_id() -> &'static TestingPlatform;
|
||||
/// Returns a full identifier for the platform.
|
||||
fn full_identifier(&self) -> (NodeIdentifier, VmIdentifier, CompilerIdentifier) {
|
||||
(
|
||||
self.node_identifier(),
|
||||
self.vm_identifier(),
|
||||
self.compiler_identifier(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the identifier of the node used.
|
||||
fn node_identifier(&self) -> NodeIdentifier;
|
||||
|
||||
/// Returns the identifier of the vm used.
|
||||
fn vm_identifier(&self) -> VmIdentifier;
|
||||
|
||||
/// Returns the identifier of the compiler used.
|
||||
fn compiler_identifier(&self) -> CompilerIdentifier;
|
||||
|
||||
/// Creates a new node for the platform by spawning a new thread, creating the node object,
|
||||
/// initializing it, spawning it, and waiting for it to start up.
|
||||
fn new_node(
|
||||
&self,
|
||||
context: Context,
|
||||
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>>;
|
||||
|
||||
/// Creates a new compiler for the provided platform
|
||||
fn new_compiler(
|
||||
&self,
|
||||
context: Context,
|
||||
version: Option<VersionOrRequirement>,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Geth;
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||
pub struct GethEvmSolcPlatform;
|
||||
|
||||
impl Platform for Geth {
|
||||
type Blockchain = geth::GethNode;
|
||||
type Compiler = solc::Solc;
|
||||
impl Platform for GethEvmSolcPlatform {
|
||||
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||
PlatformIdentifier::GethEvmSolc
|
||||
}
|
||||
|
||||
fn config_id() -> &'static TestingPlatform {
|
||||
&TestingPlatform::Geth
|
||||
fn node_identifier(&self) -> NodeIdentifier {
|
||||
NodeIdentifier::Geth
|
||||
}
|
||||
|
||||
fn vm_identifier(&self) -> VmIdentifier {
|
||||
VmIdentifier::Evm
|
||||
}
|
||||
|
||||
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||
CompilerIdentifier::Solc
|
||||
}
|
||||
|
||||
fn new_node(
|
||||
&self,
|
||||
context: Context,
|
||||
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||
let genesis = genesis_configuration.genesis()?.clone();
|
||||
Ok(thread::spawn(move || {
|
||||
let node = GethNode::new(context);
|
||||
let node = spawn_node::<GethNode>(node, genesis)?;
|
||||
Ok(Box::new(node) as Box<_>)
|
||||
}))
|
||||
}
|
||||
|
||||
fn new_compiler(
|
||||
&self,
|
||||
context: Context,
|
||||
version: Option<VersionOrRequirement>,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||
Box::pin(async move {
|
||||
let compiler = Solc::new(context, version).await;
|
||||
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Kitchensink;
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||
pub struct KitchensinkPolkavmResolcPlatform;
|
||||
|
||||
impl Platform for Kitchensink {
|
||||
type Blockchain = KitchensinkNode;
|
||||
type Compiler = revive_resolc::Resolc;
|
||||
impl Platform for KitchensinkPolkavmResolcPlatform {
|
||||
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||
PlatformIdentifier::KitchensinkPolkavmResolc
|
||||
}
|
||||
|
||||
fn config_id() -> &'static TestingPlatform {
|
||||
&TestingPlatform::Kitchensink
|
||||
fn node_identifier(&self) -> NodeIdentifier {
|
||||
NodeIdentifier::Kitchensink
|
||||
}
|
||||
|
||||
fn vm_identifier(&self) -> VmIdentifier {
|
||||
VmIdentifier::PolkaVM
|
||||
}
|
||||
|
||||
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||
CompilerIdentifier::Resolc
|
||||
}
|
||||
|
||||
fn new_node(
|
||||
&self,
|
||||
context: Context,
|
||||
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||
let kitchensink_path = AsRef::<KitchensinkConfiguration>::as_ref(&context)
|
||||
.path
|
||||
.clone();
|
||||
let genesis = genesis_configuration.genesis()?.clone();
|
||||
Ok(thread::spawn(move || {
|
||||
let node = SubstrateNode::new(
|
||||
kitchensink_path,
|
||||
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
|
||||
context,
|
||||
);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
Ok(Box::new(node) as Box<_>)
|
||||
}))
|
||||
}
|
||||
|
||||
fn new_compiler(
|
||||
&self,
|
||||
context: Context,
|
||||
version: Option<VersionOrRequirement>,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||
Box::pin(async move {
|
||||
let compiler = Resolc::new(context, version).await;
|
||||
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||
pub struct KitchensinkRevmSolcPlatform;
|
||||
|
||||
impl Platform for KitchensinkRevmSolcPlatform {
|
||||
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||
PlatformIdentifier::KitchensinkRevmSolc
|
||||
}
|
||||
|
||||
fn node_identifier(&self) -> NodeIdentifier {
|
||||
NodeIdentifier::Kitchensink
|
||||
}
|
||||
|
||||
fn vm_identifier(&self) -> VmIdentifier {
|
||||
VmIdentifier::Evm
|
||||
}
|
||||
|
||||
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||
CompilerIdentifier::Solc
|
||||
}
|
||||
|
||||
fn new_node(
|
||||
&self,
|
||||
context: Context,
|
||||
) -> anyhow::Result<JoinHandle<anyhow::Result<Box<dyn EthereumNode + Send + Sync>>>> {
|
||||
let genesis_configuration = AsRef::<GenesisConfiguration>::as_ref(&context);
|
||||
let kitchensink_path = AsRef::<KitchensinkConfiguration>::as_ref(&context)
|
||||
.path
|
||||
.clone();
|
||||
let genesis = genesis_configuration.genesis()?.clone();
|
||||
Ok(thread::spawn(move || {
|
||||
let node = SubstrateNode::new(
|
||||
kitchensink_path,
|
||||
SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND,
|
||||
context,
|
||||
);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
Ok(Box::new(node) as Box<_>)
|
||||
}))
|
||||
}
|
||||
|
||||
fn new_compiler(
|
||||
&self,
|
||||
context: Context,
|
||||
version: Option<VersionOrRequirement>,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||
Box::pin(async move {
|
||||
let compiler = Solc::new(context, version).await;
|
||||
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||
pub struct ReviveDevNodePolkavmResolcPlatform;
|
||||
|
||||
impl Platform for ReviveDevNodePolkavmResolcPlatform {
|
||||
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||
PlatformIdentifier::ReviveDevNodePolkavmResolc
|
||||
}
|
||||
|
||||
fn node_identifier(&self) -> NodeIdentifier {
|
||||
NodeIdentifier::ReviveDevNode
|
||||
}
|
||||
|
||||
fn vm_identifier(&self) -> VmIdentifier {
|
||||
VmIdentifier::PolkaVM
|
||||
}
|
||||
|
||||
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||
CompilerIdentifier::Resolc
|
||||
}
|
||||
|
||||
fn new_node(
|
||||
&self,
|
||||
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 genesis = genesis_configuration.genesis()?.clone();
|
||||
Ok(thread::spawn(move || {
|
||||
let node = SubstrateNode::new(
|
||||
revive_dev_node_path,
|
||||
SubstrateNode::REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND,
|
||||
context,
|
||||
);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
Ok(Box::new(node) as Box<_>)
|
||||
}))
|
||||
}
|
||||
|
||||
fn new_compiler(
|
||||
&self,
|
||||
context: Context,
|
||||
version: Option<VersionOrRequirement>,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||
Box::pin(async move {
|
||||
let compiler = Resolc::new(context, version).await;
|
||||
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||
pub struct ReviveDevNodeRevmSolcPlatform;
|
||||
|
||||
impl Platform for ReviveDevNodeRevmSolcPlatform {
|
||||
fn platform_identifier(&self) -> PlatformIdentifier {
|
||||
PlatformIdentifier::ReviveDevNodeRevmSolc
|
||||
}
|
||||
|
||||
fn node_identifier(&self) -> NodeIdentifier {
|
||||
NodeIdentifier::ReviveDevNode
|
||||
}
|
||||
|
||||
fn vm_identifier(&self) -> VmIdentifier {
|
||||
VmIdentifier::Evm
|
||||
}
|
||||
|
||||
fn compiler_identifier(&self) -> CompilerIdentifier {
|
||||
CompilerIdentifier::Solc
|
||||
}
|
||||
|
||||
fn new_node(
|
||||
&self,
|
||||
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 genesis = genesis_configuration.genesis()?.clone();
|
||||
Ok(thread::spawn(move || {
|
||||
let node = SubstrateNode::new(
|
||||
revive_dev_node_path,
|
||||
SubstrateNode::REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND,
|
||||
context,
|
||||
);
|
||||
let node = spawn_node(node, genesis)?;
|
||||
Ok(Box::new(node) as Box<_>)
|
||||
}))
|
||||
}
|
||||
|
||||
fn new_compiler(
|
||||
&self,
|
||||
context: Context,
|
||||
version: Option<VersionOrRequirement>,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn SolidityCompiler>>>>> {
|
||||
Box::pin(async move {
|
||||
let compiler = Solc::new(context, version).await;
|
||||
compiler.map(|compiler| Box::new(compiler) as Box<dyn SolidityCompiler>)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlatformIdentifier> for Box<dyn Platform> {
|
||||
fn from(value: PlatformIdentifier) -> Self {
|
||||
match value {
|
||||
PlatformIdentifier::GethEvmSolc => Box::new(GethEvmSolcPlatform) as Box<_>,
|
||||
PlatformIdentifier::KitchensinkPolkavmResolc => {
|
||||
Box::new(KitchensinkPolkavmResolcPlatform) as Box<_>
|
||||
}
|
||||
PlatformIdentifier::KitchensinkRevmSolc => {
|
||||
Box::new(KitchensinkRevmSolcPlatform) as Box<_>
|
||||
}
|
||||
PlatformIdentifier::ReviveDevNodePolkavmResolc => {
|
||||
Box::new(ReviveDevNodePolkavmResolcPlatform) as Box<_>
|
||||
}
|
||||
PlatformIdentifier::ReviveDevNodeRevmSolc => {
|
||||
Box::new(ReviveDevNodeRevmSolcPlatform) as Box<_>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlatformIdentifier> for &dyn Platform {
|
||||
fn from(value: PlatformIdentifier) -> Self {
|
||||
match value {
|
||||
PlatformIdentifier::GethEvmSolc => &GethEvmSolcPlatform as &dyn Platform,
|
||||
PlatformIdentifier::KitchensinkPolkavmResolc => {
|
||||
&KitchensinkPolkavmResolcPlatform as &dyn Platform
|
||||
}
|
||||
PlatformIdentifier::KitchensinkRevmSolc => {
|
||||
&KitchensinkRevmSolcPlatform as &dyn Platform
|
||||
}
|
||||
PlatformIdentifier::ReviveDevNodePolkavmResolc => {
|
||||
&ReviveDevNodePolkavmResolcPlatform as &dyn Platform
|
||||
}
|
||||
PlatformIdentifier::ReviveDevNodeRevmSolc => {
|
||||
&ReviveDevNodeRevmSolcPlatform as &dyn Platform
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_node<T: Node + EthereumNode + Send + Sync>(
|
||||
mut node: T,
|
||||
genesis: Genesis,
|
||||
) -> anyhow::Result<T> {
|
||||
info!(
|
||||
id = node.id(),
|
||||
connection_string = node.connection_string(),
|
||||
"Spawning node"
|
||||
);
|
||||
node.spawn(genesis)
|
||||
.context("Failed to spawn node process")?;
|
||||
info!(
|
||||
id = node.id(),
|
||||
connection_string = node.connection_string(),
|
||||
"Spawned node"
|
||||
);
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
+299
-349
@@ -1,8 +1,9 @@
|
||||
mod cached_compiler;
|
||||
mod pool;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
collections::{BTreeSet, HashMap},
|
||||
io::{BufWriter, Write, stderr},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
@@ -20,20 +21,19 @@ use futures::{Stream, StreamExt};
|
||||
use indexmap::{IndexMap, indexmap};
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
use revive_dt_report::{
|
||||
NodeDesignation, ReportAggregator, Reporter, ReporterEvent, TestCaseStatus,
|
||||
ExecutionSpecificReporter, ReportAggregator, Reporter, ReporterEvent, TestCaseStatus,
|
||||
TestSpecificReporter, TestSpecifier,
|
||||
};
|
||||
use schemars::schema_for;
|
||||
use serde_json::{Value, json};
|
||||
use tokio::try_join;
|
||||
use tracing::{debug, error, info, info_span, instrument};
|
||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||
|
||||
use revive_dt_common::{iterators::EitherIter, types::Mode};
|
||||
use revive_dt_compiler::{CompilerOutput, SolidityCompiler};
|
||||
use revive_dt_compiler::SolidityCompiler;
|
||||
use revive_dt_config::{Context, *};
|
||||
use revive_dt_core::{
|
||||
Geth, Kitchensink, Platform,
|
||||
Platform,
|
||||
driver::{CaseDriver, CaseState},
|
||||
};
|
||||
use revive_dt_format::{
|
||||
@@ -43,9 +43,9 @@ use revive_dt_format::{
|
||||
metadata::{ContractPathAndIdent, Metadata, MetadataFile},
|
||||
mode::ParsedMode,
|
||||
};
|
||||
use revive_dt_node::{Node, pool::NodePool};
|
||||
|
||||
use crate::cached_compiler::CachedCompiler;
|
||||
use crate::pool::NodePool;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let (writer, _guard) = tracing_appender::non_blocking::NonBlockingBuilder::default()
|
||||
@@ -112,7 +112,7 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
#[instrument(level = "debug", name = "Collecting Corpora", skip_all)]
|
||||
fn collect_corpora(
|
||||
context: &ExecutionContext,
|
||||
context: &TestExecutionContext,
|
||||
) -> anyhow::Result<HashMap<Corpus, Vec<MetadataFile>>> {
|
||||
let mut corpora = HashMap::new();
|
||||
|
||||
@@ -133,32 +133,35 @@ fn collect_corpora(
|
||||
Ok(corpora)
|
||||
}
|
||||
|
||||
async fn run_driver<L, F>(
|
||||
context: ExecutionContext,
|
||||
async fn run_driver(
|
||||
context: TestExecutionContext,
|
||||
metadata_files: &[MetadataFile],
|
||||
reporter: Reporter,
|
||||
report_aggregator_task: impl Future<Output = anyhow::Result<()>>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
L: Platform,
|
||||
F: Platform,
|
||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||
{
|
||||
let leader_nodes = NodePool::<L::Blockchain>::new(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")?;
|
||||
platforms: Vec<&dyn Platform>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut nodes = Vec::<(&dyn Platform, NodePool)>::new();
|
||||
for platform in platforms.into_iter() {
|
||||
let pool = NodePool::new(Context::ExecuteTests(Box::new(context.clone())), platform)
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
platform_identifier = %platform.platform_identifier(),
|
||||
"Failed to initialize the node pool for the platform."
|
||||
)
|
||||
})
|
||||
.context("Failed to initialize the node pool")?;
|
||||
nodes.push((platform, pool));
|
||||
}
|
||||
|
||||
let tests_stream = tests_stream(
|
||||
&context,
|
||||
metadata_files.iter(),
|
||||
&leader_nodes,
|
||||
&follower_nodes,
|
||||
nodes.as_slice(),
|
||||
reporter.clone(),
|
||||
)
|
||||
.await;
|
||||
let driver_task = start_driver_task::<L, F>(&context, tests_stream)
|
||||
let driver_task = start_driver_task(&context, tests_stream)
|
||||
.await
|
||||
.context("Failed to start driver task")?;
|
||||
let cli_reporting_task = start_cli_reporting_task(reporter);
|
||||
@@ -169,19 +172,12 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn tests_stream<'a, L, F>(
|
||||
args: &ExecutionContext,
|
||||
async fn tests_stream<'a>(
|
||||
args: &TestExecutionContext,
|
||||
metadata_files: impl IntoIterator<Item = &'a MetadataFile> + Clone,
|
||||
leader_node_pool: &'a NodePool<L::Blockchain>,
|
||||
follower_node_pool: &'a NodePool<F::Blockchain>,
|
||||
nodes: &'a [(&dyn Platform, NodePool)],
|
||||
reporter: Reporter,
|
||||
) -> impl Stream<Item = Test<'a, L, F>>
|
||||
where
|
||||
L: Platform,
|
||||
F: Platform,
|
||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||
{
|
||||
) -> impl Stream<Item = Test<'a>> {
|
||||
let tests = metadata_files
|
||||
.into_iter()
|
||||
.flat_map(|metadata_file| {
|
||||
@@ -231,35 +227,36 @@ where
|
||||
stream::iter(tests.into_iter())
|
||||
.filter_map(
|
||||
move |(metadata_file, case_idx, case, mode, reporter)| async move {
|
||||
let leader_compiler = <L::Compiler as SolidityCompiler>::new(
|
||||
args,
|
||||
mode.version.clone().map(Into::into),
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| error!(?err, "Failed to instantiate the leader compiler"))
|
||||
.ok()?;
|
||||
let mut platforms = Vec::new();
|
||||
for (platform, node_pool) in nodes.iter() {
|
||||
let node = node_pool.round_robbin();
|
||||
let compiler = platform
|
||||
.new_compiler(
|
||||
Context::ExecuteTests(Box::new(args.clone())),
|
||||
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(
|
||||
args,
|
||||
mode.version.clone().map(Into::into),
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| error!(?err, "Failed to instantiate the follower compiler"))
|
||||
.ok()?;
|
||||
let reporter = reporter
|
||||
.execution_specific_reporter(node.id(), platform.platform_identifier());
|
||||
platforms.push((*platform, node, compiler, reporter));
|
||||
}
|
||||
|
||||
let leader_node = leader_node_pool.round_robbin();
|
||||
let follower_node = follower_node_pool.round_robbin();
|
||||
|
||||
Some(Test::<L, F> {
|
||||
Some(Test {
|
||||
metadata: metadata_file,
|
||||
metadata_file_path: metadata_file.metadata_file_path.as_path(),
|
||||
mode: mode.clone(),
|
||||
case_idx: CaseIdx::new(case_idx),
|
||||
case,
|
||||
leader_node,
|
||||
follower_node,
|
||||
leader_compiler,
|
||||
follower_compiler,
|
||||
platforms,
|
||||
reporter,
|
||||
})
|
||||
},
|
||||
@@ -293,18 +290,10 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
async fn start_driver_task<'a, L, F>(
|
||||
context: &ExecutionContext,
|
||||
tests: impl Stream<Item = Test<'a, L, F>>,
|
||||
) -> 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,
|
||||
{
|
||||
async fn start_driver_task<'a>(
|
||||
context: &TestExecutionContext,
|
||||
tests: impl Stream<Item = Test<'a>>,
|
||||
) -> anyhow::Result<impl Future<Output = ()>> {
|
||||
info!("Starting driver task");
|
||||
|
||||
let cached_compiler = Arc::new(
|
||||
@@ -327,23 +316,18 @@ where
|
||||
let cached_compiler = cached_compiler.clone();
|
||||
|
||||
async move {
|
||||
test.reporter
|
||||
.report_leader_node_assigned_event(
|
||||
test.leader_node.id(),
|
||||
*L::config_id(),
|
||||
test.leader_node.connection_string(),
|
||||
)
|
||||
.expect("Can't fail");
|
||||
test.reporter
|
||||
.report_follower_node_assigned_event(
|
||||
test.follower_node.id(),
|
||||
*F::config_id(),
|
||||
test.follower_node.connection_string(),
|
||||
)
|
||||
.expect("Can't fail");
|
||||
for (platform, node, _, _) in test.platforms.iter() {
|
||||
test.reporter
|
||||
.report_node_assigned_event(
|
||||
node.id(),
|
||||
platform.platform_identifier(),
|
||||
node.connection_string(),
|
||||
)
|
||||
.expect("Can't fail");
|
||||
}
|
||||
|
||||
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 {
|
||||
Ok(steps_executed) => reporter
|
||||
@@ -449,230 +433,174 @@ async fn start_cli_reporting_task(reporter: Reporter) {
|
||||
mode = %test.mode,
|
||||
case_idx = %test.case_idx,
|
||||
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>(
|
||||
test: Test<'a, L, F>,
|
||||
async fn handle_case_driver<'a>(
|
||||
test: &Test<'a>,
|
||||
cached_compiler: Arc<CachedCompiler<'a>>,
|
||||
) -> anyhow::Result<usize>
|
||||
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,
|
||||
{
|
||||
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);
|
||||
) -> anyhow::Result<usize> {
|
||||
let platform_state = stream::iter(test.platforms.iter())
|
||||
// Compiling the pre-link contracts.
|
||||
.filter_map(|(platform, node, compiler, reporter)| {
|
||||
let cached_compiler = cached_compiler.clone();
|
||||
|
||||
let (
|
||||
CompilerOutput {
|
||||
contracts: leader_pre_link_contracts,
|
||||
},
|
||||
CompilerOutput {
|
||||
contracts: follower_pre_link_contracts,
|
||||
},
|
||||
) = try_join!(
|
||||
cached_compiler.compile_contracts::<L>(
|
||||
test.metadata,
|
||||
test.metadata_file_path,
|
||||
test.mode.clone(),
|
||||
None,
|
||||
&test.leader_compiler,
|
||||
&leader_reporter,
|
||||
),
|
||||
cached_compiler.compile_contracts::<F>(
|
||||
test.metadata,
|
||||
test.metadata_file_path,
|
||||
test.mode.clone(),
|
||||
None,
|
||||
&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)
|
||||
async move {
|
||||
let compiler_output = cached_compiler
|
||||
.compile_contracts(
|
||||
test.metadata,
|
||||
test.metadata_file_path,
|
||||
test.mode.clone(),
|
||||
None,
|
||||
compiler.as_ref(),
|
||||
*platform,
|
||||
reporter,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
platform_identifier = %platform.platform_identifier(),
|
||||
"Pre-linking compilation failed"
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
Some((test, platform, node, compiler, reporter, compiler_output))
|
||||
}
|
||||
};
|
||||
let follower_code = match alloy::hex::decode(follower_code) {
|
||||
Ok(code) => code,
|
||||
Err(error) => {
|
||||
anyhow::bail!("Failed to hex-decode the byte code {}", error)
|
||||
}
|
||||
};
|
||||
})
|
||||
// Deploying the libraries for the platform.
|
||||
.filter_map(
|
||||
|(test, platform, node, compiler, reporter, compiler_output)| async move {
|
||||
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
|
||||
// doing the deployments from different accounts and therefore we're not slowed down by
|
||||
// the nonce.
|
||||
let deployer_address = test
|
||||
.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 ContractPathAndIdent {
|
||||
contract_source_path: library_source_path,
|
||||
contract_ident: library_ident,
|
||||
} = contract_sources.remove(library_instance)?;
|
||||
|
||||
let (leader_receipt, follower_receipt) = try_join!(
|
||||
test.leader_node.execute_transaction(leader_tx),
|
||||
test.follower_node.execute_transaction(follower_tx)
|
||||
)?;
|
||||
let (code, abi) = compiler_output
|
||||
.contracts
|
||||
.get(&library_source_path)
|
||||
.and_then(|contracts| contracts.get(library_ident.as_str()))?;
|
||||
|
||||
debug!(
|
||||
?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 code = alloy::hex::decode(code).ok()?;
|
||||
|
||||
let leader_library_address = leader_receipt
|
||||
.contract_address
|
||||
.context("Contract deployment didn't return an address")?;
|
||||
let follower_library_address = follower_receipt
|
||||
.contract_address
|
||||
.context("Contract deployment didn't return an address")?;
|
||||
// Getting the deployer address from the cases themselves. This is to ensure
|
||||
// that we're doing the deployments from different accounts and therefore we're
|
||||
// not slowed down by the nonce.
|
||||
let deployer_address = test
|
||||
.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 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(
|
||||
library_instance.clone(),
|
||||
(
|
||||
library_ident.clone(),
|
||||
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<_, _>>(),
|
||||
)?;
|
||||
}
|
||||
debug!(
|
||||
?library_instance,
|
||||
platform_identifier = %platform.platform_identifier(),
|
||||
"Deployed library"
|
||||
);
|
||||
|
||||
let (
|
||||
CompilerOutput {
|
||||
contracts: leader_post_link_contracts,
|
||||
},
|
||||
CompilerOutput {
|
||||
contracts: follower_post_link_contracts,
|
||||
},
|
||||
) = try_join!(
|
||||
cached_compiler.compile_contracts::<L>(
|
||||
test.metadata,
|
||||
test.metadata_file_path,
|
||||
test.mode.clone(),
|
||||
leader_deployed_libraries.as_ref(),
|
||||
&test.leader_compiler,
|
||||
&leader_reporter,
|
||||
),
|
||||
cached_compiler.compile_contracts::<F>(
|
||||
test.metadata,
|
||||
test.metadata_file_path,
|
||||
test.mode.clone(),
|
||||
follower_deployed_libraries.as_ref(),
|
||||
&test.follower_compiler,
|
||||
&follower_reporter
|
||||
let library_address = receipt.contract_address?;
|
||||
|
||||
deployed_libraries.get_or_insert_default().insert(
|
||||
library_instance.clone(),
|
||||
(library_ident.clone(), library_address, abi.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
Some((
|
||||
test,
|
||||
platform,
|
||||
node,
|
||||
compiler,
|
||||
reporter,
|
||||
compiler_output,
|
||||
deployed_libraries,
|
||||
))
|
||||
},
|
||||
)
|
||||
)
|
||||
.context("Failed to compile post-link contracts for leader/follower in parallel")?;
|
||||
// Compiling the post-link contracts.
|
||||
.filter_map(
|
||||
|(test, platform, node, compiler, reporter, _, deployed_libraries)| {
|
||||
let cached_compiler = cached_compiler.clone();
|
||||
|
||||
let leader_state = CaseState::<L>::new(
|
||||
test.leader_compiler.version().clone(),
|
||||
leader_post_link_contracts,
|
||||
leader_deployed_libraries.unwrap_or_default(),
|
||||
leader_reporter,
|
||||
);
|
||||
let follower_state = CaseState::<F>::new(
|
||||
test.follower_compiler.version().clone(),
|
||||
follower_post_link_contracts,
|
||||
follower_deployed_libraries.unwrap_or_default(),
|
||||
follower_reporter,
|
||||
);
|
||||
async move {
|
||||
let compiler_output = cached_compiler
|
||||
.compile_contracts(
|
||||
test.metadata,
|
||||
test.metadata_file_path,
|
||||
test.mode.clone(),
|
||||
deployed_libraries.as_ref(),
|
||||
compiler.as_ref(),
|
||||
*platform,
|
||||
reporter,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
platform_identifier = %platform.platform_identifier(),
|
||||
"Pre-linking compilation failed"
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
let mut driver = CaseDriver::<L, F>::new(
|
||||
test.metadata,
|
||||
test.case,
|
||||
test.leader_node,
|
||||
test.follower_node,
|
||||
leader_state,
|
||||
follower_state,
|
||||
);
|
||||
let case_state = CaseState::new(
|
||||
compiler.version().clone(),
|
||||
compiler_output.contracts,
|
||||
deployed_libraries.unwrap_or_default(),
|
||||
reporter.clone(),
|
||||
);
|
||||
|
||||
Some((*node, platform.platform_identifier(), case_state))
|
||||
}
|
||||
},
|
||||
)
|
||||
// Collect
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
let mut driver = CaseDriver::new(test.metadata, test.case, platform_state);
|
||||
driver
|
||||
.execute()
|
||||
.await
|
||||
@@ -680,41 +608,43 @@ where
|
||||
}
|
||||
|
||||
async fn execute_corpus(
|
||||
context: ExecutionContext,
|
||||
context: TestExecutionContext,
|
||||
tests: &[MetadataFile],
|
||||
reporter: Reporter,
|
||||
report_aggregator_task: impl Future<Output = anyhow::Result<()>>,
|
||||
) -> anyhow::Result<()> {
|
||||
match (&context.leader, &context.follower) {
|
||||
(TestingPlatform::Geth, TestingPlatform::Kitchensink) => {
|
||||
run_driver::<Geth, Kitchensink>(context, tests, reporter, report_aggregator_task)
|
||||
.await?
|
||||
}
|
||||
(TestingPlatform::Geth, TestingPlatform::Geth) => {
|
||||
run_driver::<Geth, Geth>(context, tests, reporter, report_aggregator_task).await?
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
let platforms = context
|
||||
.platforms
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.map(Into::<&dyn Platform>::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
run_driver(context, tests, reporter, report_aggregator_task, platforms).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// this represents a single "test"; a mode, path and collection of cases.
|
||||
#[derive(Clone)]
|
||||
struct Test<'a, L: Platform, F: Platform> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
struct Test<'a> {
|
||||
metadata: &'a MetadataFile,
|
||||
metadata_file_path: &'a Path,
|
||||
mode: Cow<'a, Mode>,
|
||||
case_idx: CaseIdx,
|
||||
case: &'a Case,
|
||||
leader_node: &'a <L as Platform>::Blockchain,
|
||||
follower_node: &'a <F as Platform>::Blockchain,
|
||||
leader_compiler: L::Compiler,
|
||||
follower_compiler: F::Compiler,
|
||||
platforms: Vec<(
|
||||
&'a dyn Platform,
|
||||
&'a dyn EthereumNode,
|
||||
Box<dyn SolidityCompiler>,
|
||||
ExecutionSpecificReporter,
|
||||
)>,
|
||||
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.
|
||||
pub fn check_compatibility(&self) -> TestCheckFunctionResult {
|
||||
self.check_metadata_file_ignored()?;
|
||||
@@ -743,74 +673,94 @@ 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 {
|
||||
let leader_support =
|
||||
<L::Blockchain as Node>::matches_target(self.metadata.targets.as_deref());
|
||||
let follower_support =
|
||||
<F::Blockchain as Node>::matches_target(self.metadata.targets.as_deref());
|
||||
let is_allowed = leader_support && follower_support;
|
||||
let mut error_map = indexmap! {
|
||||
"test_desired_targets" => json!(self.metadata.targets.as_ref()),
|
||||
};
|
||||
let mut is_allowed = true;
|
||||
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 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err((
|
||||
"Either the leader or the follower do not support the target desired by the test.",
|
||||
indexmap! {
|
||||
"test_desired_targets" => json!(self.metadata.targets.as_ref()),
|
||||
"leader_support" => json!(leader_support),
|
||||
"follower_support" => json!(follower_support),
|
||||
},
|
||||
"One of the platforms do do not support the targets allowed by the test.",
|
||||
error_map,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Checks for the compatibility of the EVM version with the leader and follower nodes.
|
||||
// Checks for the compatibility of the EVM version with the platforms specified.
|
||||
fn check_evm_version_compatibility(&self) -> TestCheckFunctionResult {
|
||||
let Some(evm_version_requirement) = self.metadata.required_evm_version else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let leader_support = evm_version_requirement
|
||||
.matches(&<L::Blockchain as revive_dt_node::Node>::evm_version());
|
||||
let follower_support = evm_version_requirement
|
||||
.matches(&<F::Blockchain as revive_dt_node::Node>::evm_version());
|
||||
let is_allowed = leader_support && follower_support;
|
||||
let mut error_map = indexmap! {
|
||||
"test_desired_evm_version" => json!(self.metadata.required_evm_version),
|
||||
};
|
||||
let mut is_allowed = true;
|
||||
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 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err((
|
||||
"EVM version is incompatible with either the leader or the follower.",
|
||||
indexmap! {
|
||||
"test_desired_evm_version" => json!(self.metadata.required_evm_version),
|
||||
"leader_support" => json!(leader_support),
|
||||
"follower_support" => json!(follower_support),
|
||||
},
|
||||
"EVM version is incompatible for the platforms specified",
|
||||
error_map,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the leader and follower compilers support the mode that the test is for.
|
||||
/// Checks if the platforms compilers support the mode that the test is for.
|
||||
fn check_compiler_compatibility(&self) -> TestCheckFunctionResult {
|
||||
let leader_support = self
|
||||
.leader_compiler
|
||||
.supports_mode(self.mode.optimize_setting, self.mode.pipeline);
|
||||
let follower_support = self
|
||||
.follower_compiler
|
||||
.supports_mode(self.mode.optimize_setting, self.mode.pipeline);
|
||||
let is_allowed = leader_support && follower_support;
|
||||
let mut error_map = indexmap! {
|
||||
"test_desired_evm_version" => json!(self.metadata.required_evm_version),
|
||||
};
|
||||
let mut is_allowed = true;
|
||||
for (platform, _, compiler, ..) in self.platforms.iter() {
|
||||
let is_allowed_for_platform =
|
||||
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 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err((
|
||||
"Compilers do not support this mode either for the leader or for the follower.",
|
||||
indexmap! {
|
||||
"mode" => json!(self.mode),
|
||||
"leader_support" => json!(leader_support),
|
||||
"follower_support" => json!(follower_support),
|
||||
},
|
||||
"Compilers do not support this mode either for the provided platforms.",
|
||||
error_map,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::Platform;
|
||||
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 Platform) -> 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user