Files
pezkuwi-sdk/pezbridges/modules/messages/src/tests/mock.rs
T

565 lines
18 KiB
Rust

// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
// From construct_runtime macro
#![allow(clippy::from_over_into)]
use crate::{
tests::messages_generation::{
encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof,
prepare_messages_storage_proof,
},
Config, StoredMessagePayload,
};
use bp_header_pez_chain::{ChainWithGrandpa, StoredHeaderData};
use bp_messages::{
calc_relayers_rewards,
source_chain::{
DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered,
},
target_chain::{
DeliveryPayments, DispatchMessage, DispatchMessageData, FromBridgedChainMessagesProof,
MessageDispatch,
},
ChainWithMessages, DeliveredMessages, HashedLaneId, InboundLaneData, LaneIdType, LaneState,
Message, MessageKey, MessageNonce, OutboundLaneData, UnrewardedRelayer,
UnrewardedRelayersState,
};
use codec::{Decode, DecodeWithMemTracking, Encode};
use pezbp_runtime::{
messages::MessageDispatchResult, Chain, ChainId, Size, UnverifiedStorageProofParams,
};
use pezframe_support::{
derive_impl,
weights::{constants::RocksDbWeight, Weight},
};
use pezsp_core::H256;
use pezsp_runtime::{
testing::Header as BizinikiwiHeader,
traits::{BlakeTwo256, ConstU32},
BuildStorage, StateVersion,
};
use scale_info::TypeInfo;
use std::{collections::VecDeque, ops::RangeInclusive};
pub type AccountId = u64;
pub type Balance = u64;
#[derive(Decode, DecodeWithMemTracking, Encode, Clone, Debug, PartialEq, Eq, TypeInfo)]
pub struct TestPayload {
/// Field that may be used to identify messages.
pub id: u64,
/// Dispatch weight that is declared by the message sender.
pub declared_weight: Weight,
/// Message dispatch result.
///
/// Note: in correct code `dispatch_result.unspent_weight` will always be <= `declared_weight`,
/// but for test purposes we'll be making it larger than `declared_weight` sometimes.
pub dispatch_result: MessageDispatchResult<TestDispatchLevelResult>,
/// Extra bytes that affect payload size.
pub extra: Vec<u8>,
}
pub type TestMessageFee = u64;
pub type TestRelayer = u64;
pub type TestDispatchLevelResult = ();
pub struct ThisChain;
impl Chain for ThisChain {
const ID: ChainId = *b"ttch";
type BlockNumber = u64;
type Hash = H256;
type Hasher = BlakeTwo256;
type Header = BizinikiwiHeader;
type AccountId = AccountId;
type Balance = Balance;
type Nonce = u64;
type Signature = pezsp_runtime::MultiSignature;
const STATE_VERSION: StateVersion = StateVersion::V1;
fn max_extrinsic_size() -> u32 {
u32::MAX
}
fn max_extrinsic_weight() -> Weight {
Weight::MAX
}
}
impl ChainWithMessages for ThisChain {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithThisChainBridgeMessages";
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
}
pub struct BridgedChain;
pub type BridgedHeaderHash = H256;
pub type BridgedChainHeader = BizinikiwiHeader;
impl Chain for BridgedChain {
const ID: ChainId = *b"tbch";
type BlockNumber = u64;
type Hash = BridgedHeaderHash;
type Hasher = BlakeTwo256;
type Header = BridgedChainHeader;
type AccountId = TestRelayer;
type Balance = Balance;
type Nonce = u64;
type Signature = pezsp_runtime::MultiSignature;
const STATE_VERSION: StateVersion = StateVersion::V1;
fn max_extrinsic_size() -> u32 {
4096
}
fn max_extrinsic_weight() -> Weight {
Weight::MAX
}
}
impl ChainWithGrandpa for BridgedChain {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "WithBridgedChainBridgeGrandpa";
const MAX_AUTHORITIES_COUNT: u32 = 16;
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 4;
const MAX_MANDATORY_HEADER_SIZE: u32 = 4096;
const AVERAGE_HEADER_SIZE: u32 = 4096;
}
impl ChainWithMessages for BridgedChain {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithBridgedChainBridgeMessages";
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
}
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
use crate as pezpallet_bridge_messages;
pezframe_support::construct_runtime! {
pub enum TestRuntime
{
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
Balances: pezpallet_balances::{Pezpallet, Call, Event<T>},
BridgedChainGrandpa: pezpallet_bridge_grandpa::{Pezpallet, Call, Event<T>},
Messages: pezpallet_bridge_messages::{Pezpallet, Call, Event<T>},
}
}
pub type DbWeight = RocksDbWeight;
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for TestRuntime {
type Block = Block;
type AccountData = pezpallet_balances::AccountData<Balance>;
type DbWeight = DbWeight;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for TestRuntime {
type AccountStore = System;
}
impl pezpallet_bridge_grandpa::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = BridgedChain;
type MaxFreeHeadersPerBlock = ConstU32<4>;
type FreeHeadersInterval = ConstU32<1_024>;
type HeadersToKeep = ConstU32<8>;
type WeightInfo = pezpallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
}
/// weights of messages pezpallet calls we use in tests.
pub type TestWeightInfo = ();
impl Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = TestWeightInfo;
type ThisChain = ThisChain;
type BridgedChain = BridgedChain;
type BridgedHeaderChain = BridgedChainGrandpa;
type OutboundPayload = TestPayload;
type InboundPayload = TestPayload;
type LaneId = TestLaneIdType;
type DeliveryPayments = TestDeliveryPayments;
type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments;
type OnMessagesDelivered = TestOnMessagesDelivered;
type MessageDispatch = TestMessageDispatch;
}
#[cfg(feature = "runtime-benchmarks")]
impl crate::benchmarking::Config<()> for TestRuntime {
fn bench_lane_id() -> Self::LaneId {
test_lane_id()
}
fn prepare_message_proof(
params: crate::benchmarking::MessageProofParams<Self::LaneId>,
) -> (FromBridgedChainMessagesProof<BridgedHeaderHash, Self::LaneId>, Weight) {
use pezbp_runtime::RangeInclusiveExt;
let dispatch_weight =
REGULAR_PAYLOAD.declared_weight * params.message_nonces.saturating_len();
(
*prepare_messages_proof(
params.message_nonces.into_iter().map(|n| message(n, REGULAR_PAYLOAD)).collect(),
params.outbound_lane_data,
),
dispatch_weight,
)
}
fn prepare_message_delivery_proof(
params: crate::benchmarking::MessageDeliveryProofParams<AccountId, Self::LaneId>,
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, Self::LaneId> {
// in mock run we only care about benchmarks correctness, not the benchmark results
// => ignore size related arguments
prepare_messages_delivery_proof(params.lane, params.inbound_lane_data)
}
fn is_relayer_rewarded(_relayer: &AccountId) -> bool {
true
}
}
impl Size for TestPayload {
fn size(&self) -> u32 {
16 + self.extra.len() as u32
}
}
/// Account that has balance to use in tests.
pub const ENDOWED_ACCOUNT: AccountId = 0xDEAD;
/// Account id of test relayer.
pub const TEST_RELAYER_A: AccountId = 100;
/// Account id of additional test relayer - B.
pub const TEST_RELAYER_B: AccountId = 101;
/// Account id of additional test relayer - C.
pub const TEST_RELAYER_C: AccountId = 102;
/// Lane identifier type used for tests.
pub type TestLaneIdType = HashedLaneId;
/// Lane that we're using in tests.
pub fn test_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 2).unwrap()
}
/// Lane that is completely unknown to our runtime.
pub fn unknown_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 3).unwrap()
}
/// Lane that is registered, but it is closed.
pub fn closed_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 4).unwrap()
}
/// Regular message payload.
pub const REGULAR_PAYLOAD: TestPayload = message_payload(0, 50);
/// Reward payments at the target chain during delivery transaction.
#[derive(Debug, Default)]
pub struct TestDeliveryPayments;
impl TestDeliveryPayments {
/// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is
/// cleared after the call.
pub fn is_reward_paid(relayer: AccountId) -> bool {
let key = (b":delivery-relayer-reward:", relayer).encode();
pezframe_support::storage::unhashed::take::<bool>(&key).is_some()
}
}
impl DeliveryPayments<AccountId> for TestDeliveryPayments {
type Error = &'static str;
fn pay_reward(
relayer: AccountId,
_total_messages: MessageNonce,
_valid_messages: MessageNonce,
_actual_weight: Weight,
) {
let key = (b":delivery-relayer-reward:", relayer).encode();
pezframe_support::storage::unhashed::put(&key, &true);
}
}
/// Reward payments at the source chain during delivery confirmation transaction.
#[derive(Debug, Default)]
pub struct TestDeliveryConfirmationPayments;
impl TestDeliveryConfirmationPayments {
/// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is
/// cleared after the call.
pub fn is_reward_paid(relayer: AccountId, fee: TestMessageFee) -> bool {
let key = (b":relayer-reward:", relayer, fee).encode();
pezframe_support::storage::unhashed::take::<bool>(&key).is_some()
}
}
impl DeliveryConfirmationPayments<AccountId, TestLaneIdType> for TestDeliveryConfirmationPayments {
type Error = &'static str;
fn pay_reward(
_lane_id: TestLaneIdType,
pez_messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
_confirmation_relayer: &AccountId,
received_range: &RangeInclusive<MessageNonce>,
) -> MessageNonce {
let relayers_rewards = calc_relayers_rewards(pez_messages_relayers, received_range);
let rewarded_relayers = relayers_rewards.len();
for (relayer, reward) in &relayers_rewards {
let key = (b":relayer-reward:", relayer, reward).encode();
pezframe_support::storage::unhashed::put(&key, &true);
}
rewarded_relayers as _
}
}
/// Test message dispatcher.
#[derive(Debug)]
pub struct TestMessageDispatch;
impl TestMessageDispatch {
pub fn deactivate(lane: TestLaneIdType) {
// "enqueue" enough (to deactivate dispatcher) messages at dispatcher
let latest_received_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1;
for _ in 1..=latest_received_nonce {
Self::emulate_enqueued_message(lane);
}
}
pub fn emulate_enqueued_message(lane: TestLaneIdType) {
let key = (b"dispatched", lane).encode();
let dispatched =
pezframe_support::storage::unhashed::get_or_default::<MessageNonce>(&key[..]);
pezframe_support::storage::unhashed::put(&key[..], &(dispatched + 1));
}
}
impl MessageDispatch for TestMessageDispatch {
type DispatchPayload = TestPayload;
type DispatchLevelResult = TestDispatchLevelResult;
type LaneId = TestLaneIdType;
fn is_active(lane: Self::LaneId) -> bool {
pezframe_support::storage::unhashed::get_or_default::<MessageNonce>(
&(b"dispatched", lane).encode()[..],
) <= BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
}
fn dispatch_weight(message: &mut DispatchMessage<TestPayload, Self::LaneId>) -> Weight {
match message.data.payload.as_ref() {
Ok(payload) => payload.declared_weight,
Err(_) => Weight::zero(),
}
}
fn dispatch(
message: DispatchMessage<TestPayload, Self::LaneId>,
) -> MessageDispatchResult<TestDispatchLevelResult> {
match message.data.payload.as_ref() {
Ok(payload) => {
Self::emulate_enqueued_message(message.key.lane_id);
payload.dispatch_result.clone()
},
Err(_) => dispatch_result(0),
}
}
}
/// Test callback, called during message delivery confirmation transaction.
pub struct TestOnMessagesDelivered;
impl TestOnMessagesDelivered {
pub fn call_arguments() -> Option<(TestLaneIdType, MessageNonce)> {
pezframe_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered")
}
}
impl OnMessagesDelivered<TestLaneIdType> for TestOnMessagesDelivered {
fn on_messages_delivered(lane: TestLaneIdType, enqueued_messages: MessageNonce) {
pezframe_support::storage::unhashed::put(
b"TestOnMessagesDelivered.OnMessagesDelivered",
&(lane, enqueued_messages),
);
}
}
/// Return test lane message with given nonce and payload.
pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message<TestLaneIdType> {
Message { key: MessageKey { lane_id: test_lane_id(), nonce }, payload: payload.encode() }
}
/// Return valid outbound message data, constructed from given payload.
pub fn outbound_message_data(payload: TestPayload) -> StoredMessagePayload<TestRuntime, ()> {
StoredMessagePayload::<TestRuntime, ()>::try_from(payload.encode()).expect("payload too large")
}
/// Return valid inbound (dispatch) message data, constructed from given payload.
pub fn inbound_message_data(payload: TestPayload) -> DispatchMessageData<TestPayload> {
DispatchMessageData { payload: Ok(payload) }
}
/// Constructs message payload using given arguments and zero unspent weight.
pub const fn message_payload(id: u64, declared_weight: u64) -> TestPayload {
TestPayload {
id,
declared_weight: Weight::from_parts(declared_weight, 0),
dispatch_result: dispatch_result(0),
extra: Vec::new(),
}
}
/// Returns message dispatch result with given unspent weight.
pub const fn dispatch_result(
unspent_weight: u64,
) -> MessageDispatchResult<TestDispatchLevelResult> {
MessageDispatchResult {
unspent_weight: Weight::from_parts(unspent_weight, 0),
dispatch_level_result: (),
}
}
/// Constructs unrewarded relayer entry from nonces range and relayer id.
pub fn unrewarded_relayer(
begin: MessageNonce,
end: MessageNonce,
relayer: TestRelayer,
) -> UnrewardedRelayer<TestRelayer> {
UnrewardedRelayer { relayer, messages: DeliveredMessages { begin, end } }
}
/// Returns unrewarded relayers state at given lane.
pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRelayersState {
let inbound_lane_data = crate::InboundLanes::<TestRuntime, ()>::get(lane).unwrap().0;
UnrewardedRelayersState::from(&inbound_lane_data)
}
/// Return test externalities to use in tests.
pub fn new_test_ext() -> pezsp_io::TestExternalities {
let mut t = pezframe_system::GenesisConfig::<TestRuntime>::default()
.build_storage()
.unwrap();
pezpallet_balances::GenesisConfig::<TestRuntime> {
balances: vec![(ENDOWED_ACCOUNT, 1_000_000)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
pezsp_io::TestExternalities::new(t)
}
/// Run pezpallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
new_test_ext().execute_with(|| {
crate::InboundLanes::<TestRuntime, ()>::insert(test_lane_id(), InboundLaneData::opened());
crate::OutboundLanes::<TestRuntime, ()>::insert(test_lane_id(), OutboundLaneData::opened());
crate::InboundLanes::<TestRuntime, ()>::insert(
closed_lane_id(),
InboundLaneData { state: LaneState::Closed, ..Default::default() },
);
crate::OutboundLanes::<TestRuntime, ()>::insert(
closed_lane_id(),
OutboundLaneData { state: LaneState::Closed, ..Default::default() },
);
test()
})
}
/// Prepare valid storage proof for given messages and insert appropriate header to the
/// bridged header chain.
///
/// Since this function changes the runtime storage, you can't "inline" it in the
/// `asset_noop` macro calls.
pub fn prepare_messages_proof(
messages: Vec<Message<TestLaneIdType>>,
outbound_lane_data: Option<OutboundLaneData>,
) -> Box<FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>> {
// first - let's generate storage proof
let lane = messages.first().unwrap().key.lane_id;
let nonces_start = messages.first().unwrap().key.nonce;
let nonces_end = messages.last().unwrap().key.nonce;
let (storage_root, storage_proof) =
prepare_messages_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
lane,
nonces_start..=nonces_end,
outbound_lane_data,
UnverifiedStorageProofParams::default(),
|nonce| messages[(nonce - nonces_start) as usize].payload.clone(),
encode_all_messages,
encode_lane_data,
false,
false,
);
// let's now insert bridged chain header into the storage
let bridged_header_hash = Default::default();
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
StoredHeaderData { number: 0, state_root: storage_root },
);
Box::new(FromBridgedChainMessagesProof::<BridgedHeaderHash, TestLaneIdType> {
bridged_header_hash,
storage_proof,
lane,
nonces_start,
nonces_end,
})
}
/// Prepare valid storage proof for given messages and insert appropriate header to the
/// bridged header chain.
///
/// Since this function changes the runtime storage, you can't "inline" it in the
/// `asset_noop` macro calls.
pub fn prepare_messages_delivery_proof(
lane: TestLaneIdType,
inbound_lane_data: InboundLaneData<AccountId>,
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, TestLaneIdType> {
// first - let's generate storage proof
let (storage_root, storage_proof) =
prepare_message_delivery_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
lane,
inbound_lane_data,
UnverifiedStorageProofParams::default(),
);
// let's now insert bridged chain header into the storage
let bridged_header_hash = Default::default();
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
StoredHeaderData { number: 0, state_root: storage_root },
);
FromBridgedChainMessagesDeliveryProof::<BridgedHeaderHash, TestLaneIdType> {
bridged_header_hash,
storage_proof,
lane,
}
}