#![cfg(test)]
use std::{
collections::{BTreeMap, VecDeque},
fmt::{self, Debug, Display, Formatter},
time::Duration,
};
use derive_more::From;
use futures::channel::oneshot;
use prometheus::Registry;
use reactor::ReactorEvent;
use serde::Serialize;
use tempfile::TempDir;
use thiserror::Error;
use tokio::time;
use casper_execution_engine::{
core::engine_state::{BalanceResult, QueryResult, MAX_PAYMENT_AMOUNT},
storage::trie::merkle_proof::TrieMerkleProof,
};
use casper_types::{
account::{Account, ActionThresholds, AssociatedKeys, Weight},
CLValue, ProtocolVersion, StoredValue, URef, U512,
};
use super::*;
use crate::{
components::storage::{self, Storage},
effect::{
announcements::{ControlAnnouncement, DeployAcceptorAnnouncement},
requests::ContractRuntimeRequest,
Responder,
},
logging,
reactor::{self, EventQueueHandle, QueueKind, Runner},
testing::ConditionCheckReactor,
types::{Block, Chainspec, Deploy, NodeId},
utils::{Loadable, WithDir},
NodeRng,
};
const VERIFY_ACCOUNTS: bool = true;
const POLL_INTERVAL: Duration = Duration::from_millis(10);
const TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Debug, From, Serialize)]
#[allow(clippy::large_enum_variant)]
#[must_use]
enum Event {
#[from]
Storage(#[serde(skip_serializing)] storage::Event),
#[from]
DeployAcceptor(#[serde(skip_serializing)] super::Event),
#[from]
ControlAnnouncement(ControlAnnouncement),
#[from]
DeployAcceptorAnnouncement(#[serde(skip_serializing)] DeployAcceptorAnnouncement<NodeId>),
#[from]
ContractRuntime(#[serde(skip_serializing)] ContractRuntimeRequest),
}
impl ReactorEvent for Event {
fn as_control(&self) -> Option<&ControlAnnouncement> {
if let Self::ControlAnnouncement(ref ctrl_ann) = self {
Some(ctrl_ann)
} else {
None
}
}
}
impl From<StorageRequest> for Event {
fn from(request: StorageRequest) -> Self {
Event::Storage(storage::Event::from(request))
}
}
impl Display for Event {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Event::Storage(event) => write!(formatter, "storage: {}", event),
Event::DeployAcceptor(event) => write!(formatter, "deploy acceptor: {}", event),
Event::ControlAnnouncement(ctrl_ann) => write!(formatter, "control: {}", ctrl_ann),
Event::DeployAcceptorAnnouncement(ann) => {
write!(formatter, "deploy-acceptor announcement: {}", ann)
}
Event::ContractRuntime(event) => {
write!(formatter, "contract-runtime event: {:?}", event)
}
}
}
}
#[derive(Debug, Error)]
enum Error {
#[error("prometheus (metrics) error: {0}")]
Metrics(#[from] prometheus::Error),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum ContractScenario {
Valid,
MissingContractAtHash,
MissingContractAtName,
MissingEntryPoint,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum ContractPackageScenario {
Valid,
MissingPackageAtHash,
MissingPackageAtName,
MissingContractVersion,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum TestScenario {
FromPeerInvalidDeploy,
FromPeerValidDeploy,
FromPeerRepeatedValidDeploy,
FromPeerMissingAccount,
FromPeerAccountWithInsufficientWeight,
FromPeerAccountWithInvalidAssociatedKeys,
FromPeerCustomPaymentContract(ContractScenario),
FromPeerCustomPaymentContractPackage(ContractPackageScenario),
FromPeerSessionContract(ContractScenario),
FromPeerSessionContractPackage(ContractPackageScenario),
FromClientInvalidDeploy,
FromClientMissingAccount,
FromClientInsufficientBalance,
FromClientValidDeploy,
FromClientRepeatedValidDeploy,
FromClientAccountWithInsufficientWeight,
FromClientAccountWithInvalidAssociatedKeys,
AccountWithUnknownBalance,
FromClientCustomPaymentContract(ContractScenario),
FromClientCustomPaymentContractPackage(ContractPackageScenario),
FromClientSessionContract(ContractScenario),
FromClientSessionContractPackage(ContractPackageScenario),
DeployWithNativeTransferInPayment,
DeployWithEmptySessionModuleBytes,
DeployWithoutPaymentAmount,
DeployWithMangledPaymentAmount,
DeployWithMangledTransferAmount,
DeployWithoutTransferTarget,
DeployWithoutTransferAmount,
BalanceCheckForDeploySentByPeer,
ShouldNotAcceptExpiredDeploySentByClient,
ShouldAcceptExpiredDeploySentByPeer,
}
impl TestScenario {
fn source(&self, rng: &mut NodeRng) -> Source<NodeId> {
match self {
TestScenario::FromPeerInvalidDeploy
| TestScenario::FromPeerValidDeploy
| TestScenario::FromPeerRepeatedValidDeploy
| TestScenario::BalanceCheckForDeploySentByPeer
| TestScenario::FromPeerMissingAccount
| TestScenario::FromPeerAccountWithInsufficientWeight
| TestScenario::FromPeerAccountWithInvalidAssociatedKeys
| TestScenario::FromPeerCustomPaymentContract(_)
| TestScenario::FromPeerCustomPaymentContractPackage(_)
| TestScenario::FromPeerSessionContract(_)
| TestScenario::FromPeerSessionContractPackage(_)
| TestScenario::ShouldAcceptExpiredDeploySentByPeer => {
Source::Peer(NodeId::random(rng))
}
TestScenario::FromClientInvalidDeploy
| TestScenario::FromClientMissingAccount
| TestScenario::FromClientInsufficientBalance
| TestScenario::FromClientValidDeploy
| TestScenario::FromClientRepeatedValidDeploy
| TestScenario::FromClientAccountWithInsufficientWeight
| TestScenario::FromClientAccountWithInvalidAssociatedKeys
| TestScenario::AccountWithUnknownBalance
| TestScenario::DeployWithoutPaymentAmount
| TestScenario::DeployWithMangledPaymentAmount
| TestScenario::DeployWithMangledTransferAmount
| TestScenario::DeployWithoutTransferAmount
| TestScenario::DeployWithoutTransferTarget
| TestScenario::FromClientCustomPaymentContract(_)
| TestScenario::FromClientCustomPaymentContractPackage(_)
| TestScenario::FromClientSessionContract(_)
| TestScenario::FromClientSessionContractPackage(_)
| TestScenario::DeployWithEmptySessionModuleBytes
| TestScenario::DeployWithNativeTransferInPayment
| TestScenario::ShouldNotAcceptExpiredDeploySentByClient => Source::Client,
}
}
fn deploy(&self, rng: &mut NodeRng) -> Deploy {
match self {
TestScenario::FromPeerInvalidDeploy | TestScenario::FromClientInvalidDeploy => {
let mut deploy = Deploy::random_valid_native_transfer(rng);
deploy.invalidate();
deploy
}
TestScenario::FromPeerValidDeploy
| TestScenario::FromPeerRepeatedValidDeploy
| TestScenario::FromPeerMissingAccount
| TestScenario::FromPeerAccountWithInvalidAssociatedKeys
| TestScenario::FromPeerAccountWithInsufficientWeight
| TestScenario::FromClientMissingAccount
| TestScenario::FromClientInsufficientBalance
| TestScenario::FromClientValidDeploy
| TestScenario::FromClientRepeatedValidDeploy
| TestScenario::FromClientAccountWithInvalidAssociatedKeys
| TestScenario::FromClientAccountWithInsufficientWeight
| TestScenario::AccountWithUnknownBalance
| TestScenario::BalanceCheckForDeploySentByPeer => {
Deploy::random_valid_native_transfer(rng)
}
TestScenario::DeployWithoutPaymentAmount => Deploy::random_without_payment_amount(rng),
TestScenario::DeployWithMangledPaymentAmount => {
Deploy::random_with_mangled_payment_amount(rng)
}
TestScenario::DeployWithoutTransferTarget => {
Deploy::random_without_transfer_target(rng)
}
TestScenario::DeployWithoutTransferAmount => {
Deploy::random_without_transfer_amount(rng)
}
TestScenario::DeployWithMangledTransferAmount => {
Deploy::random_with_mangled_transfer_amount(rng)
}
TestScenario::FromPeerCustomPaymentContract(contract_scenario)
| TestScenario::FromClientCustomPaymentContract(contract_scenario) => {
match contract_scenario {
ContractScenario::Valid | ContractScenario::MissingContractAtName => {
Deploy::random_with_valid_custom_payment_contract_by_name(rng)
}
ContractScenario::MissingEntryPoint => {
Deploy::random_with_missing_entry_point_in_payment_contract(rng)
}
ContractScenario::MissingContractAtHash => {
Deploy::random_with_missing_payment_contract_by_hash(rng)
}
}
}
TestScenario::FromPeerCustomPaymentContractPackage(contract_package_scenario)
| TestScenario::FromClientCustomPaymentContractPackage(contract_package_scenario) => {
match contract_package_scenario {
ContractPackageScenario::Valid
| ContractPackageScenario::MissingPackageAtName => {
Deploy::random_with_valid_custom_payment_package_by_name(rng)
}
ContractPackageScenario::MissingPackageAtHash => {
Deploy::random_with_missing_payment_package_by_hash(rng)
}
ContractPackageScenario::MissingContractVersion => {
Deploy::random_with_nonexistent_contract_version_in_payment_package(rng)
}
}
}
TestScenario::FromPeerSessionContract(contract_scenario)
| TestScenario::FromClientSessionContract(contract_scenario) => match contract_scenario
{
ContractScenario::Valid | ContractScenario::MissingContractAtName => {
Deploy::random_with_valid_session_contract_by_name(rng)
}
ContractScenario::MissingContractAtHash => {
Deploy::random_with_missing_session_contract_by_hash(rng)
}
ContractScenario::MissingEntryPoint => {
Deploy::random_with_missing_entry_point_in_session_contract(rng)
}
},
TestScenario::FromPeerSessionContractPackage(contract_package_scenario)
| TestScenario::FromClientSessionContractPackage(contract_package_scenario) => {
match contract_package_scenario {
ContractPackageScenario::Valid
| ContractPackageScenario::MissingPackageAtName => {
Deploy::random_with_valid_session_package_by_name(rng)
}
ContractPackageScenario::MissingPackageAtHash => {
Deploy::random_with_missing_session_package_by_hash(rng)
}
ContractPackageScenario::MissingContractVersion => {
Deploy::random_with_nonexistent_contract_version_in_session_package(rng)
}
}
}
TestScenario::DeployWithEmptySessionModuleBytes => {
Deploy::random_with_empty_session_module_bytes(rng)
}
TestScenario::DeployWithNativeTransferInPayment => {
Deploy::random_with_native_transfer_in_payment_logic(rng)
}
TestScenario::ShouldAcceptExpiredDeploySentByPeer
| TestScenario::ShouldNotAcceptExpiredDeploySentByClient => {
Deploy::random_expired_deploy(rng)
}
}
}
fn is_valid_deploy_case(&self) -> bool {
match self {
TestScenario::FromPeerRepeatedValidDeploy
| TestScenario::FromPeerValidDeploy
| TestScenario::FromPeerMissingAccount | TestScenario::FromPeerAccountWithInsufficientWeight | TestScenario::FromPeerAccountWithInvalidAssociatedKeys | TestScenario::FromClientRepeatedValidDeploy
| TestScenario::FromClientValidDeploy
| TestScenario::ShouldAcceptExpiredDeploySentByPeer=> true,
TestScenario::FromPeerInvalidDeploy
| TestScenario::FromClientInsufficientBalance
| TestScenario::FromClientMissingAccount
| TestScenario::FromClientInvalidDeploy
| TestScenario::FromClientAccountWithInsufficientWeight
| TestScenario::FromClientAccountWithInvalidAssociatedKeys
| TestScenario::AccountWithUnknownBalance
| TestScenario::DeployWithEmptySessionModuleBytes
| TestScenario::DeployWithNativeTransferInPayment
| TestScenario::DeployWithoutPaymentAmount
| TestScenario::DeployWithMangledPaymentAmount
| TestScenario::DeployWithMangledTransferAmount
| TestScenario::DeployWithoutTransferAmount
| TestScenario::DeployWithoutTransferTarget
| TestScenario::BalanceCheckForDeploySentByPeer
| TestScenario::ShouldNotAcceptExpiredDeploySentByClient => false,
TestScenario::FromPeerCustomPaymentContract(contract_scenario)
| TestScenario::FromPeerSessionContract(contract_scenario)
| TestScenario::FromClientCustomPaymentContract(contract_scenario)
| TestScenario::FromClientSessionContract(contract_scenario) => match contract_scenario
{
ContractScenario::Valid
| ContractScenario::MissingContractAtName => true,
| ContractScenario::MissingContractAtHash
| ContractScenario::MissingEntryPoint => false,
},
TestScenario::FromPeerCustomPaymentContractPackage(contract_package_scenario)
| TestScenario::FromPeerSessionContractPackage(contract_package_scenario)
| TestScenario::FromClientCustomPaymentContractPackage(contract_package_scenario)
| TestScenario::FromClientSessionContractPackage(contract_package_scenario) => {
match contract_package_scenario {
ContractPackageScenario::Valid
| ContractPackageScenario::MissingPackageAtName => true,
| ContractPackageScenario::MissingPackageAtHash
| ContractPackageScenario::MissingContractVersion => false,
}
}
}
}
fn is_repeated_deploy_case(&self) -> bool {
matches!(
self,
TestScenario::FromClientRepeatedValidDeploy | TestScenario::FromPeerRepeatedValidDeploy
)
}
}
fn create_account(account_hash: AccountHash, test_scenario: TestScenario) -> Account {
match test_scenario {
TestScenario::FromPeerAccountWithInvalidAssociatedKeys
| TestScenario::FromClientAccountWithInvalidAssociatedKeys => {
Account::create(AccountHash::default(), BTreeMap::new(), URef::default())
}
TestScenario::FromPeerAccountWithInsufficientWeight
| TestScenario::FromClientAccountWithInsufficientWeight => {
let invalid_action_threshold =
ActionThresholds::new(Weight::new(100u8), Weight::new(100u8))
.expect("should create action threshold");
Account::new(
account_hash,
BTreeMap::new(),
URef::default(),
AssociatedKeys::new(account_hash, Weight::new(1)),
invalid_action_threshold,
)
}
_ => Account::create(account_hash, BTreeMap::new(), URef::default()),
}
}
struct Reactor {
storage: Storage,
deploy_acceptor: DeployAcceptor,
_storage_tempdir: TempDir,
test_scenario: TestScenario,
}
impl reactor::Reactor for Reactor {
type Event = Event;
type Config = TestScenario;
type Error = Error;
fn new(
config: Self::Config,
registry: &Registry,
_event_queue: EventQueueHandle<Self::Event>,
_rng: &mut NodeRng,
) -> Result<(Self, Effects<Self::Event>), Self::Error> {
let (storage_config, storage_tempdir) = storage::Config::default_for_tests();
let storage_withdir = WithDir::new(storage_tempdir.path(), storage_config);
let storage = Storage::new(
&storage_withdir,
None,
ProtocolVersion::from_parts(1, 0, 0),
false,
"test",
)
.unwrap();
let deploy_acceptor = DeployAcceptor::new(
super::Config::new(VERIFY_ACCOUNTS),
&Chainspec::from_resources("local"),
registry,
)
.unwrap();
let reactor = Reactor {
storage,
deploy_acceptor,
_storage_tempdir: storage_tempdir,
test_scenario: config,
};
let effects = Effects::new();
Ok((reactor, effects))
}
fn dispatch_event(
&mut self,
effect_builder: EffectBuilder<Self::Event>,
rng: &mut NodeRng,
event: Event,
) -> Effects<Self::Event> {
match event {
Event::Storage(event) => reactor::wrap_effects(
Event::Storage,
self.storage.handle_event(effect_builder, rng, event),
),
Event::DeployAcceptor(event) => reactor::wrap_effects(
Event::DeployAcceptor,
self.deploy_acceptor
.handle_event(effect_builder, rng, event),
),
Event::ControlAnnouncement(ctrl_ann) => {
panic!("unhandled control announcement: {}", ctrl_ann)
}
Event::DeployAcceptorAnnouncement(_) => {
Effects::new()
}
Event::ContractRuntime(event) => match event {
ContractRuntimeRequest::Query {
query_request,
responder,
} => {
let query_result = if self.test_scenario
== TestScenario::FromClientMissingAccount
|| self.test_scenario == TestScenario::FromPeerMissingAccount
{
QueryResult::ValueNotFound(String::new())
} else if let Key::Account(account_hash) = query_request.key() {
if query_request.path().is_empty() {
let account = create_account(account_hash, self.test_scenario);
QueryResult::Success {
value: Box::new(StoredValue::Account(account)),
proofs: vec![],
}
} else {
match self.test_scenario {
TestScenario::FromPeerCustomPaymentContractPackage(
contract_package_scenario,
)
| TestScenario::FromPeerSessionContractPackage(
contract_package_scenario,
)
| TestScenario::FromClientCustomPaymentContractPackage(
contract_package_scenario,
)
| TestScenario::FromClientSessionContractPackage(
contract_package_scenario,
) => match contract_package_scenario {
ContractPackageScenario::Valid
| ContractPackageScenario::MissingContractVersion => {
QueryResult::Success {
value: Box::new(StoredValue::ContractPackage(
ContractPackage::default(),
)),
proofs: vec![],
}
}
_ => QueryResult::ValueNotFound(String::new()),
},
TestScenario::FromPeerSessionContract(contract_scenario)
| TestScenario::FromPeerCustomPaymentContract(contract_scenario)
| TestScenario::FromClientSessionContract(contract_scenario)
| TestScenario::FromClientCustomPaymentContract(
contract_scenario,
) => match contract_scenario {
ContractScenario::Valid
| ContractScenario::MissingEntryPoint => QueryResult::Success {
value: Box::new(StoredValue::Contract(Contract::default())),
proofs: vec![],
},
_ => QueryResult::ValueNotFound(String::new()),
},
_ => QueryResult::ValueNotFound(String::new()),
}
}
} else if let Key::Hash(_) = query_request.key() {
match self.test_scenario {
TestScenario::FromPeerSessionContract(contract_scenario)
| TestScenario::FromPeerCustomPaymentContract(contract_scenario)
| TestScenario::FromClientSessionContract(contract_scenario)
| TestScenario::FromClientCustomPaymentContract(contract_scenario) => {
match contract_scenario {
ContractScenario::Valid
| ContractScenario::MissingEntryPoint => QueryResult::Success {
value: Box::new(StoredValue::Contract(Contract::default())),
proofs: vec![],
},
ContractScenario::MissingContractAtHash
| ContractScenario::MissingContractAtName => {
QueryResult::ValueNotFound(String::new())
}
}
}
TestScenario::FromPeerSessionContractPackage(
contract_package_scenario,
)
| TestScenario::FromPeerCustomPaymentContractPackage(
contract_package_scenario,
)
| TestScenario::FromClientSessionContractPackage(
contract_package_scenario,
)
| TestScenario::FromClientCustomPaymentContractPackage(
contract_package_scenario,
) => match contract_package_scenario {
ContractPackageScenario::Valid
| ContractPackageScenario::MissingContractVersion => {
QueryResult::Success {
value: Box::new(StoredValue::ContractPackage(
ContractPackage::default(),
)),
proofs: vec![],
}
}
ContractPackageScenario::MissingPackageAtHash
| ContractPackageScenario::MissingPackageAtName => {
QueryResult::ValueNotFound(String::new())
}
},
_ => QueryResult::ValueNotFound(String::new()),
}
} else {
panic!("expect only queries using Key::Account or Key::Hash variant");
};
responder.respond(Ok(query_result)).ignore()
}
ContractRuntimeRequest::GetBalance {
balance_request,
responder,
} => {
let proof = TrieMerkleProof::new(
balance_request.purse_uref().into(),
StoredValue::CLValue(CLValue::from_t(()).expect("should get CLValue")),
VecDeque::new(),
);
let motes = if self.test_scenario == TestScenario::FromClientInsufficientBalance
{
MAX_PAYMENT_AMOUNT - 1
} else {
MAX_PAYMENT_AMOUNT
};
let balance_result =
if self.test_scenario == TestScenario::AccountWithUnknownBalance {
BalanceResult::RootNotFound
} else {
BalanceResult::Success {
motes: U512::from(motes),
proof: Box::new(proof),
}
};
responder.respond(Ok(balance_result)).ignore()
}
_ => panic!("should not receive {:?}", event),
},
}
}
fn maybe_exit(&self) -> Option<crate::reactor::ReactorExit> {
panic!()
}
}
fn put_block_to_storage(
block: Box<Block>,
responder: Responder<bool>,
) -> impl FnOnce(EffectBuilder<Event>) -> Effects<Event> {
|effect_builder: EffectBuilder<Event>| {
effect_builder
.into_inner()
.schedule(
StorageRequest::PutBlock { block, responder },
QueueKind::Regular,
)
.ignore()
}
}
fn put_deploy_to_storage(
deploy: Box<Deploy>,
responder: Responder<bool>,
) -> impl FnOnce(EffectBuilder<Event>) -> Effects<Event> {
|effect_builder: EffectBuilder<Event>| {
effect_builder
.into_inner()
.schedule(
StorageRequest::PutDeploy { deploy, responder },
QueueKind::Regular,
)
.ignore()
}
}
fn schedule_accept_deploy(
deploy: Box<Deploy>,
source: Source<NodeId>,
responder: Responder<Result<(), super::Error>>,
) -> impl FnOnce(EffectBuilder<Event>) -> Effects<Event> {
|effect_builder: EffectBuilder<Event>| {
effect_builder
.into_inner()
.schedule(
super::Event::Accept {
deploy,
source,
maybe_responder: Some(responder),
},
QueueKind::Regular,
)
.ignore()
}
}
fn inject_balance_check_for_peer(
deploy: Box<Deploy>,
source: Source<NodeId>,
responder: Responder<Result<(), super::Error>>,
) -> impl FnOnce(EffectBuilder<Event>) -> Effects<Event> {
|effect_builder: EffectBuilder<Event>| {
let event_metadata = EventMetadata::new(deploy, source, Some(responder));
effect_builder
.into_inner()
.schedule(
super::Event::GetBalanceResult {
event_metadata,
prestate_hash: Default::default(),
maybe_balance_value: None,
account_hash: Default::default(),
verification_start_timestamp: Timestamp::now(),
},
QueueKind::Regular,
)
.ignore()
}
}
async fn run_deploy_acceptor_without_timeout(
test_scenario: TestScenario,
) -> Result<(), super::Error> {
let _ = logging::init();
let mut rng = crate::new_rng();
let mut runner: Runner<ConditionCheckReactor<Reactor>> =
Runner::new(test_scenario, &mut rng).await.unwrap();
let block = Box::new(Block::random(&mut rng));
let (block_sender, block_receiver) = oneshot::channel();
let block_responder = Responder::without_shutdown(block_sender);
runner
.process_injected_effects(put_block_to_storage(block, block_responder))
.await;
while runner.try_crank(&mut rng).await.is_none() {
time::sleep(POLL_INTERVAL).await;
}
assert!(block_receiver.await.unwrap());
let (deploy_sender, deploy_receiver) = oneshot::channel();
let deploy_responder = Responder::without_shutdown(deploy_sender);
let deploy = test_scenario.deploy(&mut rng);
let source = test_scenario.source(&mut rng);
{
if test_scenario.is_repeated_deploy_case() {
let injected_deploy = Box::new(deploy.clone());
let (injected_sender, injected_receiver) = oneshot::channel();
let injected_responder = Responder::without_shutdown(injected_sender);
runner
.process_injected_effects(put_deploy_to_storage(
injected_deploy,
injected_responder,
))
.await;
while runner.try_crank(&mut rng).await.is_none() {
time::sleep(POLL_INTERVAL).await;
}
assert!(injected_receiver.await.unwrap());
}
if test_scenario == TestScenario::BalanceCheckForDeploySentByPeer {
let fatal_deploy = Box::new(deploy.clone());
let (deploy_sender, _) = oneshot::channel();
let deploy_responder = Responder::without_shutdown(deploy_sender);
runner
.process_injected_effects(inject_balance_check_for_peer(
fatal_deploy,
source.clone(),
deploy_responder,
))
.await;
while runner.try_crank(&mut rng).await.is_none() {
time::sleep(POLL_INTERVAL).await;
}
}
}
runner
.process_injected_effects(schedule_accept_deploy(
Box::new(deploy.clone()),
source,
deploy_responder,
))
.await;
let stopping_condition = move |event: &Event| -> bool {
match test_scenario {
TestScenario::FromClientInvalidDeploy
| TestScenario::FromClientMissingAccount
| TestScenario::FromClientInsufficientBalance
| TestScenario::FromClientAccountWithInvalidAssociatedKeys
| TestScenario::FromClientAccountWithInsufficientWeight
| TestScenario::DeployWithEmptySessionModuleBytes
| TestScenario::AccountWithUnknownBalance
| TestScenario::DeployWithNativeTransferInPayment
| TestScenario::DeployWithoutPaymentAmount
| TestScenario::DeployWithMangledPaymentAmount
| TestScenario::DeployWithMangledTransferAmount
| TestScenario::DeployWithoutTransferTarget
| TestScenario::DeployWithoutTransferAmount
| TestScenario::ShouldNotAcceptExpiredDeploySentByClient => {
matches!(
event,
Event::DeployAcceptorAnnouncement(DeployAcceptorAnnouncement::InvalidDeploy {
source: Source::Client,
..
})
)
}
TestScenario::FromPeerCustomPaymentContract(contract_scenario)
| TestScenario::FromPeerSessionContract(contract_scenario)
| TestScenario::FromClientCustomPaymentContract(contract_scenario)
| TestScenario::FromClientSessionContract(contract_scenario) => match contract_scenario
{
ContractScenario::Valid | ContractScenario::MissingContractAtName => matches!(
event,
Event::DeployAcceptorAnnouncement(
DeployAcceptorAnnouncement::AcceptedNewDeploy { .. }
)
),
ContractScenario::MissingContractAtHash | ContractScenario::MissingEntryPoint => {
matches!(
event,
Event::DeployAcceptorAnnouncement(
DeployAcceptorAnnouncement::InvalidDeploy { .. }
)
)
}
},
TestScenario::FromPeerCustomPaymentContractPackage(contract_package_scenario)
| TestScenario::FromPeerSessionContractPackage(contract_package_scenario)
| TestScenario::FromClientCustomPaymentContractPackage(contract_package_scenario)
| TestScenario::FromClientSessionContractPackage(contract_package_scenario) => {
match contract_package_scenario {
ContractPackageScenario::Valid
| ContractPackageScenario::MissingPackageAtName => matches!(
event,
Event::DeployAcceptorAnnouncement(
DeployAcceptorAnnouncement::AcceptedNewDeploy { .. }
)
),
ContractPackageScenario::MissingContractVersion
| ContractPackageScenario::MissingPackageAtHash => matches!(
event,
Event::DeployAcceptorAnnouncement(
DeployAcceptorAnnouncement::InvalidDeploy { .. }
)
),
}
}
TestScenario::FromPeerInvalidDeploy | TestScenario::BalanceCheckForDeploySentByPeer => {
matches!(
event,
Event::DeployAcceptorAnnouncement(DeployAcceptorAnnouncement::InvalidDeploy {
source: Source::Peer(_),
..
})
)
}
TestScenario::FromPeerValidDeploy
| TestScenario::FromPeerMissingAccount
| TestScenario::FromPeerAccountWithInvalidAssociatedKeys
| TestScenario::FromPeerAccountWithInsufficientWeight
| TestScenario::ShouldAcceptExpiredDeploySentByPeer => {
matches!(
event,
Event::DeployAcceptorAnnouncement(
DeployAcceptorAnnouncement::AcceptedNewDeploy {
source: Source::Peer(_),
..
}
)
)
}
TestScenario::FromClientValidDeploy => {
matches!(
event,
Event::DeployAcceptorAnnouncement(
DeployAcceptorAnnouncement::AcceptedNewDeploy {
source: Source::Client,
..
}
)
)
}
TestScenario::FromClientRepeatedValidDeploy
| TestScenario::FromPeerRepeatedValidDeploy => {
matches!(
event,
Event::DeployAcceptor(super::Event::PutToStorageResult { is_new: false, .. })
)
}
}
};
runner
.reactor_mut()
.set_condition_checker(Box::new(stopping_condition));
loop {
if runner.try_crank(&mut rng).await.is_some() {
if runner.reactor().condition_result() {
break;
}
} else {
time::sleep(POLL_INTERVAL).await;
}
}
{
let is_in_storage = runner
.reactor()
.inner()
.storage
.get_deploy_by_hash(*deploy.id())
.is_some();
if test_scenario.is_valid_deploy_case() {
assert!(is_in_storage)
} else {
assert!(!is_in_storage)
}
}
deploy_receiver.await.unwrap()
}
async fn run_deploy_acceptor(test_scenario: TestScenario) -> Result<(), super::Error> {
time::timeout(TIMEOUT, run_deploy_acceptor_without_timeout(test_scenario))
.await
.unwrap()
}
#[tokio::test]
async fn should_accept_valid_deploy_from_peer() {
let result = run_deploy_acceptor(TestScenario::FromPeerValidDeploy).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_invalid_deploy_from_peer() {
let result = run_deploy_acceptor(TestScenario::FromPeerInvalidDeploy).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployConfiguration(_))
))
}
#[tokio::test]
async fn should_accept_valid_deploy_from_peer_for_missing_account() {
let result = run_deploy_acceptor(TestScenario::FromPeerMissingAccount).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_valid_deploy_from_peer_for_account_with_invalid_associated_keys() {
let result = run_deploy_acceptor(TestScenario::FromPeerAccountWithInvalidAssociatedKeys).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_valid_deploy_from_peer_for_account_with_insufficient_weight() {
let result = run_deploy_acceptor(TestScenario::FromPeerAccountWithInsufficientWeight).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_valid_deploy_from_client() {
let result = run_deploy_acceptor(TestScenario::FromClientValidDeploy).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_invalid_deploy_from_client() {
let result = run_deploy_acceptor(TestScenario::FromClientInvalidDeploy).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployConfiguration(_))
))
}
#[tokio::test]
async fn should_reject_valid_deploy_from_client_for_missing_account() {
let result = run_deploy_acceptor(TestScenario::FromClientMissingAccount).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentAccount { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_valid_deploy_from_client_for_account_with_invalid_associated_keys() {
let result =
run_deploy_acceptor(TestScenario::FromClientAccountWithInvalidAssociatedKeys).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::InvalidAssociatedKeys,
..
})
))
}
#[tokio::test]
async fn should_reject_valid_deploy_from_client_for_account_with_insufficient_weight() {
let result = run_deploy_acceptor(TestScenario::FromClientAccountWithInsufficientWeight).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::InsufficientDeploySignatureWeight,
..
})
))
}
#[tokio::test]
async fn should_reject_valid_deploy_from_client_for_insufficient_balance() {
let result = run_deploy_acceptor(TestScenario::FromClientInsufficientBalance).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::InsufficientBalance { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_valid_deploy_from_client_for_unknown_balance() {
let result = run_deploy_acceptor(TestScenario::AccountWithUnknownBalance).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::UnknownBalance { .. },
..
})
))
}
#[tokio::test]
async fn should_accept_repeated_valid_deploy_from_peer() {
let result = run_deploy_acceptor(TestScenario::FromPeerRepeatedValidDeploy).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_repeated_valid_deploy_from_client() {
let result = run_deploy_acceptor(TestScenario::FromClientRepeatedValidDeploy).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_deploy_with_valid_custom_payment_from_client() {
let test_scenario = TestScenario::FromClientCustomPaymentContract(ContractScenario::Valid);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_deploy_with_missing_custom_payment_contract_by_name_from_client() {
let test_scenario =
TestScenario::FromClientCustomPaymentContract(ContractScenario::MissingContractAtName);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_deploy_with_missing_custom_payment_contract_by_hash_from_client() {
let test_scenario =
TestScenario::FromClientCustomPaymentContract(ContractScenario::MissingContractAtHash);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractAtHash { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_missing_entry_point_custom_payment_from_client() {
let test_scenario =
TestScenario::FromClientCustomPaymentContract(ContractScenario::MissingEntryPoint);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractEntryPoint { .. },
..
})
))
}
#[tokio::test]
async fn should_accept_deploy_with_valid_payment_contract_package_by_name_from_client() {
let test_scenario =
TestScenario::FromClientCustomPaymentContractPackage(ContractPackageScenario::Valid);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_deploy_with_missing_payment_contract_package_at_name_from_client() {
let test_scenario = TestScenario::FromClientCustomPaymentContractPackage(
ContractPackageScenario::MissingPackageAtName,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_deploy_with_missing_payment_contract_package_at_hash_from_client() {
let test_scenario = TestScenario::FromClientCustomPaymentContractPackage(
ContractPackageScenario::MissingPackageAtHash,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractPackageAtHash { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_missing_version_in_payment_contract_package_from_client() {
let test_scenario = TestScenario::FromClientCustomPaymentContractPackage(
ContractPackageScenario::MissingContractVersion,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::InvalidContractAtVersion { .. },
..
})
))
}
#[tokio::test]
async fn should_accept_deploy_with_valid_session_contract_from_client() {
let test_scenario = TestScenario::FromClientSessionContract(ContractScenario::Valid);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_deploy_with_missing_session_contract_by_name_from_client() {
let test_scenario =
TestScenario::FromClientSessionContract(ContractScenario::MissingContractAtName);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_deploy_with_missing_session_contract_by_hash_from_client() {
let test_scenario =
TestScenario::FromClientSessionContract(ContractScenario::MissingContractAtHash);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractAtHash { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_missing_entry_point_in_session_contract_from_client() {
let test_scenario =
TestScenario::FromClientSessionContract(ContractScenario::MissingEntryPoint);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractEntryPoint { .. },
..
})
))
}
#[tokio::test]
async fn should_accept_deploy_with_valid_session_contract_package_from_client() {
let test_scenario =
TestScenario::FromClientSessionContractPackage(ContractPackageScenario::Valid);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_deploy_with_missing_session_contract_package_at_name_from_client() {
let test_scenario = TestScenario::FromClientSessionContractPackage(
ContractPackageScenario::MissingPackageAtName,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_deploy_with_missing_session_contract_package_at_hash_from_client() {
let test_scenario = TestScenario::FromClientSessionContractPackage(
ContractPackageScenario::MissingPackageAtHash,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractPackageAtHash { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_missing_version_in_session_contract_package_from_client() {
let test_scenario = TestScenario::FromClientCustomPaymentContractPackage(
ContractPackageScenario::MissingContractVersion,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::InvalidContractAtVersion { .. },
..
})
))
}
#[tokio::test]
async fn should_accept_deploy_with_valid_custom_payment_from_peer() {
let test_scenario = TestScenario::FromPeerCustomPaymentContract(ContractScenario::Valid);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_deploy_with_missing_custom_payment_contract_by_name_from_peer() {
let test_scenario =
TestScenario::FromPeerCustomPaymentContract(ContractScenario::MissingContractAtName);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_deploy_with_missing_custom_payment_contract_by_hash_from_peer() {
let test_scenario =
TestScenario::FromPeerCustomPaymentContract(ContractScenario::MissingContractAtHash);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractAtHash { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_missing_entry_point_custom_payment_from_peer() {
let test_scenario =
TestScenario::FromPeerCustomPaymentContract(ContractScenario::MissingEntryPoint);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractEntryPoint { .. },
..
})
))
}
#[tokio::test]
async fn should_accept_deploy_with_valid_payment_contract_package_by_name_from_peer() {
let test_scenario =
TestScenario::FromPeerCustomPaymentContractPackage(ContractPackageScenario::Valid);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_deploy_with_missing_payment_contract_package_at_name_from_peer() {
let test_scenario = TestScenario::FromPeerCustomPaymentContractPackage(
ContractPackageScenario::MissingPackageAtName,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_deploy_with_missing_payment_contract_package_at_hash_from_peer() {
let test_scenario = TestScenario::FromPeerCustomPaymentContractPackage(
ContractPackageScenario::MissingPackageAtHash,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractPackageAtHash { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_missing_version_in_payment_contract_package_from_peer() {
let test_scenario = TestScenario::FromPeerCustomPaymentContractPackage(
ContractPackageScenario::MissingContractVersion,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::InvalidContractAtVersion { .. },
..
})
))
}
#[tokio::test]
async fn should_accept_deploy_with_valid_session_contract_from_peer() {
let test_scenario = TestScenario::FromPeerSessionContract(ContractScenario::Valid);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_deploy_with_missing_session_contract_by_name_from_peer() {
let test_scenario =
TestScenario::FromPeerSessionContract(ContractScenario::MissingContractAtName);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_deploy_with_missing_session_contract_by_hash_from_peer() {
let test_scenario =
TestScenario::FromPeerSessionContract(ContractScenario::MissingContractAtHash);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractAtHash { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_missing_entry_point_in_session_contract_from_peer() {
let test_scenario = TestScenario::FromPeerSessionContract(ContractScenario::MissingEntryPoint);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractEntryPoint { .. },
..
})
))
}
#[tokio::test]
async fn should_accept_deploy_with_valid_session_contract_package_from_peer() {
let test_scenario =
TestScenario::FromPeerSessionContractPackage(ContractPackageScenario::Valid);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_accept_deploy_with_missing_session_contract_package_at_name_from_peer() {
let test_scenario =
TestScenario::FromPeerSessionContractPackage(ContractPackageScenario::MissingPackageAtName);
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
async fn should_reject_deploy_with_missing_session_contract_package_at_hash_from_peer() {
let test_scenario =
TestScenario::FromPeerSessionContractPackage(ContractPackageScenario::MissingPackageAtHash);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::NonexistentContractPackageAtHash { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_missing_version_in_session_contract_package_from_peer() {
let test_scenario = TestScenario::FromPeerCustomPaymentContractPackage(
ContractPackageScenario::MissingContractVersion,
);
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::InvalidContractAtVersion { .. },
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_empty_module_bytes_in_session() {
let test_scenario = TestScenario::DeployWithEmptySessionModuleBytes;
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::MissingModuleBytes,
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_transfer_in_payment() {
let test_scenario = TestScenario::DeployWithNativeTransferInPayment;
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::InvalidPaymentVariant,
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_without_payment_amount() {
let test_scenario = TestScenario::DeployWithoutPaymentAmount;
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::MissingPaymentAmount,
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_mangled_payment_amount() {
let test_scenario = TestScenario::DeployWithMangledPaymentAmount;
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::FailedToParsePaymentAmount,
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_without_transfer_amount() {
let test_scenario = TestScenario::DeployWithoutTransferAmount;
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployConfiguration(
DeployConfigurationFailure::MissingTransferAmount
))
))
}
#[tokio::test]
async fn should_reject_deploy_without_transfer_target() {
let test_scenario = TestScenario::DeployWithoutTransferTarget;
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployParameters {
failure: DeployParameterFailure::MissingTransferTarget,
..
})
))
}
#[tokio::test]
async fn should_reject_deploy_with_mangled_transfer_amount() {
let test_scenario = TestScenario::DeployWithMangledTransferAmount;
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(
result,
Err(super::Error::InvalidDeployConfiguration(
DeployConfigurationFailure::FailedToParseTransferAmount
))
))
}
#[tokio::test]
async fn should_reject_expired_deploy_from_client() {
let test_scenario = TestScenario::ShouldNotAcceptExpiredDeploySentByClient;
let result = run_deploy_acceptor(test_scenario).await;
assert!(matches!(result, Err(super::Error::ExpiredDeploy { .. })))
}
#[tokio::test]
async fn should_accept_expired_deploy_from_peer() {
let test_scenario = TestScenario::ShouldAcceptExpiredDeploySentByPeer;
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}
#[tokio::test]
#[should_panic]
async fn should_panic_when_balance_checking_for_deploy_sent_by_peer() {
let test_scenario = TestScenario::BalanceCheckForDeploySentByPeer;
let result = run_deploy_acceptor(test_scenario).await;
assert!(result.is_ok())
}