mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 06:27:58 +00:00
2db84b74cc
* relay headers between Kusama and Polkadot * relay messages between Kusama and Polkadot * complex Kusama <> Polkadot relayer * expose relayer_fund_account_id from messages pallet * create relayers fund accounts on Kusama/Polkadot + some more fixes * fmt * fix compilation * compilation + clippy * compilation * MAXIMAL_BALANCE_DECREASE_PER_DAY for K<>P header relays * fmt * deduplicate tests * Update modules/messages/src/lib.rs Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * extract storage_parameter_key function * other grumbles * fix * fmt Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
2333 lines
75 KiB
Rust
2333 lines
75 KiB
Rust
// Copyright 2019-2021 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/>.
|
|
|
|
//! Runtime module that allows sending and receiving messages using lane concept:
|
|
//!
|
|
//! 1) the message is sent using `send_message()` call;
|
|
//! 2) every outbound message is assigned nonce;
|
|
//! 3) the messages are stored in the storage;
|
|
//! 4) external component (relay) delivers messages to bridged chain;
|
|
//! 5) messages are processed in order (ordered by assigned nonce);
|
|
//! 6) relay may send proof-of-delivery back to this chain.
|
|
//!
|
|
//! Once message is sent, its progress can be tracked by looking at module events.
|
|
//! The assigned nonce is reported using `MessageAccepted` event. When message is
|
|
//! delivered to the the bridged chain, it is reported using `MessagesDelivered` event.
|
|
//!
|
|
//! **IMPORTANT NOTE**: after generating weights (custom `WeighInfo` implementation) for
|
|
//! your runtime (where this module is plugged to), please add test for these weights.
|
|
//! The test should call the `ensure_weights_are_correct` function from this module.
|
|
//! If this test fails with your weights, then either weights are computed incorrectly,
|
|
//! or some benchmarks assumptions are broken for your runtime.
|
|
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
// Generated by `decl_event!`
|
|
#![allow(clippy::unused_unit)]
|
|
|
|
pub use crate::weights_ext::{
|
|
ensure_able_to_receive_confirmation, ensure_able_to_receive_message, ensure_weights_are_correct, WeightInfoExt,
|
|
EXPECTED_DEFAULT_MESSAGE_LENGTH,
|
|
};
|
|
|
|
use crate::inbound_lane::{InboundLane, InboundLaneStorage, ReceivalResult};
|
|
use crate::outbound_lane::{OutboundLane, OutboundLaneStorage, ReceivalConfirmationResult};
|
|
use crate::weights::WeightInfo;
|
|
|
|
use bp_messages::{
|
|
source_chain::{
|
|
LaneMessageVerifier, MessageDeliveryAndDispatchPayment, OnDeliveryConfirmed, OnMessageAccepted,
|
|
RelayersRewards, TargetHeaderChain,
|
|
},
|
|
target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
|
|
total_unrewarded_messages, DeliveredMessages, InboundLaneData, LaneId, MessageData, MessageKey, MessageNonce,
|
|
OperatingMode, OutboundLaneData, Parameter as MessagesParameter, UnrewardedRelayersState,
|
|
};
|
|
use bp_runtime::{ChainId, Size};
|
|
use codec::{Decode, Encode};
|
|
use frame_support::{
|
|
fail,
|
|
traits::Get,
|
|
weights::{Pays, PostDispatchInfo},
|
|
};
|
|
use frame_system::RawOrigin;
|
|
use num_traits::{SaturatingAdd, Zero};
|
|
use sp_core::H256;
|
|
use sp_runtime::traits::{BadOrigin, Convert};
|
|
use sp_std::{cell::RefCell, cmp::PartialOrd, marker::PhantomData, prelude::*};
|
|
|
|
mod inbound_lane;
|
|
mod outbound_lane;
|
|
mod weights_ext;
|
|
|
|
pub mod instant_payments;
|
|
pub mod weights;
|
|
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
pub mod benchmarking;
|
|
|
|
#[cfg(test)]
|
|
mod mock;
|
|
|
|
pub use pallet::*;
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
use super::*;
|
|
use frame_support::pallet_prelude::*;
|
|
use frame_system::pallet_prelude::*;
|
|
|
|
#[pallet::config]
|
|
pub trait Config<I: 'static = ()>: frame_system::Config {
|
|
// General types
|
|
|
|
/// The overarching event type.
|
|
type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>;
|
|
/// Benchmarks results from runtime we're plugged into.
|
|
type WeightInfo: WeightInfoExt;
|
|
|
|
/// Gets the chain id value from the instance.
|
|
#[pallet::constant]
|
|
type BridgedChainId: Get<ChainId>;
|
|
/// Pallet parameter that is opaque to the pallet itself, but may be used by the runtime
|
|
/// for integrating the pallet.
|
|
///
|
|
/// All pallet parameters may only be updated either by the root, or by the pallet owner.
|
|
type Parameter: MessagesParameter;
|
|
|
|
/// Maximal number of messages that may be pruned during maintenance. Maintenance occurs
|
|
/// whenever new message is sent. The reason is that if you want to use lane, you should
|
|
/// be ready to pay for its maintenance.
|
|
type MaxMessagesToPruneAtOnce: Get<MessageNonce>;
|
|
/// Maximal number of unrewarded relayer entries at inbound lane. Unrewarded means that the
|
|
/// relayer has delivered messages, but either confirmations haven't been delivered back to the
|
|
/// source chain, or we haven't received reward confirmations yet.
|
|
///
|
|
/// This constant limits maximal number of entries in the `InboundLaneData::relayers`. Keep
|
|
/// in mind that the same relayer account may take several (non-consecutive) entries in this
|
|
/// set.
|
|
type MaxUnrewardedRelayerEntriesAtInboundLane: Get<MessageNonce>;
|
|
/// Maximal number of unconfirmed messages at inbound lane. Unconfirmed means that the
|
|
/// message has been delivered, but either confirmations haven't been delivered back to the
|
|
/// source chain, or we haven't received reward confirmations for these messages yet.
|
|
///
|
|
/// This constant limits difference between last message from last entry of the
|
|
/// `InboundLaneData::relayers` and first message at the first entry.
|
|
///
|
|
/// There is no point of making this parameter lesser than MaxUnrewardedRelayerEntriesAtInboundLane,
|
|
/// because then maximal number of relayer entries will be limited by maximal number of messages.
|
|
///
|
|
/// This value also represents maximal number of messages in single delivery transaction. Transaction
|
|
/// that is declaring more messages than this value, will be rejected. Even if these messages are
|
|
/// from different lanes.
|
|
type MaxUnconfirmedMessagesAtInboundLane: Get<MessageNonce>;
|
|
|
|
/// Payload type of outbound messages. This payload is dispatched on the bridged chain.
|
|
type OutboundPayload: Parameter + Size;
|
|
/// Message fee type of outbound messages. This fee is paid on this chain.
|
|
type OutboundMessageFee: Default + From<u64> + PartialOrd + Parameter + SaturatingAdd + Zero;
|
|
|
|
/// Payload type of inbound messages. This payload is dispatched on this chain.
|
|
type InboundPayload: Decode;
|
|
/// Message fee type of inbound messages. This fee is paid on the bridged chain.
|
|
type InboundMessageFee: Decode;
|
|
/// Identifier of relayer that deliver messages to this chain. Relayer reward is paid on the bridged chain.
|
|
type InboundRelayer: Parameter;
|
|
|
|
/// A type which can be turned into an AccountId from a 256-bit hash.
|
|
///
|
|
/// Used when deriving the shared relayer fund account.
|
|
type AccountIdConverter: sp_runtime::traits::Convert<sp_core::hash::H256, Self::AccountId>;
|
|
|
|
// Types that are used by outbound_lane (on source chain).
|
|
|
|
/// Target header chain.
|
|
type TargetHeaderChain: TargetHeaderChain<Self::OutboundPayload, Self::AccountId>;
|
|
/// Message payload verifier.
|
|
type LaneMessageVerifier: LaneMessageVerifier<Self::AccountId, Self::OutboundPayload, Self::OutboundMessageFee>;
|
|
/// Message delivery payment.
|
|
type MessageDeliveryAndDispatchPayment: MessageDeliveryAndDispatchPayment<
|
|
Self::AccountId,
|
|
Self::OutboundMessageFee,
|
|
>;
|
|
/// Handler for accepted messages.
|
|
type OnMessageAccepted: OnMessageAccepted;
|
|
/// Handler for delivered messages.
|
|
type OnDeliveryConfirmed: OnDeliveryConfirmed;
|
|
|
|
// Types that are used by inbound_lane (on target chain).
|
|
|
|
/// Source header chain, as it is represented on target chain.
|
|
type SourceHeaderChain: SourceHeaderChain<Self::InboundMessageFee>;
|
|
/// Message dispatch.
|
|
type MessageDispatch: MessageDispatch<
|
|
Self::AccountId,
|
|
Self::InboundMessageFee,
|
|
DispatchPayload = Self::InboundPayload,
|
|
>;
|
|
}
|
|
|
|
/// Shortcut to messages proof type for Config.
|
|
type MessagesProofOf<T, I> =
|
|
<<T as Config<I>>::SourceHeaderChain as SourceHeaderChain<<T as Config<I>>::InboundMessageFee>>::MessagesProof;
|
|
/// Shortcut to messages delivery proof type for Config.
|
|
type MessagesDeliveryProofOf<T, I> = <<T as Config<I>>::TargetHeaderChain as TargetHeaderChain<
|
|
<T as Config<I>>::OutboundPayload,
|
|
<T as frame_system::Config>::AccountId,
|
|
>>::MessagesDeliveryProof;
|
|
|
|
#[pallet::pallet]
|
|
#[pallet::generate_store(pub(super) trait Store)]
|
|
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
|
|
|
|
#[pallet::call]
|
|
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
|
/// Change `PalletOwner`.
|
|
///
|
|
/// May only be called either by root, or by `PalletOwner`.
|
|
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
|
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
|
|
ensure_owner_or_root::<T, I>(origin)?;
|
|
match new_owner {
|
|
Some(new_owner) => {
|
|
PalletOwner::<T, I>::put(&new_owner);
|
|
log::info!(target: "runtime::bridge-messages", "Setting pallet Owner to: {:?}", new_owner);
|
|
}
|
|
None => {
|
|
PalletOwner::<T, I>::kill();
|
|
log::info!(target: "runtime::bridge-messages", "Removed Owner of pallet.");
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Halt or resume all/some pallet operations.
|
|
///
|
|
/// May only be called either by root, or by `PalletOwner`.
|
|
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
|
pub fn set_operating_mode(origin: OriginFor<T>, operating_mode: OperatingMode) -> DispatchResult {
|
|
ensure_owner_or_root::<T, I>(origin)?;
|
|
PalletOperatingMode::<T, I>::put(operating_mode);
|
|
log::info!(
|
|
target: "runtime::bridge-messages",
|
|
"Setting messages pallet operating mode to {:?}.",
|
|
operating_mode,
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
/// Update pallet parameter.
|
|
///
|
|
/// May only be called either by root, or by `PalletOwner`.
|
|
///
|
|
/// The weight is: single read for permissions check + 2 writes for parameter value and event.
|
|
#[pallet::weight((T::DbWeight::get().reads_writes(1, 2), DispatchClass::Operational))]
|
|
pub fn update_pallet_parameter(origin: OriginFor<T>, parameter: T::Parameter) -> DispatchResult {
|
|
ensure_owner_or_root::<T, I>(origin)?;
|
|
parameter.save();
|
|
Self::deposit_event(Event::ParameterUpdated(parameter));
|
|
Ok(())
|
|
}
|
|
|
|
/// Send message over lane.
|
|
#[pallet::weight(T::WeightInfo::send_message_weight(payload, T::DbWeight::get()))]
|
|
pub fn send_message(
|
|
origin: OriginFor<T>,
|
|
lane_id: LaneId,
|
|
payload: T::OutboundPayload,
|
|
delivery_and_dispatch_fee: T::OutboundMessageFee,
|
|
) -> DispatchResultWithPostInfo {
|
|
crate::send_message::<T, I>(
|
|
origin.into().map_err(|_| BadOrigin)?,
|
|
lane_id,
|
|
payload,
|
|
delivery_and_dispatch_fee,
|
|
)
|
|
.map(|sent_message| sent_message.post_dispatch_info)
|
|
}
|
|
|
|
/// Pay additional fee for the message.
|
|
#[pallet::weight(T::WeightInfo::maximal_increase_message_fee())]
|
|
pub fn increase_message_fee(
|
|
origin: OriginFor<T>,
|
|
lane_id: LaneId,
|
|
nonce: MessageNonce,
|
|
additional_fee: T::OutboundMessageFee,
|
|
) -> DispatchResultWithPostInfo {
|
|
ensure_not_halted::<T, I>()?;
|
|
// if someone tries to pay for already-delivered message, we're rejecting this intention
|
|
// (otherwise this additional fee will be locked forever in relayers fund)
|
|
//
|
|
// if someone tries to pay for not-yet-sent message, we're rejecting this intention, or
|
|
// we're risking to have mess in the storage
|
|
let lane = outbound_lane::<T, I>(lane_id);
|
|
ensure!(
|
|
nonce > lane.data().latest_received_nonce,
|
|
Error::<T, I>::MessageIsAlreadyDelivered
|
|
);
|
|
ensure!(
|
|
nonce <= lane.data().latest_generated_nonce,
|
|
Error::<T, I>::MessageIsNotYetSent
|
|
);
|
|
|
|
// withdraw additional fee from submitter
|
|
let submitter = origin.into().map_err(|_| BadOrigin)?;
|
|
T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee(
|
|
&submitter,
|
|
&additional_fee,
|
|
&relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
|
|
)
|
|
.map_err(|err| {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Submitter {:?} can't pay additional fee {:?} for the message {:?}/{:?} to {:?}: {:?}",
|
|
submitter,
|
|
additional_fee,
|
|
lane_id,
|
|
nonce,
|
|
relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
|
|
err,
|
|
);
|
|
|
|
Error::<T, I>::FailedToWithdrawMessageFee
|
|
})?;
|
|
|
|
// and finally update fee in the storage
|
|
let message_key = MessageKey { lane_id, nonce };
|
|
let message_size = OutboundMessages::<T, I>::mutate(message_key, |message_data| {
|
|
// saturating_add is fine here - overflow here means that someone controls all
|
|
// chain funds, which shouldn't ever happen + `pay_delivery_and_dispatch_fee`
|
|
// above will fail before we reach here
|
|
let message_data = message_data
|
|
.as_mut()
|
|
.expect("the message is sent and not yet delivered; so it is in the storage; qed");
|
|
message_data.fee = message_data.fee.saturating_add(&additional_fee);
|
|
message_data.payload.len()
|
|
});
|
|
|
|
// compute actual dispatch weight that depends on the stored message size
|
|
let actual_weight = sp_std::cmp::min(
|
|
T::WeightInfo::maximal_increase_message_fee(),
|
|
T::WeightInfo::increase_message_fee(message_size as _),
|
|
);
|
|
|
|
Ok(PostDispatchInfo {
|
|
actual_weight: Some(actual_weight),
|
|
pays_fee: Pays::Yes,
|
|
})
|
|
}
|
|
|
|
/// Receive messages proof from bridged chain.
|
|
///
|
|
/// The weight of the call assumes that the transaction always brings outbound lane
|
|
/// state update. Because of that, the submitter (relayer) has no benefit of not including
|
|
/// this data in the transaction, so reward confirmations lags should be minimal.
|
|
#[pallet::weight(T::WeightInfo::receive_messages_proof_weight(proof, *messages_count, *dispatch_weight))]
|
|
pub fn receive_messages_proof(
|
|
origin: OriginFor<T>,
|
|
relayer_id_at_bridged_chain: T::InboundRelayer,
|
|
proof: MessagesProofOf<T, I>,
|
|
messages_count: u32,
|
|
dispatch_weight: Weight,
|
|
) -> DispatchResultWithPostInfo {
|
|
ensure_not_halted::<T, I>()?;
|
|
let relayer_id_at_this_chain = ensure_signed(origin)?;
|
|
|
|
// reject transactions that are declaring too many messages
|
|
ensure!(
|
|
MessageNonce::from(messages_count) <= T::MaxUnconfirmedMessagesAtInboundLane::get(),
|
|
Error::<T, I>::TooManyMessagesInTheProof
|
|
);
|
|
|
|
// why do we need to know the weight of this (`receive_messages_proof`) call? Because
|
|
// we may want to return some funds for not-dispatching (or partially dispatching) some
|
|
// messages to the call origin (relayer). And this is done by returning actual weight
|
|
// from the call. But we only know dispatch weight of every messages. So to refund relayer
|
|
// because we have not dispatched Message, we need to:
|
|
//
|
|
// ActualWeight = DeclaredWeight - Message.DispatchWeight
|
|
//
|
|
// The DeclaredWeight is exactly what's computed here. Unfortunately it is impossible
|
|
// to get pre-computed value (and it has been already computed by the executive).
|
|
let declared_weight = T::WeightInfo::receive_messages_proof_weight(&proof, messages_count, dispatch_weight);
|
|
let mut actual_weight = declared_weight;
|
|
|
|
// verify messages proof && convert proof into messages
|
|
let messages = verify_and_decode_messages_proof::<
|
|
T::SourceHeaderChain,
|
|
T::InboundMessageFee,
|
|
T::InboundPayload,
|
|
>(proof, messages_count)
|
|
.map_err(|err| {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Rejecting invalid messages proof: {:?}",
|
|
err,
|
|
);
|
|
|
|
Error::<T, I>::InvalidMessagesProof
|
|
})?;
|
|
|
|
// dispatch messages and (optionally) update lane(s) state(s)
|
|
let mut total_messages = 0;
|
|
let mut valid_messages = 0;
|
|
let mut dispatch_weight_left = dispatch_weight;
|
|
for (lane_id, lane_data) in messages {
|
|
let mut lane = inbound_lane::<T, I>(lane_id);
|
|
|
|
if let Some(lane_state) = lane_data.lane_state {
|
|
let updated_latest_confirmed_nonce = lane.receive_state_update(lane_state);
|
|
if let Some(updated_latest_confirmed_nonce) = updated_latest_confirmed_nonce {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Received lane {:?} state update: latest_confirmed_nonce={}",
|
|
lane_id,
|
|
updated_latest_confirmed_nonce,
|
|
);
|
|
}
|
|
}
|
|
|
|
for message in lane_data.messages {
|
|
debug_assert_eq!(message.key.lane_id, lane_id);
|
|
|
|
// ensure that relayer has declared enough weight for dispatching next message on
|
|
// this lane. We can't dispatch lane messages out-of-order, so if declared weight
|
|
// is not enough, let's move to next lane
|
|
let dispatch_weight = T::MessageDispatch::dispatch_weight(&message);
|
|
if dispatch_weight > dispatch_weight_left {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Cannot dispatch any more messages on lane {:?}. Weight: declared={}, left={}",
|
|
lane_id,
|
|
dispatch_weight,
|
|
dispatch_weight_left,
|
|
);
|
|
break;
|
|
}
|
|
total_messages += 1;
|
|
|
|
let receival_result = lane.receive_message::<T::MessageDispatch, T::AccountId>(
|
|
&relayer_id_at_bridged_chain,
|
|
&relayer_id_at_this_chain,
|
|
message.key.nonce,
|
|
message.data,
|
|
);
|
|
|
|
// note that we're returning unspent weight to relayer even if message has been
|
|
// rejected by the lane. This allows relayers to submit spam transactions with
|
|
// e.g. the same set of already delivered messages over and over again, without
|
|
// losing funds for messages dispatch. But keep in mind that relayer pays base
|
|
// delivery transaction cost anyway. And base cost covers everything except
|
|
// dispatch, so we have a balance here.
|
|
let (unspent_weight, refund_pay_dispatch_fee) = match receival_result {
|
|
ReceivalResult::Dispatched(dispatch_result) => {
|
|
valid_messages += 1;
|
|
(
|
|
dispatch_result.unspent_weight,
|
|
!dispatch_result.dispatch_fee_paid_during_dispatch,
|
|
)
|
|
}
|
|
ReceivalResult::InvalidNonce
|
|
| ReceivalResult::TooManyUnrewardedRelayers
|
|
| ReceivalResult::TooManyUnconfirmedMessages => (dispatch_weight, true),
|
|
};
|
|
|
|
let unspent_weight = sp_std::cmp::min(unspent_weight, dispatch_weight);
|
|
dispatch_weight_left -= dispatch_weight - unspent_weight;
|
|
actual_weight = actual_weight.saturating_sub(unspent_weight).saturating_sub(
|
|
// delivery call weight formula assumes that the fee is paid at
|
|
// this (target) chain. If the message is prepaid at the source
|
|
// chain, let's refund relayer with this extra cost.
|
|
if refund_pay_dispatch_fee {
|
|
T::WeightInfo::pay_inbound_dispatch_fee_overhead()
|
|
} else {
|
|
0
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Received messages: total={}, valid={}. Weight used: {}/{}",
|
|
total_messages,
|
|
valid_messages,
|
|
actual_weight,
|
|
declared_weight,
|
|
);
|
|
|
|
Ok(PostDispatchInfo {
|
|
actual_weight: Some(actual_weight),
|
|
pays_fee: Pays::Yes,
|
|
})
|
|
}
|
|
|
|
/// Receive messages delivery proof from bridged chain.
|
|
#[pallet::weight(T::WeightInfo::receive_messages_delivery_proof_weight(
|
|
proof,
|
|
relayers_state,
|
|
T::DbWeight::get(),
|
|
))]
|
|
pub fn receive_messages_delivery_proof(
|
|
origin: OriginFor<T>,
|
|
proof: MessagesDeliveryProofOf<T, I>,
|
|
relayers_state: UnrewardedRelayersState,
|
|
) -> DispatchResultWithPostInfo {
|
|
ensure_not_halted::<T, I>()?;
|
|
|
|
// why do we need to know the weight of this (`receive_messages_delivery_proof`) call? Because
|
|
// we may want to return some funds for messages that are not processed by the delivery callback,
|
|
// or if their actual processing weight is less than accounted by weight formula.
|
|
// So to refund relayer, we need to:
|
|
//
|
|
// ActualWeight = DeclaredWeight - UnspentCallbackWeight
|
|
//
|
|
// The DeclaredWeight is exactly what's computed here. Unfortunately it is impossible
|
|
// to get pre-computed value (and it has been already computed by the executive).
|
|
let single_message_callback_overhead = T::WeightInfo::single_message_callback_overhead(T::DbWeight::get());
|
|
let declared_weight =
|
|
T::WeightInfo::receive_messages_delivery_proof_weight(&proof, &relayers_state, T::DbWeight::get());
|
|
let mut actual_weight = declared_weight;
|
|
|
|
let confirmation_relayer = ensure_signed(origin)?;
|
|
let (lane_id, lane_data) = T::TargetHeaderChain::verify_messages_delivery_proof(proof).map_err(|err| {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Rejecting invalid messages delivery proof: {:?}",
|
|
err,
|
|
);
|
|
|
|
Error::<T, I>::InvalidMessagesDeliveryProof
|
|
})?;
|
|
|
|
// verify that the relayer has declared correct `lane_data::relayers` state
|
|
// (we only care about total number of entries and messages, because this affects call weight)
|
|
ensure!(
|
|
total_unrewarded_messages(&lane_data.relayers).unwrap_or(MessageNonce::MAX)
|
|
== relayers_state.total_messages
|
|
&& lane_data.relayers.len() as MessageNonce == relayers_state.unrewarded_relayer_entries,
|
|
Error::<T, I>::InvalidUnrewardedRelayersState
|
|
);
|
|
|
|
// mark messages as delivered
|
|
let mut lane = outbound_lane::<T, I>(lane_id);
|
|
let mut relayers_rewards: RelayersRewards<_, T::OutboundMessageFee> = RelayersRewards::new();
|
|
let last_delivered_nonce = lane_data.last_delivered_nonce();
|
|
let confirmed_messages =
|
|
match lane.confirm_delivery(relayers_state.total_messages, last_delivered_nonce, &lane_data.relayers) {
|
|
ReceivalConfirmationResult::ConfirmedMessages(confirmed_messages) => Some(confirmed_messages),
|
|
ReceivalConfirmationResult::NoNewConfirmations => None,
|
|
ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected(to_confirm_messages_count) => {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Messages delivery proof contains too many messages to confirm: {} vs declared {}",
|
|
to_confirm_messages_count,
|
|
relayers_state.total_messages,
|
|
);
|
|
|
|
fail!(Error::<T, I>::TryingToConfirmMoreMessagesThanExpected);
|
|
}
|
|
error => {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Messages delivery proof contains invalid unrewarded relayers vec: {:?}",
|
|
error,
|
|
);
|
|
|
|
fail!(Error::<T, I>::InvalidUnrewardedRelayers);
|
|
}
|
|
};
|
|
|
|
if let Some(confirmed_messages) = confirmed_messages {
|
|
// handle messages delivery confirmation
|
|
let preliminary_callback_overhead = relayers_state
|
|
.total_messages
|
|
.saturating_mul(single_message_callback_overhead);
|
|
let actual_callback_weight =
|
|
T::OnDeliveryConfirmed::on_messages_delivered(&lane_id, &confirmed_messages);
|
|
match preliminary_callback_overhead.checked_sub(actual_callback_weight) {
|
|
Some(difference) if difference == 0 => (),
|
|
Some(difference) => {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"T::OnDeliveryConfirmed callback has spent less weight than expected. Refunding: \
|
|
{} - {} = {}",
|
|
preliminary_callback_overhead,
|
|
actual_callback_weight,
|
|
difference,
|
|
);
|
|
actual_weight = actual_weight.saturating_sub(difference);
|
|
}
|
|
None => {
|
|
debug_assert!(false, "T::OnDeliveryConfirmed callback consumed too much weight.");
|
|
log::error!(
|
|
target: "runtime::bridge-messages",
|
|
"T::OnDeliveryConfirmed callback has spent more weight that it is allowed to: \
|
|
{} vs {}",
|
|
preliminary_callback_overhead,
|
|
actual_callback_weight,
|
|
);
|
|
}
|
|
}
|
|
|
|
// emit 'delivered' event
|
|
let received_range = confirmed_messages.begin..=confirmed_messages.end;
|
|
Self::deposit_event(Event::MessagesDelivered(lane_id, confirmed_messages));
|
|
|
|
// remember to reward relayers that have delivered messages
|
|
// this loop is bounded by `T::MaxUnrewardedRelayerEntriesAtInboundLane` on the bridged chain
|
|
for entry in lane_data.relayers {
|
|
let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start());
|
|
let nonce_end = sp_std::cmp::min(entry.messages.end, *received_range.end());
|
|
|
|
// loop won't proceed if current entry is ahead of received range (begin > end).
|
|
// this loop is bound by `T::MaxUnconfirmedMessagesAtInboundLane` on the bridged chain
|
|
let mut relayer_reward = relayers_rewards.entry(entry.relayer).or_default();
|
|
for nonce in nonce_begin..nonce_end + 1 {
|
|
let message_data = OutboundMessages::<T, I>::get(MessageKey { lane_id, nonce })
|
|
.expect("message was just confirmed; we never prune unconfirmed messages; qed");
|
|
relayer_reward.reward = relayer_reward.reward.saturating_add(&message_data.fee);
|
|
relayer_reward.messages += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if some new messages have been confirmed, reward relayers
|
|
if !relayers_rewards.is_empty() {
|
|
let relayer_fund_account = relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>();
|
|
<T as Config<I>>::MessageDeliveryAndDispatchPayment::pay_relayers_rewards(
|
|
&confirmation_relayer,
|
|
relayers_rewards,
|
|
&relayer_fund_account,
|
|
);
|
|
}
|
|
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Received messages delivery proof up to (and including) {} at lane {:?}",
|
|
last_delivered_nonce,
|
|
lane_id,
|
|
);
|
|
|
|
Ok(PostDispatchInfo {
|
|
actual_weight: Some(actual_weight),
|
|
pays_fee: Pays::Yes,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[pallet::event]
|
|
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
|
#[pallet::metadata(T::Parameter = "Parameter")]
|
|
pub enum Event<T: Config<I>, I: 'static = ()> {
|
|
/// Pallet parameter has been updated.
|
|
ParameterUpdated(T::Parameter),
|
|
/// Message has been accepted and is waiting to be delivered.
|
|
MessageAccepted(LaneId, MessageNonce),
|
|
/// Messages in the inclusive range have been delivered to the bridged chain.
|
|
MessagesDelivered(LaneId, DeliveredMessages),
|
|
}
|
|
|
|
#[pallet::error]
|
|
pub enum Error<T, I = ()> {
|
|
/// All pallet operations are halted.
|
|
Halted,
|
|
/// Message has been treated as invalid by chain verifier.
|
|
MessageRejectedByChainVerifier,
|
|
/// Message has been treated as invalid by lane verifier.
|
|
MessageRejectedByLaneVerifier,
|
|
/// Submitter has failed to pay fee for delivering and dispatching messages.
|
|
FailedToWithdrawMessageFee,
|
|
/// The transaction brings too many messages.
|
|
TooManyMessagesInTheProof,
|
|
/// Invalid messages has been submitted.
|
|
InvalidMessagesProof,
|
|
/// Invalid messages delivery proof has been submitted.
|
|
InvalidMessagesDeliveryProof,
|
|
/// The bridged chain has invalid `UnrewardedRelayers` in its storage (fatal for the lane).
|
|
InvalidUnrewardedRelayers,
|
|
/// The relayer has declared invalid unrewarded relayers state in the `receive_messages_delivery_proof` call.
|
|
InvalidUnrewardedRelayersState,
|
|
/// The message someone is trying to work with (i.e. increase fee) is already-delivered.
|
|
MessageIsAlreadyDelivered,
|
|
/// The message someone is trying to work with (i.e. increase fee) is not yet sent.
|
|
MessageIsNotYetSent,
|
|
/// The number of actually confirmed messages is going to be larger than the number of messages in the proof.
|
|
/// This may mean that this or bridged chain storage is corrupted.
|
|
TryingToConfirmMoreMessagesThanExpected,
|
|
}
|
|
|
|
/// Optional pallet owner.
|
|
///
|
|
/// Pallet owner has a right to halt all pallet operations and then resume it. If it is
|
|
/// `None`, then there are no direct ways to halt/resume pallet operations, but other
|
|
/// runtime methods may still be used to do that (i.e. democracy::referendum to update halt
|
|
/// flag directly or call the `halt_operations`).
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn module_owner)]
|
|
pub type PalletOwner<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
|
|
|
|
/// The current operating mode of the pallet.
|
|
///
|
|
/// Depending on the mode either all, some, or no transactions will be allowed.
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn operating_mode)]
|
|
pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> = StorageValue<_, OperatingMode, ValueQuery>;
|
|
|
|
/// Map of lane id => inbound lane data.
|
|
#[pallet::storage]
|
|
pub type InboundLanes<T: Config<I>, I: 'static = ()> =
|
|
StorageMap<_, Blake2_128Concat, LaneId, InboundLaneData<T::InboundRelayer>, ValueQuery>;
|
|
|
|
/// Map of lane id => outbound lane data.
|
|
#[pallet::storage]
|
|
pub type OutboundLanes<T: Config<I>, I: 'static = ()> =
|
|
StorageMap<_, Blake2_128Concat, LaneId, OutboundLaneData, ValueQuery>;
|
|
|
|
/// All queued outbound messages.
|
|
#[pallet::storage]
|
|
pub type OutboundMessages<T: Config<I>, I: 'static = ()> =
|
|
StorageMap<_, Blake2_128Concat, MessageKey, MessageData<T::OutboundMessageFee>>;
|
|
|
|
#[pallet::genesis_config]
|
|
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
|
|
/// Initial pallet operating mode.
|
|
pub operating_mode: OperatingMode,
|
|
/// Initial pallet owner.
|
|
pub owner: Option<T::AccountId>,
|
|
/// Dummy marker.
|
|
pub phantom: sp_std::marker::PhantomData<I>,
|
|
}
|
|
|
|
#[cfg(feature = "std")]
|
|
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
|
|
fn default() -> Self {
|
|
Self {
|
|
operating_mode: Default::default(),
|
|
owner: Default::default(),
|
|
phantom: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pallet::genesis_build]
|
|
impl<T: Config<I>, I: 'static> GenesisBuild<T, I> for GenesisConfig<T, I> {
|
|
fn build(&self) {
|
|
PalletOperatingMode::<T, I>::put(&self.operating_mode);
|
|
if let Some(ref owner) = self.owner {
|
|
PalletOwner::<T, I>::put(owner);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
|
/// Get stored data of the outbound message with given nonce.
|
|
pub fn outbound_message_data(lane: LaneId, nonce: MessageNonce) -> Option<MessageData<T::OutboundMessageFee>> {
|
|
OutboundMessages::<T, I>::get(MessageKey { lane_id: lane, nonce })
|
|
}
|
|
|
|
/// Get nonce of the latest generated message at given outbound lane.
|
|
pub fn outbound_latest_generated_nonce(lane: LaneId) -> MessageNonce {
|
|
OutboundLanes::<T, I>::get(&lane).latest_generated_nonce
|
|
}
|
|
|
|
/// Get nonce of the latest confirmed message at given outbound lane.
|
|
pub fn outbound_latest_received_nonce(lane: LaneId) -> MessageNonce {
|
|
OutboundLanes::<T, I>::get(&lane).latest_received_nonce
|
|
}
|
|
|
|
/// Get nonce of the latest received message at given inbound lane.
|
|
pub fn inbound_latest_received_nonce(lane: LaneId) -> MessageNonce {
|
|
InboundLanes::<T, I>::get(&lane).last_delivered_nonce()
|
|
}
|
|
|
|
/// Get nonce of the latest confirmed message at given inbound lane.
|
|
pub fn inbound_latest_confirmed_nonce(lane: LaneId) -> MessageNonce {
|
|
InboundLanes::<T, I>::get(&lane).last_confirmed_nonce
|
|
}
|
|
|
|
/// Get state of unrewarded relayers set.
|
|
pub fn inbound_unrewarded_relayers_state(lane: bp_messages::LaneId) -> bp_messages::UnrewardedRelayersState {
|
|
let relayers = InboundLanes::<T, I>::get(&lane).relayers;
|
|
bp_messages::UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: relayers.len() as _,
|
|
messages_in_oldest_entry: relayers
|
|
.front()
|
|
.map(|entry| 1 + entry.messages.end - entry.messages.begin)
|
|
.unwrap_or(0),
|
|
total_messages: total_unrewarded_messages(&relayers).unwrap_or(MessageNonce::MAX),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Getting storage keys for messages and lanes states. These keys are normally used when building
|
|
/// messages and lanes states proofs.
|
|
pub mod storage_keys {
|
|
use super::*;
|
|
use frame_support::StorageHasher;
|
|
use sp_core::storage::StorageKey;
|
|
|
|
/// Storage key of the outbound message in the runtime storage.
|
|
pub fn message_key(pallet_prefix: &str, lane: &LaneId, nonce: MessageNonce) -> StorageKey {
|
|
storage_map_final_key(
|
|
pallet_prefix,
|
|
"OutboundMessages",
|
|
&MessageKey { lane_id: *lane, nonce }.encode(),
|
|
)
|
|
}
|
|
|
|
/// Storage key of the outbound message lane state in the runtime storage.
|
|
pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey {
|
|
storage_map_final_key(pallet_prefix, "OutboundLanes", lane)
|
|
}
|
|
|
|
/// Storage key of the inbound message lane state in the runtime storage.
|
|
pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey {
|
|
storage_map_final_key(pallet_prefix, "InboundLanes", lane)
|
|
}
|
|
|
|
/// This is a copypaste of the `frame_support::storage::generator::StorageMap::storage_map_final_key`.
|
|
fn storage_map_final_key(pallet_prefix: &str, map_name: &str, key: &[u8]) -> StorageKey {
|
|
let pallet_prefix_hashed = frame_support::Twox128::hash(pallet_prefix.as_bytes());
|
|
let storage_prefix_hashed = frame_support::Twox128::hash(map_name.as_bytes());
|
|
let key_hashed = frame_support::Blake2_128Concat::hash(key);
|
|
|
|
let mut final_key =
|
|
Vec::with_capacity(pallet_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len());
|
|
|
|
final_key.extend_from_slice(&pallet_prefix_hashed[..]);
|
|
final_key.extend_from_slice(&storage_prefix_hashed[..]);
|
|
final_key.extend_from_slice(key_hashed.as_ref());
|
|
|
|
StorageKey(final_key)
|
|
}
|
|
}
|
|
|
|
/// AccountId of the shared relayer fund account.
|
|
///
|
|
/// This account is passed to `MessageDeliveryAndDispatchPayment` trait, and depending
|
|
/// on the implementation it can be used to store relayers rewards.
|
|
/// See [`InstantCurrencyPayments`] for a concrete implementation.
|
|
pub fn relayer_fund_account_id<AccountId, AccountIdConverter: Convert<H256, AccountId>>() -> AccountId {
|
|
let encoded_id = bp_runtime::derive_relayer_fund_account_id(bp_runtime::NO_INSTANCE_ID);
|
|
AccountIdConverter::convert(encoded_id)
|
|
}
|
|
|
|
impl<T, I> bp_messages::source_chain::MessagesBridge<T::AccountId, T::OutboundMessageFee, T::OutboundPayload>
|
|
for Pallet<T, I>
|
|
where
|
|
T: Config<I>,
|
|
I: 'static,
|
|
{
|
|
type Error = sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>;
|
|
|
|
fn send_message(
|
|
sender: bp_messages::source_chain::Sender<T::AccountId>,
|
|
lane: LaneId,
|
|
message: T::OutboundPayload,
|
|
delivery_and_dispatch_fee: T::OutboundMessageFee,
|
|
) -> Result<MessageNonce, Self::Error> {
|
|
crate::send_message::<T, I>(sender, lane, message, delivery_and_dispatch_fee)
|
|
.map(|sent_message| sent_message.nonce)
|
|
}
|
|
}
|
|
|
|
/// Message that has been sent.
|
|
struct SentMessage {
|
|
/// Nonce of the message.
|
|
pub nonce: MessageNonce,
|
|
/// Post-dispatch call info.
|
|
pub post_dispatch_info: PostDispatchInfo,
|
|
}
|
|
|
|
/// Function that actually sends message.
|
|
fn send_message<T: Config<I>, I: 'static>(
|
|
submitter: bp_messages::source_chain::Sender<T::AccountId>,
|
|
lane_id: LaneId,
|
|
payload: T::OutboundPayload,
|
|
delivery_and_dispatch_fee: T::OutboundMessageFee,
|
|
) -> sp_std::result::Result<SentMessage, sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>> {
|
|
ensure_normal_operating_mode::<T, I>()?;
|
|
|
|
// initially, actual (post-dispatch) weight is equal to pre-dispatch weight
|
|
let mut actual_weight = T::WeightInfo::send_message_weight(&payload, T::DbWeight::get());
|
|
|
|
// let's first check if message can be delivered to target chain
|
|
T::TargetHeaderChain::verify_message(&payload).map_err(|err| {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Message to lane {:?} is rejected by target chain: {:?}",
|
|
lane_id,
|
|
err,
|
|
);
|
|
|
|
Error::<T, I>::MessageRejectedByChainVerifier
|
|
})?;
|
|
|
|
// now let's enforce any additional lane rules
|
|
let mut lane = outbound_lane::<T, I>(lane_id);
|
|
T::LaneMessageVerifier::verify_message(&submitter, &delivery_and_dispatch_fee, &lane_id, &lane.data(), &payload)
|
|
.map_err(|err| {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Message to lane {:?} is rejected by lane verifier: {:?}",
|
|
lane_id,
|
|
err,
|
|
);
|
|
|
|
Error::<T, I>::MessageRejectedByLaneVerifier
|
|
})?;
|
|
|
|
// let's withdraw delivery and dispatch fee from submitter
|
|
T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee(
|
|
&submitter,
|
|
&delivery_and_dispatch_fee,
|
|
&relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
|
|
)
|
|
.map_err(|err| {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Message to lane {:?} is rejected because submitter {:?} is unable to pay fee {:?}: {:?}",
|
|
lane_id,
|
|
submitter,
|
|
delivery_and_dispatch_fee,
|
|
err,
|
|
);
|
|
|
|
Error::<T, I>::FailedToWithdrawMessageFee
|
|
})?;
|
|
|
|
// finally, save message in outbound storage and emit event
|
|
let encoded_payload = payload.encode();
|
|
let encoded_payload_len = encoded_payload.len();
|
|
let nonce = lane.send_message(MessageData {
|
|
payload: encoded_payload,
|
|
fee: delivery_and_dispatch_fee,
|
|
});
|
|
// Guaranteed to be called outside only when the message is accepted.
|
|
// We assume that the maximum weight call back used is `single_message_callback_overhead`, so do not perform
|
|
// complex db operation in callback. If you want to, put these magic logic in outside pallet and control
|
|
// the weight there.
|
|
let single_message_callback_overhead = T::WeightInfo::single_message_callback_overhead(T::DbWeight::get());
|
|
let actual_callback_weight = T::OnMessageAccepted::on_messages_accepted(&lane_id, &nonce);
|
|
match single_message_callback_overhead.checked_sub(actual_callback_weight) {
|
|
Some(difference) if difference == 0 => (),
|
|
Some(difference) => {
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"T::OnMessageAccepted callback has spent less weight than expected. Refunding: \
|
|
{} - {} = {}",
|
|
single_message_callback_overhead,
|
|
actual_callback_weight,
|
|
difference,
|
|
);
|
|
actual_weight = actual_weight.saturating_sub(difference);
|
|
}
|
|
None => {
|
|
debug_assert!(false, "T::OnMessageAccepted callback consumed too much weight.");
|
|
log::error!(
|
|
target: "runtime::bridge-messages",
|
|
"T::OnMessageAccepted callback has spent more weight that it is allowed to: \
|
|
{} vs {}",
|
|
single_message_callback_overhead,
|
|
actual_callback_weight,
|
|
);
|
|
}
|
|
}
|
|
|
|
// message sender pays for pruning at most `MaxMessagesToPruneAtOnce` messages
|
|
// the cost of pruning every message is roughly single db write
|
|
// => lets refund sender if less than `MaxMessagesToPruneAtOnce` messages pruned
|
|
let max_messages_to_prune = T::MaxMessagesToPruneAtOnce::get();
|
|
let pruned_messages = lane.prune_messages(max_messages_to_prune);
|
|
if let Some(extra_messages) = max_messages_to_prune.checked_sub(pruned_messages) {
|
|
actual_weight = actual_weight.saturating_sub(T::DbWeight::get().writes(extra_messages));
|
|
}
|
|
|
|
log::trace!(
|
|
target: "runtime::bridge-messages",
|
|
"Accepted message {} to lane {:?}. Message size: {:?}",
|
|
nonce,
|
|
lane_id,
|
|
encoded_payload_len,
|
|
);
|
|
|
|
Pallet::<T, I>::deposit_event(Event::MessageAccepted(lane_id, nonce));
|
|
|
|
Ok(SentMessage {
|
|
nonce,
|
|
post_dispatch_info: PostDispatchInfo {
|
|
actual_weight: Some(actual_weight),
|
|
pays_fee: Pays::Yes,
|
|
},
|
|
})
|
|
}
|
|
|
|
/// Ensure that the origin is either root, or `PalletOwner`.
|
|
fn ensure_owner_or_root<T: Config<I>, I: 'static>(origin: T::Origin) -> Result<(), BadOrigin> {
|
|
match origin.into() {
|
|
Ok(RawOrigin::Root) => Ok(()),
|
|
Ok(RawOrigin::Signed(ref signer)) if Some(signer) == Pallet::<T, I>::module_owner().as_ref() => Ok(()),
|
|
_ => Err(BadOrigin),
|
|
}
|
|
}
|
|
|
|
/// Ensure that the pallet is in normal operational mode.
|
|
fn ensure_normal_operating_mode<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
|
|
if PalletOperatingMode::<T, I>::get() != OperatingMode::Normal {
|
|
Err(Error::<T, I>::Halted)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Ensure that the pallet is not halted.
|
|
fn ensure_not_halted<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
|
|
if PalletOperatingMode::<T, I>::get() == OperatingMode::Halted {
|
|
Err(Error::<T, I>::Halted)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Creates new inbound lane object, backed by runtime storage.
|
|
fn inbound_lane<T: Config<I>, I: 'static>(lane_id: LaneId) -> InboundLane<RuntimeInboundLaneStorage<T, I>> {
|
|
InboundLane::new(inbound_lane_storage::<T, I>(lane_id))
|
|
}
|
|
|
|
/// Creates new runtime inbound lane storage.
|
|
fn inbound_lane_storage<T: Config<I>, I: 'static>(lane_id: LaneId) -> RuntimeInboundLaneStorage<T, I> {
|
|
RuntimeInboundLaneStorage {
|
|
lane_id,
|
|
cached_data: RefCell::new(None),
|
|
_phantom: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// Creates new outbound lane object, backed by runtime storage.
|
|
fn outbound_lane<T: Config<I>, I: 'static>(lane_id: LaneId) -> OutboundLane<RuntimeOutboundLaneStorage<T, I>> {
|
|
OutboundLane::new(RuntimeOutboundLaneStorage {
|
|
lane_id,
|
|
_phantom: Default::default(),
|
|
})
|
|
}
|
|
|
|
/// Runtime inbound lane storage.
|
|
struct RuntimeInboundLaneStorage<T: Config<I>, I: 'static = ()> {
|
|
lane_id: LaneId,
|
|
cached_data: RefCell<Option<InboundLaneData<T::InboundRelayer>>>,
|
|
_phantom: PhantomData<I>,
|
|
}
|
|
|
|
impl<T: Config<I>, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage<T, I> {
|
|
type MessageFee = T::InboundMessageFee;
|
|
type Relayer = T::InboundRelayer;
|
|
|
|
fn id(&self) -> LaneId {
|
|
self.lane_id
|
|
}
|
|
|
|
fn max_unrewarded_relayer_entries(&self) -> MessageNonce {
|
|
T::MaxUnrewardedRelayerEntriesAtInboundLane::get()
|
|
}
|
|
|
|
fn max_unconfirmed_messages(&self) -> MessageNonce {
|
|
T::MaxUnconfirmedMessagesAtInboundLane::get()
|
|
}
|
|
|
|
fn data(&self) -> InboundLaneData<T::InboundRelayer> {
|
|
match self.cached_data.clone().into_inner() {
|
|
Some(data) => data,
|
|
None => {
|
|
let data = InboundLanes::<T, I>::get(&self.lane_id);
|
|
*self.cached_data.try_borrow_mut().expect(
|
|
"we're in the single-threaded environment;\
|
|
we have no recursive borrows; qed",
|
|
) = Some(data.clone());
|
|
data
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_data(&mut self, data: InboundLaneData<T::InboundRelayer>) {
|
|
*self.cached_data.try_borrow_mut().expect(
|
|
"we're in the single-threaded environment;\
|
|
we have no recursive borrows; qed",
|
|
) = Some(data.clone());
|
|
InboundLanes::<T, I>::insert(&self.lane_id, data)
|
|
}
|
|
}
|
|
|
|
/// Runtime outbound lane storage.
|
|
struct RuntimeOutboundLaneStorage<T, I = ()> {
|
|
lane_id: LaneId,
|
|
_phantom: PhantomData<(T, I)>,
|
|
}
|
|
|
|
impl<T: Config<I>, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage<T, I> {
|
|
type MessageFee = T::OutboundMessageFee;
|
|
|
|
fn id(&self) -> LaneId {
|
|
self.lane_id
|
|
}
|
|
|
|
fn data(&self) -> OutboundLaneData {
|
|
OutboundLanes::<T, I>::get(&self.lane_id)
|
|
}
|
|
|
|
fn set_data(&mut self, data: OutboundLaneData) {
|
|
OutboundLanes::<T, I>::insert(&self.lane_id, data)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn message(&self, nonce: &MessageNonce) -> Option<MessageData<T::OutboundMessageFee>> {
|
|
OutboundMessages::<T, I>::get(MessageKey {
|
|
lane_id: self.lane_id,
|
|
nonce: *nonce,
|
|
})
|
|
}
|
|
|
|
fn save_message(&mut self, nonce: MessageNonce, mesage_data: MessageData<T::OutboundMessageFee>) {
|
|
OutboundMessages::<T, I>::insert(
|
|
MessageKey {
|
|
lane_id: self.lane_id,
|
|
nonce,
|
|
},
|
|
mesage_data,
|
|
);
|
|
}
|
|
|
|
fn remove_message(&mut self, nonce: &MessageNonce) {
|
|
OutboundMessages::<T, I>::remove(MessageKey {
|
|
lane_id: self.lane_id,
|
|
nonce: *nonce,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Verify messages proof and return proved messages with decoded payload.
|
|
fn verify_and_decode_messages_proof<Chain: SourceHeaderChain<Fee>, Fee, DispatchPayload: Decode>(
|
|
proof: Chain::MessagesProof,
|
|
messages_count: u32,
|
|
) -> Result<ProvedMessages<DispatchMessage<DispatchPayload, Fee>>, Chain::Error> {
|
|
// `receive_messages_proof` weight formula and `MaxUnconfirmedMessagesAtInboundLane` check
|
|
// guarantees that the `message_count` is sane and Vec<Message> may be allocated.
|
|
// (tx with too many messages will either be rejected from the pool, or will fail earlier)
|
|
Chain::verify_messages_proof(proof, messages_count).map(|messages_by_lane| {
|
|
messages_by_lane
|
|
.into_iter()
|
|
.map(|(lane, lane_data)| {
|
|
(
|
|
lane,
|
|
ProvedLaneMessages {
|
|
lane_state: lane_data.lane_state,
|
|
messages: lane_data.messages.into_iter().map(Into::into).collect(),
|
|
},
|
|
)
|
|
})
|
|
.collect()
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::mock::{
|
|
message, message_payload, run_test, unrewarded_relayer, Event as TestEvent, Origin,
|
|
TestMessageDeliveryAndDispatchPayment, TestMessagesDeliveryProof, TestMessagesParameter, TestMessagesProof,
|
|
TestOnDeliveryConfirmed1, TestOnDeliveryConfirmed2, TestOnMessageAccepted, TestRuntime, TokenConversionRate,
|
|
PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_RELAYER_A, TEST_RELAYER_B,
|
|
};
|
|
use bp_messages::{UnrewardedRelayer, UnrewardedRelayersState};
|
|
use frame_support::{assert_noop, assert_ok, weights::Weight};
|
|
use frame_system::{EventRecord, Pallet as System, Phase};
|
|
use hex_literal::hex;
|
|
use sp_runtime::DispatchError;
|
|
|
|
fn get_ready_for_events() {
|
|
System::<TestRuntime>::set_block_number(1);
|
|
System::<TestRuntime>::reset_events();
|
|
}
|
|
|
|
fn send_regular_message() -> Weight {
|
|
get_ready_for_events();
|
|
|
|
let message_nonce = outbound_lane::<TestRuntime, ()>(TEST_LANE_ID)
|
|
.data()
|
|
.latest_generated_nonce
|
|
+ 1;
|
|
let weight = Pallet::<TestRuntime>::send_message(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
REGULAR_PAYLOAD,
|
|
REGULAR_PAYLOAD.declared_weight,
|
|
)
|
|
.expect("send_message has failed")
|
|
.actual_weight
|
|
.expect("send_message always returns Some");
|
|
|
|
// check event with assigned nonce
|
|
assert_eq!(
|
|
System::<TestRuntime>::events(),
|
|
vec![EventRecord {
|
|
phase: Phase::Initialization,
|
|
event: TestEvent::Messages(Event::MessageAccepted(TEST_LANE_ID, message_nonce)),
|
|
topics: vec![],
|
|
}],
|
|
);
|
|
|
|
// check that fee has been withdrawn from submitter
|
|
assert!(TestMessageDeliveryAndDispatchPayment::is_fee_paid(
|
|
1,
|
|
REGULAR_PAYLOAD.declared_weight
|
|
));
|
|
|
|
weight
|
|
}
|
|
|
|
fn receive_messages_delivery_proof() {
|
|
System::<TestRuntime>::set_block_number(1);
|
|
System::<TestRuntime>::reset_events();
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 1,
|
|
relayers: vec![UnrewardedRelayer {
|
|
relayer: 0,
|
|
messages: DeliveredMessages::new(1, true),
|
|
}]
|
|
.into_iter()
|
|
.collect(),
|
|
},
|
|
))),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 1,
|
|
total_messages: 1,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
assert_eq!(
|
|
System::<TestRuntime>::events(),
|
|
vec![EventRecord {
|
|
phase: Phase::Initialization,
|
|
event: TestEvent::Messages(Event::MessagesDelivered(TEST_LANE_ID, DeliveredMessages::new(1, true),)),
|
|
topics: vec![],
|
|
}],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pallet_owner_may_change_owner() {
|
|
run_test(|| {
|
|
PalletOwner::<TestRuntime>::put(2);
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::set_owner(Origin::root(), Some(1)));
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::set_operating_mode(Origin::signed(2), OperatingMode::Halted),
|
|
DispatchError::BadOrigin,
|
|
);
|
|
assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
|
|
Origin::root(),
|
|
OperatingMode::Halted
|
|
));
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::set_owner(Origin::signed(1), None));
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::set_operating_mode(Origin::signed(1), OperatingMode::Normal),
|
|
DispatchError::BadOrigin,
|
|
);
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::set_operating_mode(Origin::signed(2), OperatingMode::Normal),
|
|
DispatchError::BadOrigin,
|
|
);
|
|
assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
|
|
Origin::root(),
|
|
OperatingMode::Normal
|
|
));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn pallet_may_be_halted_by_root() {
|
|
run_test(|| {
|
|
assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
|
|
Origin::root(),
|
|
OperatingMode::Halted
|
|
));
|
|
assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
|
|
Origin::root(),
|
|
OperatingMode::Normal
|
|
));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn pallet_may_be_halted_by_owner() {
|
|
run_test(|| {
|
|
PalletOwner::<TestRuntime>::put(2);
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
|
|
Origin::signed(2),
|
|
OperatingMode::Halted
|
|
));
|
|
assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
|
|
Origin::signed(2),
|
|
OperatingMode::Normal
|
|
));
|
|
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::set_operating_mode(Origin::signed(1), OperatingMode::Halted),
|
|
DispatchError::BadOrigin,
|
|
);
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::set_operating_mode(Origin::signed(1), OperatingMode::Normal),
|
|
DispatchError::BadOrigin,
|
|
);
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
|
|
Origin::signed(2),
|
|
OperatingMode::Halted
|
|
));
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::set_operating_mode(Origin::signed(1), OperatingMode::Normal),
|
|
DispatchError::BadOrigin,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn pallet_parameter_may_be_updated_by_root() {
|
|
run_test(|| {
|
|
get_ready_for_events();
|
|
|
|
let parameter = TestMessagesParameter::TokenConversionRate(10.into());
|
|
assert_ok!(Pallet::<TestRuntime>::update_pallet_parameter(
|
|
Origin::root(),
|
|
parameter.clone(),
|
|
));
|
|
|
|
assert_eq!(TokenConversionRate::get(), 10.into());
|
|
assert_eq!(
|
|
System::<TestRuntime>::events(),
|
|
vec![EventRecord {
|
|
phase: Phase::Initialization,
|
|
event: TestEvent::Messages(Event::ParameterUpdated(parameter)),
|
|
topics: vec![],
|
|
}],
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn pallet_parameter_may_be_updated_by_owner() {
|
|
run_test(|| {
|
|
PalletOwner::<TestRuntime>::put(2);
|
|
get_ready_for_events();
|
|
|
|
let parameter = TestMessagesParameter::TokenConversionRate(10.into());
|
|
assert_ok!(Pallet::<TestRuntime>::update_pallet_parameter(
|
|
Origin::signed(2),
|
|
parameter.clone(),
|
|
));
|
|
|
|
assert_eq!(TokenConversionRate::get(), 10.into());
|
|
assert_eq!(
|
|
System::<TestRuntime>::events(),
|
|
vec![EventRecord {
|
|
phase: Phase::Initialization,
|
|
event: TestEvent::Messages(Event::ParameterUpdated(parameter)),
|
|
topics: vec![],
|
|
}],
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn pallet_parameter_cant_be_updated_by_arbitrary_submitter() {
|
|
run_test(|| {
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::update_pallet_parameter(
|
|
Origin::signed(2),
|
|
TestMessagesParameter::TokenConversionRate(10.into()),
|
|
),
|
|
DispatchError::BadOrigin,
|
|
);
|
|
|
|
PalletOwner::<TestRuntime>::put(2);
|
|
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::update_pallet_parameter(
|
|
Origin::signed(1),
|
|
TestMessagesParameter::TokenConversionRate(10.into()),
|
|
),
|
|
DispatchError::BadOrigin,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn fixed_u128_works_as_i_think() {
|
|
// this test is here just to be sure that conversion rate may be represented with FixedU128
|
|
run_test(|| {
|
|
use sp_runtime::{FixedPointNumber, FixedU128};
|
|
|
|
// 1:1 conversion that we use by default for testnets
|
|
let rialto_token = 1u64;
|
|
let rialto_token_in_millau_tokens = TokenConversionRate::get().saturating_mul_int(rialto_token);
|
|
assert_eq!(rialto_token_in_millau_tokens, 1);
|
|
|
|
// let's say conversion rate is 1:1.7
|
|
let conversion_rate = FixedU128::saturating_from_rational(170, 100);
|
|
let rialto_tokens = 100u64;
|
|
let rialto_tokens_in_millau_tokens = conversion_rate.saturating_mul_int(rialto_tokens);
|
|
assert_eq!(rialto_tokens_in_millau_tokens, 170);
|
|
|
|
// let's say conversion rate is 1:0.25
|
|
let conversion_rate = FixedU128::saturating_from_rational(25, 100);
|
|
let rialto_tokens = 100u64;
|
|
let rialto_tokens_in_millau_tokens = conversion_rate.saturating_mul_int(rialto_tokens);
|
|
assert_eq!(rialto_tokens_in_millau_tokens, 25);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn pallet_rejects_transactions_if_halted() {
|
|
run_test(|| {
|
|
// send message first to be able to check that delivery_proof fails later
|
|
send_regular_message();
|
|
|
|
PalletOperatingMode::<TestRuntime, ()>::put(OperatingMode::Halted);
|
|
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::send_message(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
REGULAR_PAYLOAD,
|
|
REGULAR_PAYLOAD.declared_weight,
|
|
),
|
|
Error::<TestRuntime, ()>::Halted,
|
|
);
|
|
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 1, 1,),
|
|
Error::<TestRuntime, ()>::Halted,
|
|
);
|
|
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
Ok(vec![message(2, REGULAR_PAYLOAD)]).into(),
|
|
1,
|
|
REGULAR_PAYLOAD.declared_weight,
|
|
),
|
|
Error::<TestRuntime, ()>::Halted,
|
|
);
|
|
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 1,
|
|
relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into_iter().collect(),
|
|
},
|
|
))),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 1,
|
|
messages_in_oldest_entry: 1,
|
|
total_messages: 1,
|
|
},
|
|
),
|
|
Error::<TestRuntime, ()>::Halted,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() {
|
|
run_test(|| {
|
|
// send message first to be able to check that delivery_proof fails later
|
|
send_regular_message();
|
|
|
|
PalletOperatingMode::<TestRuntime, ()>::put(OperatingMode::RejectingOutboundMessages);
|
|
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::send_message(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
REGULAR_PAYLOAD,
|
|
REGULAR_PAYLOAD.declared_weight,
|
|
),
|
|
Error::<TestRuntime, ()>::Halted,
|
|
);
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::increase_message_fee(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
1,
|
|
1,
|
|
));
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
Ok(vec![message(1, REGULAR_PAYLOAD)]).into(),
|
|
1,
|
|
REGULAR_PAYLOAD.declared_weight,
|
|
),);
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 1,
|
|
relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into_iter().collect(),
|
|
},
|
|
))),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 1,
|
|
messages_in_oldest_entry: 1,
|
|
total_messages: 1,
|
|
},
|
|
));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn send_message_works() {
|
|
run_test(|| {
|
|
send_regular_message();
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn chain_verifier_rejects_invalid_message_in_send_message() {
|
|
run_test(|| {
|
|
// messages with this payload are rejected by target chain verifier
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::send_message(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
PAYLOAD_REJECTED_BY_TARGET_CHAIN,
|
|
PAYLOAD_REJECTED_BY_TARGET_CHAIN.declared_weight
|
|
),
|
|
Error::<TestRuntime, ()>::MessageRejectedByChainVerifier,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn lane_verifier_rejects_invalid_message_in_send_message() {
|
|
run_test(|| {
|
|
// messages with zero fee are rejected by lane verifier
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::send_message(Origin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD, 0),
|
|
Error::<TestRuntime, ()>::MessageRejectedByLaneVerifier,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn message_send_fails_if_submitter_cant_pay_message_fee() {
|
|
run_test(|| {
|
|
TestMessageDeliveryAndDispatchPayment::reject_payments();
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::send_message(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
REGULAR_PAYLOAD,
|
|
REGULAR_PAYLOAD.declared_weight
|
|
),
|
|
Error::<TestRuntime, ()>::FailedToWithdrawMessageFee,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_proof_works() {
|
|
run_test(|| {
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
Ok(vec![message(1, REGULAR_PAYLOAD)]).into(),
|
|
1,
|
|
REGULAR_PAYLOAD.declared_weight,
|
|
));
|
|
|
|
assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).last_delivered_nonce(), 1);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_proof_updates_confirmed_message_nonce() {
|
|
run_test(|| {
|
|
// say we have received 10 messages && last confirmed message is 8
|
|
InboundLanes::<TestRuntime, ()>::insert(
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 8,
|
|
relayers: vec![
|
|
unrewarded_relayer(9, 9, TEST_RELAYER_A),
|
|
unrewarded_relayer(10, 10, TEST_RELAYER_B),
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
},
|
|
);
|
|
assert_eq!(
|
|
Pallet::<TestRuntime>::inbound_unrewarded_relayers_state(TEST_LANE_ID),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 2,
|
|
messages_in_oldest_entry: 1,
|
|
total_messages: 2,
|
|
},
|
|
);
|
|
|
|
// message proof includes outbound lane state with latest confirmed message updated to 9
|
|
let mut message_proof: TestMessagesProof = Ok(vec![message(11, REGULAR_PAYLOAD)]).into();
|
|
message_proof.result.as_mut().unwrap()[0].1.lane_state = Some(OutboundLaneData {
|
|
latest_received_nonce: 9,
|
|
..Default::default()
|
|
});
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
message_proof,
|
|
1,
|
|
REGULAR_PAYLOAD.declared_weight,
|
|
));
|
|
|
|
assert_eq!(
|
|
InboundLanes::<TestRuntime>::get(TEST_LANE_ID),
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 9,
|
|
relayers: vec![
|
|
unrewarded_relayer(10, 10, TEST_RELAYER_B),
|
|
unrewarded_relayer(11, 11, TEST_RELAYER_A)
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
},
|
|
);
|
|
assert_eq!(
|
|
Pallet::<TestRuntime>::inbound_unrewarded_relayers_state(TEST_LANE_ID),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 2,
|
|
messages_in_oldest_entry: 1,
|
|
total_messages: 2,
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enough() {
|
|
run_test(|| {
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
Ok(vec![message(1, REGULAR_PAYLOAD)]).into(),
|
|
1,
|
|
REGULAR_PAYLOAD.declared_weight - 1,
|
|
));
|
|
assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).last_delivered_nonce(), 0);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_proof_rejects_invalid_proof() {
|
|
run_test(|| {
|
|
assert_noop!(
|
|
Pallet::<TestRuntime, ()>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
Err(()).into(),
|
|
1,
|
|
0,
|
|
),
|
|
Error::<TestRuntime, ()>::InvalidMessagesProof,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_proof_rejects_proof_with_too_many_messages() {
|
|
run_test(|| {
|
|
assert_noop!(
|
|
Pallet::<TestRuntime, ()>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
Ok(vec![message(1, REGULAR_PAYLOAD)]).into(),
|
|
u32::MAX,
|
|
0,
|
|
),
|
|
Error::<TestRuntime, ()>::TooManyMessagesInTheProof,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_delivery_proof_works() {
|
|
run_test(|| {
|
|
send_regular_message();
|
|
receive_messages_delivery_proof();
|
|
|
|
assert_eq!(
|
|
OutboundLanes::<TestRuntime, ()>::get(&TEST_LANE_ID).latest_received_nonce,
|
|
1,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_delivery_proof_rewards_relayers() {
|
|
run_test(|| {
|
|
assert_ok!(Pallet::<TestRuntime>::send_message(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
REGULAR_PAYLOAD,
|
|
1000,
|
|
));
|
|
assert_ok!(Pallet::<TestRuntime>::send_message(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
REGULAR_PAYLOAD,
|
|
2000,
|
|
));
|
|
|
|
// this reports delivery of message 1 => reward is paid to TEST_RELAYER_A
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into_iter().collect(),
|
|
..Default::default()
|
|
}
|
|
))),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 1,
|
|
total_messages: 1,
|
|
..Default::default()
|
|
},
|
|
));
|
|
assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(
|
|
TEST_RELAYER_A,
|
|
1000
|
|
));
|
|
assert!(!TestMessageDeliveryAndDispatchPayment::is_reward_paid(
|
|
TEST_RELAYER_B,
|
|
2000
|
|
));
|
|
|
|
// this reports delivery of both message 1 and message 2 => reward is paid only to TEST_RELAYER_B
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
relayers: vec![
|
|
unrewarded_relayer(1, 1, TEST_RELAYER_A),
|
|
unrewarded_relayer(2, 2, TEST_RELAYER_B)
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
..Default::default()
|
|
}
|
|
))),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 2,
|
|
total_messages: 2,
|
|
..Default::default()
|
|
},
|
|
));
|
|
assert!(!TestMessageDeliveryAndDispatchPayment::is_reward_paid(
|
|
TEST_RELAYER_A,
|
|
1000
|
|
));
|
|
assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(
|
|
TEST_RELAYER_B,
|
|
2000
|
|
));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_delivery_proof_rejects_invalid_proof() {
|
|
run_test(|| {
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Err(())),
|
|
Default::default(),
|
|
),
|
|
Error::<TestRuntime, ()>::InvalidMessagesDeliveryProof,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_invalid() {
|
|
run_test(|| {
|
|
// when number of relayers entries is invalid
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
relayers: vec![
|
|
unrewarded_relayer(1, 1, TEST_RELAYER_A),
|
|
unrewarded_relayer(2, 2, TEST_RELAYER_B)
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
..Default::default()
|
|
}
|
|
))),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 1,
|
|
total_messages: 2,
|
|
..Default::default()
|
|
},
|
|
),
|
|
Error::<TestRuntime, ()>::InvalidUnrewardedRelayersState,
|
|
);
|
|
|
|
// when number of messages is invalid
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
relayers: vec![
|
|
unrewarded_relayer(1, 1, TEST_RELAYER_A),
|
|
unrewarded_relayer(2, 2, TEST_RELAYER_B)
|
|
]
|
|
.into_iter()
|
|
.collect(),
|
|
..Default::default()
|
|
}
|
|
))),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 2,
|
|
total_messages: 1,
|
|
..Default::default()
|
|
},
|
|
),
|
|
Error::<TestRuntime, ()>::InvalidUnrewardedRelayersState,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_accepts_single_message_with_invalid_payload() {
|
|
run_test(|| {
|
|
let mut invalid_message = message(1, REGULAR_PAYLOAD);
|
|
invalid_message.data.payload = Vec::new();
|
|
|
|
assert_ok!(Pallet::<TestRuntime, ()>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
Ok(vec![invalid_message]).into(),
|
|
1,
|
|
0, // weight may be zero in this case (all messages are improperly encoded)
|
|
),);
|
|
|
|
assert_eq!(
|
|
InboundLanes::<TestRuntime>::get(&TEST_LANE_ID).last_delivered_nonce(),
|
|
1,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_accepts_batch_with_message_with_invalid_payload() {
|
|
run_test(|| {
|
|
let mut invalid_message = message(2, REGULAR_PAYLOAD);
|
|
invalid_message.data.payload = Vec::new();
|
|
|
|
assert_ok!(Pallet::<TestRuntime, ()>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
Ok(vec![
|
|
message(1, REGULAR_PAYLOAD),
|
|
invalid_message,
|
|
message(3, REGULAR_PAYLOAD),
|
|
])
|
|
.into(),
|
|
3,
|
|
REGULAR_PAYLOAD.declared_weight + REGULAR_PAYLOAD.declared_weight,
|
|
),);
|
|
|
|
assert_eq!(
|
|
InboundLanes::<TestRuntime>::get(&TEST_LANE_ID).last_delivered_nonce(),
|
|
3,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn storage_message_key_computed_properly() {
|
|
// If this test fails, then something has been changed in module storage that is breaking all
|
|
// previously crafted messages proofs.
|
|
let storage_key = storage_keys::message_key("BridgeMessages", &*b"test", 42).0;
|
|
assert_eq!(
|
|
storage_key,
|
|
hex!("dd16c784ebd3390a9bc0357c7511ed018a395e6242c6813b196ca31ed0547ea79446af0e09063bd4a7874aef8a997cec746573742a00000000000000").to_vec(),
|
|
"Unexpected storage key: {}",
|
|
hex::encode(&storage_key),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn outbound_lane_data_key_computed_properly() {
|
|
// If this test fails, then something has been changed in module storage that is breaking all
|
|
// previously crafted outbound lane state proofs.
|
|
let storage_key = storage_keys::outbound_lane_data_key("BridgeMessages", &*b"test").0;
|
|
assert_eq!(
|
|
storage_key,
|
|
hex!("dd16c784ebd3390a9bc0357c7511ed0196c246acb9b55077390e3ca723a0ca1f44a8995dd50b6657a037a7839304535b74657374").to_vec(),
|
|
"Unexpected storage key: {}",
|
|
hex::encode(&storage_key),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn inbound_lane_data_key_computed_properly() {
|
|
// If this test fails, then something has been changed in module storage that is breaking all
|
|
// previously crafted inbound lane state proofs.
|
|
let storage_key = storage_keys::inbound_lane_data_key("BridgeMessages", &*b"test").0;
|
|
assert_eq!(
|
|
storage_key,
|
|
hex!("dd16c784ebd3390a9bc0357c7511ed01e5f83cf83f2127eb47afdc35d6e43fab44a8995dd50b6657a037a7839304535b74657374").to_vec(),
|
|
"Unexpected storage key: {}",
|
|
hex::encode(&storage_key),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn actual_dispatch_weight_does_not_overlow() {
|
|
run_test(|| {
|
|
let message1 = message(1, message_payload(0, Weight::MAX / 2));
|
|
let message2 = message(2, message_payload(0, Weight::MAX / 2));
|
|
let message3 = message(3, message_payload(0, Weight::MAX / 2));
|
|
|
|
assert_ok!(Pallet::<TestRuntime, ()>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
// this may cause overflow if source chain storage is invalid
|
|
Ok(vec![message1, message2, message3]).into(),
|
|
3,
|
|
Weight::MAX,
|
|
));
|
|
assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).last_delivered_nonce(), 2);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn increase_message_fee_fails_if_message_is_already_delivered() {
|
|
run_test(|| {
|
|
send_regular_message();
|
|
receive_messages_delivery_proof();
|
|
|
|
assert_noop!(
|
|
Pallet::<TestRuntime, ()>::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 1, 100,),
|
|
Error::<TestRuntime, ()>::MessageIsAlreadyDelivered,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn increase_message_fee_fails_if_message_is_not_yet_sent() {
|
|
run_test(|| {
|
|
assert_noop!(
|
|
Pallet::<TestRuntime, ()>::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 1, 100,),
|
|
Error::<TestRuntime, ()>::MessageIsNotYetSent,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn increase_message_fee_fails_if_submitter_cant_pay_additional_fee() {
|
|
run_test(|| {
|
|
send_regular_message();
|
|
|
|
TestMessageDeliveryAndDispatchPayment::reject_payments();
|
|
|
|
assert_noop!(
|
|
Pallet::<TestRuntime, ()>::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 1, 100,),
|
|
Error::<TestRuntime, ()>::FailedToWithdrawMessageFee,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn increase_message_fee_succeeds() {
|
|
run_test(|| {
|
|
send_regular_message();
|
|
|
|
assert_ok!(Pallet::<TestRuntime, ()>::increase_message_fee(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
1,
|
|
100,
|
|
),);
|
|
assert!(TestMessageDeliveryAndDispatchPayment::is_fee_paid(1, 100));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn weight_refund_from_receive_messages_proof_works() {
|
|
run_test(|| {
|
|
fn submit_with_unspent_weight(
|
|
nonce: MessageNonce,
|
|
unspent_weight: Weight,
|
|
is_prepaid: bool,
|
|
) -> (Weight, Weight) {
|
|
let mut payload = REGULAR_PAYLOAD;
|
|
payload.dispatch_result.unspent_weight = unspent_weight;
|
|
payload.dispatch_result.dispatch_fee_paid_during_dispatch = !is_prepaid;
|
|
let proof = Ok(vec![message(nonce, payload)]).into();
|
|
let messages_count = 1;
|
|
let pre_dispatch_weight = <TestRuntime as Config>::WeightInfo::receive_messages_proof_weight(
|
|
&proof,
|
|
messages_count,
|
|
REGULAR_PAYLOAD.declared_weight,
|
|
);
|
|
let post_dispatch_weight = Pallet::<TestRuntime>::receive_messages_proof(
|
|
Origin::signed(1),
|
|
TEST_RELAYER_A,
|
|
proof,
|
|
messages_count,
|
|
REGULAR_PAYLOAD.declared_weight,
|
|
)
|
|
.expect("delivery has failed")
|
|
.actual_weight
|
|
.expect("receive_messages_proof always returns Some");
|
|
|
|
(pre_dispatch_weight, post_dispatch_weight)
|
|
}
|
|
|
|
// when dispatch is returning `unspent_weight < declared_weight`
|
|
let (pre, post) = submit_with_unspent_weight(1, 1, false);
|
|
assert_eq!(post, pre - 1);
|
|
|
|
// when dispatch is returning `unspent_weight = declared_weight`
|
|
let (pre, post) = submit_with_unspent_weight(2, REGULAR_PAYLOAD.declared_weight, false);
|
|
assert_eq!(post, pre - REGULAR_PAYLOAD.declared_weight);
|
|
|
|
// when dispatch is returning `unspent_weight > declared_weight`
|
|
let (pre, post) = submit_with_unspent_weight(3, REGULAR_PAYLOAD.declared_weight + 1, false);
|
|
assert_eq!(post, pre - REGULAR_PAYLOAD.declared_weight);
|
|
|
|
// when there's no unspent weight
|
|
let (pre, post) = submit_with_unspent_weight(4, 0, false);
|
|
assert_eq!(post, pre);
|
|
|
|
// when dispatch is returning `unspent_weight < declared_weight` AND message is prepaid
|
|
let (pre, post) = submit_with_unspent_weight(5, 1, true);
|
|
assert_eq!(
|
|
post,
|
|
pre - 1 - <TestRuntime as Config>::WeightInfo::pay_inbound_dispatch_fee_overhead()
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn messages_delivered_callbacks_are_called() {
|
|
run_test(|| {
|
|
send_regular_message();
|
|
send_regular_message();
|
|
send_regular_message();
|
|
|
|
// messages 1+2 are confirmed in 1 tx, message 3 in a separate tx
|
|
// dispatch of message 2 has failed
|
|
let mut delivered_messages_1_and_2 = DeliveredMessages::new(1, true);
|
|
delivered_messages_1_and_2.note_dispatched_message(false);
|
|
let messages_1_and_2_proof = Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 0,
|
|
relayers: vec![UnrewardedRelayer {
|
|
relayer: 0,
|
|
messages: delivered_messages_1_and_2.clone(),
|
|
}]
|
|
.into_iter()
|
|
.collect(),
|
|
},
|
|
));
|
|
let delivered_message_3 = DeliveredMessages::new(3, true);
|
|
let messages_3_proof = Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 0,
|
|
relayers: vec![UnrewardedRelayer {
|
|
relayer: 0,
|
|
messages: delivered_message_3.clone(),
|
|
}]
|
|
.into_iter()
|
|
.collect(),
|
|
},
|
|
));
|
|
|
|
// first tx with messages 1+2
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(messages_1_and_2_proof),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 1,
|
|
total_messages: 2,
|
|
..Default::default()
|
|
},
|
|
));
|
|
// second tx with message 3
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(messages_3_proof),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 1,
|
|
total_messages: 1,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
// ensure that both callbacks have been called twice: for 1+2, then for 3
|
|
TestOnDeliveryConfirmed1::ensure_called(&TEST_LANE_ID, &delivered_messages_1_and_2);
|
|
TestOnDeliveryConfirmed1::ensure_called(&TEST_LANE_ID, &delivered_message_3);
|
|
TestOnDeliveryConfirmed2::ensure_called(&TEST_LANE_ID, &delivered_messages_1_and_2);
|
|
TestOnDeliveryConfirmed2::ensure_called(&TEST_LANE_ID, &delivered_message_3);
|
|
});
|
|
}
|
|
|
|
fn confirm_3_messages_delivery() -> (Weight, Weight) {
|
|
send_regular_message();
|
|
send_regular_message();
|
|
send_regular_message();
|
|
|
|
let proof = TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 0,
|
|
relayers: vec![unrewarded_relayer(1, 3, TEST_RELAYER_A)].into_iter().collect(),
|
|
},
|
|
)));
|
|
let relayers_state = UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 1,
|
|
total_messages: 3,
|
|
..Default::default()
|
|
};
|
|
let pre_dispatch_weight = <TestRuntime as Config>::WeightInfo::receive_messages_delivery_proof_weight(
|
|
&proof,
|
|
&relayers_state,
|
|
crate::mock::DbWeight::get(),
|
|
);
|
|
let post_dispatch_weight =
|
|
Pallet::<TestRuntime>::receive_messages_delivery_proof(Origin::signed(1), proof, relayers_state)
|
|
.expect("confirmation has failed")
|
|
.actual_weight
|
|
.expect("receive_messages_delivery_proof always returns Some");
|
|
(pre_dispatch_weight, post_dispatch_weight)
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_delivery_proof_refunds_zero_weight() {
|
|
run_test(|| {
|
|
let (pre_dispatch_weight, post_dispatch_weight) = confirm_3_messages_delivery();
|
|
assert_eq!(pre_dispatch_weight, post_dispatch_weight);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_delivery_proof_refunds_non_zero_weight() {
|
|
run_test(|| {
|
|
TestOnDeliveryConfirmed1::set_consumed_weight_per_message(crate::mock::DbWeight::get().writes(1));
|
|
|
|
let (pre_dispatch_weight, post_dispatch_weight) = confirm_3_messages_delivery();
|
|
assert_eq!(
|
|
pre_dispatch_weight.saturating_sub(post_dispatch_weight),
|
|
crate::mock::DbWeight::get().reads(1) * 3
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn receive_messages_panics_in_debug_mode_if_callback_is_wrong() {
|
|
run_test(|| {
|
|
TestOnDeliveryConfirmed1::set_consumed_weight_per_message(crate::mock::DbWeight::get().reads_writes(2, 2));
|
|
confirm_3_messages_delivery()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn receive_messages_delivery_proof_rejects_proof_if_trying_to_confirm_more_messages_than_expected() {
|
|
run_test(|| {
|
|
// send message first to be able to check that delivery_proof fails later
|
|
send_regular_message();
|
|
|
|
// 1) InboundLaneData declares that the `last_confirmed_nonce` is 1;
|
|
// 2) InboundLaneData has no entries => `InboundLaneData::last_delivered_nonce()`
|
|
// returns `last_confirmed_nonce`;
|
|
// 3) it means that we're going to confirm delivery of messages 1..=1;
|
|
// 4) so the number of declared messages (see `UnrewardedRelayersState`) is `0` and
|
|
// numer of actually confirmed messages is `1`.
|
|
assert_noop!(
|
|
Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 1,
|
|
relayers: Default::default(),
|
|
},
|
|
))),
|
|
UnrewardedRelayersState::default(),
|
|
),
|
|
Error::<TestRuntime, ()>::TryingToConfirmMoreMessagesThanExpected,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn increase_message_fee_weight_depends_on_message_size() {
|
|
run_test(|| {
|
|
let mut small_payload = message_payload(0, 100);
|
|
let mut large_payload = message_payload(1, 100);
|
|
small_payload.extra = vec![1; 100];
|
|
large_payload.extra = vec![2; 16_384];
|
|
|
|
assert_ok!(Pallet::<TestRuntime>::send_message(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
small_payload,
|
|
100,
|
|
));
|
|
assert_ok!(Pallet::<TestRuntime>::send_message(
|
|
Origin::signed(1),
|
|
TEST_LANE_ID,
|
|
large_payload,
|
|
100,
|
|
));
|
|
|
|
let small_weight = Pallet::<TestRuntime>::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 1, 1)
|
|
.expect("increase_message_fee has failed")
|
|
.actual_weight
|
|
.expect("increase_message_fee always returns Some");
|
|
|
|
let large_weight = Pallet::<TestRuntime>::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 2, 1)
|
|
.expect("increase_message_fee has failed")
|
|
.actual_weight
|
|
.expect("increase_message_fee always returns Some");
|
|
|
|
assert!(
|
|
large_weight > small_weight,
|
|
"Actual post-dispatch weigth for larger message {} must be larger than {} for small message",
|
|
large_weight,
|
|
small_weight,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn weight_is_refunded_for_messages_that_are_not_pruned() {
|
|
run_test(|| {
|
|
// send first MAX messages - no messages are pruned
|
|
let max_messages_to_prune = crate::mock::MaxMessagesToPruneAtOnce::get();
|
|
let when_zero_messages_are_pruned = send_regular_message();
|
|
let mut delivered_messages = DeliveredMessages::new(1, true);
|
|
for _ in 1..max_messages_to_prune {
|
|
assert_eq!(send_regular_message(), when_zero_messages_are_pruned);
|
|
delivered_messages.note_dispatched_message(true);
|
|
}
|
|
|
|
// confirm delivery of all sent messages
|
|
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
|
|
Origin::signed(1),
|
|
TestMessagesDeliveryProof(Ok((
|
|
TEST_LANE_ID,
|
|
InboundLaneData {
|
|
last_confirmed_nonce: 1,
|
|
relayers: vec![UnrewardedRelayer {
|
|
relayer: 0,
|
|
messages: delivered_messages,
|
|
}]
|
|
.into_iter()
|
|
.collect(),
|
|
},
|
|
))),
|
|
UnrewardedRelayersState {
|
|
unrewarded_relayer_entries: 1,
|
|
total_messages: max_messages_to_prune,
|
|
..Default::default()
|
|
},
|
|
));
|
|
|
|
// when next message is sent, MAX messages are pruned
|
|
let weight_when_max_messages_are_pruned = send_regular_message();
|
|
assert_eq!(
|
|
weight_when_max_messages_are_pruned,
|
|
when_zero_messages_are_pruned + crate::mock::DbWeight::get().writes(max_messages_to_prune),
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn message_accepted_callbacks_are_called() {
|
|
run_test(|| {
|
|
send_regular_message();
|
|
TestOnMessageAccepted::ensure_called(&TEST_LANE_ID, &1);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn message_accepted_panics_in_debug_mode_if_callback_is_wrong() {
|
|
run_test(|| {
|
|
TestOnMessageAccepted::set_consumed_weight_per_message(crate::mock::DbWeight::get().reads_writes(2, 2));
|
|
send_regular_message();
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn message_accepted_refunds_non_zero_weight() {
|
|
run_test(|| {
|
|
TestOnMessageAccepted::set_consumed_weight_per_message(crate::mock::DbWeight::get().writes(1));
|
|
let actual_callback_weight = send_regular_message();
|
|
let pre_dispatch_weight = <TestRuntime as Config>::WeightInfo::send_message_weight(
|
|
®ULAR_PAYLOAD,
|
|
crate::mock::DbWeight::get(),
|
|
);
|
|
let prune_weight =
|
|
crate::mock::DbWeight::get().writes(<TestRuntime as Config>::MaxMessagesToPruneAtOnce::get());
|
|
|
|
assert_eq!(
|
|
pre_dispatch_weight.saturating_sub(actual_callback_weight),
|
|
crate::mock::DbWeight::get().reads(1).saturating_add(prune_weight)
|
|
);
|
|
});
|
|
}
|
|
}
|