// 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 .
//! Transaction extension that refunds relayer if he has delivered some new messages.
//! It also refunds transaction cost if the transaction is an `utility.batchAll()`
//! with calls that are: delivering new messsage and all necessary underlying headers
//! (parachain or relay chain).
use crate::{
messages_call_ext::{
CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType,
},
RefundableParachainId,
};
use bp_messages::{LaneId, MessageNonce};
use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams};
use bp_runtime::{RangeInclusiveExt, StaticStrProvider};
use codec::{Codec, Decode, Encode};
use frame_support::{
dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo},
traits::IsSubType,
weights::Weight,
CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
use pallet_bridge_grandpa::{
CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, SubmitFinalityProofInfo,
};
use pallet_bridge_messages::Config as MessagesConfig;
use pallet_bridge_parachains::{
BoundedBridgeGrandpaConfig, CallSubType as ParachainsCallSubType, Config as ParachainsConfig,
RelayBlockNumber, SubmitParachainHeadsHelper, SubmitParachainHeadsInfo,
};
use pallet_bridge_relayers::{
Config as RelayersConfig, Pallet as RelayersPallet, WeightInfoExt as _,
};
use pallet_transaction_payment::{Config as TransactionPaymentConfig, OnChargeTransaction};
use pallet_utility::{Call as UtilityCall, Config as UtilityConfig, Pallet as UtilityPallet};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{
AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Get, PostDispatchInfoOf,
TransactionExtension, TransactionExtensionBase, ValidateResult, Zero,
},
transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionValidityError, ValidTransactionBuilder,
},
DispatchResult, FixedPointOperand, RuntimeDebug,
};
use sp_std::{marker::PhantomData, vec, vec::Vec};
type AccountIdOf = ::AccountId;
// without this typedef rustfmt fails with internal err
type BalanceOf =
<::OnChargeTransaction as OnChargeTransaction>::Balance;
type CallOf = ::RuntimeCall;
/// Trait identifying a bridged messages lane. A relayer might be refunded for delivering messages
/// coming from this lane.
pub trait RefundableMessagesLaneId {
/// The instance of the bridge messages pallet.
type Instance: 'static;
/// The messages lane id.
type Id: Get;
}
/// Default implementation of `RefundableMessagesLaneId`.
pub struct RefundableMessagesLane(PhantomData<(Instance, Id)>);
impl RefundableMessagesLaneId for RefundableMessagesLane
where
Instance: 'static,
Id: Get,
{
type Instance = Instance;
type Id = Id;
}
/// Refund calculator.
pub trait RefundCalculator {
/// The underlying integer type in which the refund is calculated.
type Balance;
/// Compute refund for given transaction.
fn compute_refund(
info: &DispatchInfo,
post_info: &PostDispatchInfo,
len: usize,
tip: Self::Balance,
) -> Self::Balance;
}
/// `RefundCalculator` implementation which refunds the actual transaction fee.
pub struct ActualFeeRefund(PhantomData);
impl RefundCalculator for ActualFeeRefund
where
R: TransactionPaymentConfig,
CallOf: Dispatchable,
BalanceOf: FixedPointOperand,
{
type Balance = BalanceOf;
fn compute_refund(
info: &DispatchInfo,
post_info: &PostDispatchInfo,
len: usize,
tip: BalanceOf,
) -> BalanceOf {
pallet_transaction_payment::Pallet::::compute_actual_fee(len as _, info, post_info, tip)
}
}
/// Data that is crafted in `pre_dispatch` method and used at `post_dispatch`.
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct PreDispatchData {
/// Transaction submitter (relayer) account.
relayer: AccountId,
/// Type of the call.
call_info: CallInfo,
}
/// Type of the call that the extension recognizes.
#[derive(RuntimeDebugNoBound, PartialEq)]
pub enum CallInfo {
/// Relay chain finality + parachain finality + message delivery/confirmation calls.
AllFinalityAndMsgs(
SubmitFinalityProofInfo,
SubmitParachainHeadsInfo,
MessagesCallInfo,
),
/// Relay chain finality + message delivery/confirmation calls.
RelayFinalityAndMsgs(SubmitFinalityProofInfo, MessagesCallInfo),
/// Parachain finality + message delivery/confirmation calls.
///
/// This variant is used only when bridging with parachain.
ParachainFinalityAndMsgs(SubmitParachainHeadsInfo, MessagesCallInfo),
/// Standalone message delivery/confirmation call.
Msgs(MessagesCallInfo),
}
impl CallInfo {
/// Returns true if call is a message delivery call (with optional finality calls).
fn is_receive_messages_proof_call(&self) -> bool {
match self.messages_call_info() {
MessagesCallInfo::ReceiveMessagesProof(_) => true,
MessagesCallInfo::ReceiveMessagesDeliveryProof(_) => false,
}
}
/// Returns the pre-dispatch `finality_target` sent to the `SubmitFinalityProof` call.
fn submit_finality_proof_info(&self) -> Option> {
match *self {
Self::AllFinalityAndMsgs(info, _, _) => Some(info),
Self::RelayFinalityAndMsgs(info, _) => Some(info),
_ => None,
}
}
/// Returns mutable reference to pre-dispatch `finality_target` sent to the
/// `SubmitFinalityProof` call.
#[cfg(test)]
fn submit_finality_proof_info_mut(
&mut self,
) -> Option<&mut SubmitFinalityProofInfo> {
match *self {
Self::AllFinalityAndMsgs(ref mut info, _, _) => Some(info),
Self::RelayFinalityAndMsgs(ref mut info, _) => Some(info),
_ => None,
}
}
/// Returns the pre-dispatch `SubmitParachainHeadsInfo`.
fn submit_parachain_heads_info(&self) -> Option<&SubmitParachainHeadsInfo> {
match self {
Self::AllFinalityAndMsgs(_, info, _) => Some(info),
Self::ParachainFinalityAndMsgs(info, _) => Some(info),
_ => None,
}
}
/// Returns the pre-dispatch `ReceiveMessagesProofInfo`.
fn messages_call_info(&self) -> &MessagesCallInfo {
match self {
Self::AllFinalityAndMsgs(_, _, info) => info,
Self::RelayFinalityAndMsgs(_, info) => info,
Self::ParachainFinalityAndMsgs(_, info) => info,
Self::Msgs(info) => info,
}
}
}
/// The actions on relayer account that need to be performed because of his actions.
#[derive(RuntimeDebug, PartialEq)]
pub enum RelayerAccountAction {
/// Do nothing with relayer account.
None,
/// Reward the relayer.
Reward(AccountId, RewardsAccountParams, Reward),
/// Slash the relayer.
Slash(AccountId, RewardsAccountParams),
}
/// Everything common among our refund transaction extensions.
pub trait RefundTransactionExtension:
'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo
{
/// This chain runtime.
type Runtime: MessagesConfig<::Instance>
+ RelayersConfig;
/// Messages pallet and lane reference.
type Msgs: RefundableMessagesLaneId;
/// Refund amount calculator.
type Refund: RefundCalculator::Reward>;
/// Priority boost calculator.
type Priority: Get;
/// Signed extension unique identifier.
type Id: StaticStrProvider;
/// Unpack batch runtime call.
fn expand_call(call: &CallOf) -> Vec<&CallOf>;
/// Given runtime call, check if it has supported format. Additionally, check if any of
/// (optionally batched) calls are obsolete and we shall reject the transaction.
fn parse_and_check_for_obsolete_call(
call: &CallOf,
) -> Result, TransactionValidityError>;
/// Check if parsed call is already obsolete.
fn check_obsolete_parsed_call(
call: &CallOf,
) -> Result<&CallOf, TransactionValidityError>;
/// Called from post-dispatch and shall perform additional checks (apart from messages
/// transaction success) of given call result.
fn additional_call_result_check(
relayer: &AccountIdOf,
call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool;
/// Given post-dispatch information, analyze the outcome of relayer call and return
/// actions that need to be performed on relayer account.
fn analyze_call_result(
pre: Option>>>,
info: &DispatchInfo,
post_info: &PostDispatchInfo,
len: usize,
result: &DispatchResult,
) -> RelayerAccountAction, ::Reward>
{
let mut extra_weight = Weight::zero();
let mut extra_size = 0;
// We don't refund anything for transactions that we don't support.
let (relayer, call_info) = match pre {
Some(Some(pre)) => (pre.relayer, pre.call_info),
_ => return RelayerAccountAction::None,
};
// now we know that the relayer either needs to be rewarded, or slashed
// => let's prepare the correspondent account that pays reward/receives slashed amount
let reward_account_params =
RewardsAccountParams::new(
::Id::get(),
::Instance,
>>::BridgedChainId::get(),
if call_info.is_receive_messages_proof_call() {
RewardsAccountOwner::ThisChain
} else {
RewardsAccountOwner::BridgedChain
},
);
// prepare return value for the case if the call has failed or it has not caused
// expected side effects (e.g. not all messages have been accepted)
//
// we are not checking if relayer is registered here - it happens during the slash attempt
//
// there are couple of edge cases here:
//
// - when the relayer becomes registered during message dispatch: this is unlikely + relayer
// should be ready for slashing after registration;
//
// - when relayer is registered after `validate` is called and priority is not boosted:
// relayer should be ready for slashing after registration.
let may_slash_relayer =
Self::bundled_messages_for_priority_boost(Some(&call_info)).is_some();
let slash_relayer_if_delivery_result = may_slash_relayer
.then(|| RelayerAccountAction::Slash(relayer.clone(), reward_account_params))
.unwrap_or(RelayerAccountAction::None);
// We don't refund anything if the transaction has failed.
if let Err(e) = result {
log::trace!(
target: "runtime::bridge",
"{} via {:?}: relayer {:?} has submitted invalid messages transaction: {:?}",
Self::Id::STR,
::Id::get(),
relayer,
e,
);
return slash_relayer_if_delivery_result
}
// Check if the `ReceiveMessagesProof` call delivered at least some of the messages that
// it contained. If this happens, we consider the transaction "helpful" and refund it.
let msgs_call_info = call_info.messages_call_info();
if !MessagesCallHelper::::Instance>::was_successful(msgs_call_info) {
log::trace!(
target: "runtime::bridge",
"{} via {:?}: relayer {:?} has submitted invalid messages call",
Self::Id::STR,
::Id::get(),
relayer,
);
return slash_relayer_if_delivery_result
}
// do additional checks
if !Self::additional_call_result_check(
&relayer,
&call_info,
&mut extra_weight,
&mut extra_size,
) {
return slash_relayer_if_delivery_result
}
// regarding the tip - refund that happens here (at this side of the bridge) isn't the whole
// relayer compensation. He'll receive some amount at the other side of the bridge. It shall
// (in theory) cover the tip there. Otherwise, if we'll be compensating tip here, some
// malicious relayer may use huge tips, effectively depleting account that pay rewards. The
// cost of this attack is nothing. Hence we use zero as tip here.
let tip = Zero::zero();
// decrease post-dispatch weight/size using extra weight/size that we know now
let post_info_len = len.saturating_sub(extra_size as usize);
let mut post_info_weight =
post_info.actual_weight.unwrap_or(info.weight).saturating_sub(extra_weight);
// let's also replace the weight of slashing relayer with the weight of rewarding relayer
if call_info.is_receive_messages_proof_call() {
post_info_weight = post_info_weight.saturating_sub(
::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(),
);
}
// compute the relayer refund
let mut post_info = *post_info;
post_info.actual_weight = Some(post_info_weight);
let refund = Self::Refund::compute_refund(info, &post_info, post_info_len, tip);
// we can finally reward relayer
RelayerAccountAction::Reward(relayer, reward_account_params, refund)
}
/// Returns number of bundled messages `Some(_)`, if the given call info is a:
///
/// - message delivery transaction;
///
/// - with reasonable bundled messages that may be accepted by the messages pallet.
///
/// This function is used to check whether the transaction priority should be
/// virtually boosted. The relayer registration (we only boost priority for registered
/// relayer transactions) must be checked outside.
fn bundled_messages_for_priority_boost(call_info: Option<&CallInfo>) -> Option {
// we only boost priority of message delivery transactions
let parsed_call = match call_info {
Some(parsed_call) if parsed_call.is_receive_messages_proof_call() => parsed_call,
_ => return None,
};
// compute total number of messages in transaction
let bundled_messages = parsed_call.messages_call_info().bundled_messages().saturating_len();
// a quick check to avoid invalid high-priority transactions
let max_unconfirmed_messages_in_confirmation_tx = ::Instance,
>>::MaxUnconfirmedMessagesAtInboundLane::get(
);
if bundled_messages > max_unconfirmed_messages_in_confirmation_tx {
return None
}
Some(bundled_messages)
}
}
/// Adapter that allow implementing `sp_runtime::traits::TransactionExtension` for any
/// `RefundTransactionExtension`.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
pub struct RefundTransactionExtensionAdapter(T);
impl TransactionExtensionBase
for RefundTransactionExtensionAdapter
where
CallOf: Dispatchable
+ MessagesCallSubType::Instance>,
{
const IDENTIFIER: &'static str = T::Id::STR;
type Implicit = ();
}
impl TransactionExtension, Context>
for RefundTransactionExtensionAdapter
where
CallOf: Dispatchable
+ MessagesCallSubType::Instance>,
as Dispatchable>::RuntimeOrigin:
AsSystemOriginSigner> + Clone,
{
type Pre = Option>>;
type Val = Option;
fn validate(
&self,
origin: as Dispatchable>::RuntimeOrigin,
call: &CallOf,
_info: &DispatchInfoOf>,
_len: usize,
_context: &mut Context,
_self_implicit: Self::Implicit,
_inherited_implication: &impl Encode,
) -> ValidateResult> {
let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
// this is the only relevant line of code for the `pre_dispatch`
//
// we're not calling `validate` from `pre_dispatch` directly because of performance
// reasons, so if you're adding some code that may fail here, please check if it needs
// to be added to the `pre_dispatch` as well
let parsed_call = T::parse_and_check_for_obsolete_call(call)?;
// the following code just plays with transaction priority and never returns an error
// we only boost priority of presumably correct message delivery transactions
let bundled_messages = match T::bundled_messages_for_priority_boost(parsed_call.as_ref()) {
Some(bundled_messages) => bundled_messages,
None => return Ok((Default::default(), parsed_call, origin)),
};
// we only boost priority if relayer has staked required balance
if !RelayersPallet::::is_registration_active(who) {
return Ok((Default::default(), parsed_call, origin))
}
// compute priority boost
let priority_boost = crate::extensions::priority_calculator::compute_priority_boost::<
T::Priority,
>(bundled_messages);
let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost);
log::trace!(
target: "runtime::bridge",
"{} via {:?} has boosted priority of message delivery transaction \
of relayer {:?}: {} messages -> {} priority",
Self::IDENTIFIER,
::Id::get(),
who,
bundled_messages,
priority_boost,
);
let validity = valid_transaction.build()?;
Ok((validity, parsed_call, origin))
}
fn prepare(
self,
val: Self::Val,
origin: & as Dispatchable>::RuntimeOrigin,
_call: &CallOf,
_info: &DispatchInfoOf>,
_len: usize,
_context: &Context,
) -> Result {
let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
Ok(val.map(|call_info| {
log::trace!(
target: "runtime::bridge",
"{} via {:?} parsed bridge transaction in pre-dispatch: {:?}",
Self::IDENTIFIER,
::Id::get(),
call_info,
);
PreDispatchData { relayer: who.clone(), call_info }
}))
}
fn post_dispatch(
pre: Self::Pre,
info: &DispatchInfoOf>,
post_info: &PostDispatchInfoOf>,
len: usize,
result: &DispatchResult,
_context: &Context,
) -> Result<(), TransactionValidityError> {
let call_result = T::analyze_call_result(Some(pre), info, post_info, len, result);
match call_result {
RelayerAccountAction::None => (),
RelayerAccountAction::Reward(relayer, reward_account, reward) => {
RelayersPallet::::register_relayer_reward(
reward_account,
&relayer,
reward,
);
log::trace!(
target: "runtime::bridge",
"{} via {:?} has registered reward: {:?} for {:?}",
Self::IDENTIFIER,
::Id::get(),
reward,
relayer,
);
},
RelayerAccountAction::Slash(relayer, slash_account) =>
RelayersPallet::::slash_and_deregister(
&relayer,
ExplicitOrAccountParams::Params(slash_account),
),
}
Ok(())
}
}
/// Transaction extension that refunds a relayer for new messages coming from a parachain.
///
/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
/// with message delivery transaction. Batch may deliver either both relay chain header and
/// parachain head, or just parachain head. Corresponding headers must be used in messages
/// proof verification.
///
/// Extension does not refund transaction tip due to security reasons.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
#[scale_info(skip_type_params(Runtime, Para, Msgs, Refund, Priority, Id))]
pub struct RefundBridgedParachainMessages(
PhantomData<(
// runtime with `frame-utility`, `pallet-bridge-grandpa`, `pallet-bridge-parachains`,
// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
Runtime,
// implementation of `RefundableParachainId` trait, which specifies the instance of
// the used `pallet-bridge-parachains` pallet and the bridged parachain id
Para,
// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
// the used `pallet-bridge-messages` pallet and the lane within this pallet
Msgs,
// implementation of the `RefundCalculator` trait, that is used to compute refund that
// we give to relayer for his transaction
Refund,
// getter for per-message `TransactionPriority` boost that we give to message
// delivery transactions
Priority,
// the runtime-unique identifier of this signed extension
Id,
)>,
);
impl RefundTransactionExtension
for RefundBridgedParachainMessages
where
Self: 'static + Send + Sync,
RefundBridgedGrandpaMessages<
Runtime,
Runtime::BridgesGrandpaPalletInstance,
Msgs,
Refund,
Priority,
Id,
>: 'static + Send + Sync,
Runtime: UtilityConfig>
+ BoundedBridgeGrandpaConfig
+ ParachainsConfig
+ MessagesConfig
+ RelayersConfig,
Para: RefundableParachainId,
Msgs: RefundableMessagesLaneId,
Refund: RefundCalculator,
Priority: Get,
Id: StaticStrProvider,
CallOf: Dispatchable
+ IsSubType, Runtime>>
+ GrandpaCallSubType
+ ParachainsCallSubType
+ MessagesCallSubType,
{
type Runtime = Runtime;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
type Id = Id;
fn expand_call(call: &CallOf) -> Vec<&CallOf> {
match call.is_sub_type() {
Some(UtilityCall::::batch_all { ref calls }) if calls.len() <= 3 =>
calls.iter().collect(),
Some(_) => vec![],
None => vec![call],
}
}
fn parse_and_check_for_obsolete_call(
call: &CallOf,
) -> Result, TransactionValidityError> {
let calls = Self::expand_call(call);
let total_calls = calls.len();
let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info_for(Msgs::Id::get()));
let para_finality_call = calls
.next()
.transpose()?
.and_then(|c| c.submit_parachain_heads_info_for(Para::Id::get()));
let relay_finality_call =
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
Ok(match (total_calls, relay_finality_call, para_finality_call, msgs_call) {
(3, Some(relay_finality_call), Some(para_finality_call), Some(msgs_call)) => Some(
CallInfo::AllFinalityAndMsgs(relay_finality_call, para_finality_call, msgs_call),
),
(2, None, Some(para_finality_call), Some(msgs_call)) =>
Some(CallInfo::ParachainFinalityAndMsgs(para_finality_call, msgs_call)),
(1, None, None, Some(msgs_call)) => Some(CallInfo::Msgs(msgs_call)),
_ => None,
})
}
fn check_obsolete_parsed_call(
call: &CallOf,
) -> Result<&CallOf, TransactionValidityError> {
call.check_obsolete_submit_finality_proof()?;
call.check_obsolete_submit_parachain_heads()?;
call.check_obsolete_call()?;
Ok(call)
}
fn additional_call_result_check(
relayer: &Runtime::AccountId,
call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool {
// check if relay chain state has been updated
let is_granda_call_succeeded =
RefundBridgedGrandpaMessages::<
Runtime,
Runtime::BridgesGrandpaPalletInstance,
Msgs,
Refund,
Priority,
Id,
>::additional_call_result_check(relayer, call_info, extra_weight, extra_size);
if !is_granda_call_succeeded {
return false
}
// check if parachain state has been updated
if let Some(para_proof_info) = call_info.submit_parachain_heads_info() {
if !SubmitParachainHeadsHelper::::was_successful(
para_proof_info,
) {
// we only refund relayer if all calls have updated chain state
log::trace!(
target: "runtime::bridge",
"{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof",
Id::STR,
Para::Id::get(),
Msgs::Id::get(),
relayer,
);
return false
}
}
true
}
}
/// Transaction extension that refunds a relayer for new messages coming from a standalone (GRANDPA)
/// chain.
///
/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
/// with message delivery transaction. Batch may deliver either both relay chain header and
/// parachain head, or just parachain head. Corresponding headers must be used in messages proof
/// verification.
///
/// Extension does not refund transaction tip due to security reasons.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))]
pub struct RefundBridgedGrandpaMessages(
PhantomData<(
// runtime with `frame-utility`, `pallet-bridge-grandpa`,
// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
Runtime,
// bridge GRANDPA pallet instance, used to track bridged chain state
GrandpaInstance,
// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
// the used `pallet-bridge-messages` pallet and the lane within this pallet
Msgs,
// implementation of the `RefundCalculator` trait, that is used to compute refund that
// we give to relayer for his transaction
Refund,
// getter for per-message `TransactionPriority` boost that we give to message
// delivery transactions
Priority,
// the runtime-unique identifier of this signed extension
Id,
)>,
);
impl RefundTransactionExtension
for RefundBridgedGrandpaMessages
where
Self: 'static + Send + Sync,
Runtime: UtilityConfig>
+ BoundedBridgeGrandpaConfig
+ MessagesConfig
+ RelayersConfig,
GrandpaInstance: 'static,
Msgs: RefundableMessagesLaneId,
Refund: RefundCalculator,
Priority: Get,
Id: StaticStrProvider,
CallOf: Dispatchable
+ IsSubType, Runtime>>
+ GrandpaCallSubType
+ MessagesCallSubType,
{
type Runtime = Runtime;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
type Id = Id;
fn expand_call(call: &CallOf) -> Vec<&CallOf> {
match call.is_sub_type() {
Some(UtilityCall::::batch_all { ref calls }) if calls.len() <= 2 =>
calls.iter().collect(),
Some(_) => vec![],
None => vec![call],
}
}
fn parse_and_check_for_obsolete_call(
call: &CallOf,
) -> Result, TransactionValidityError> {
let calls = Self::expand_call(call);
let total_calls = calls.len();
let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info_for(Msgs::Id::get()));
let relay_finality_call =
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
Ok(match (total_calls, relay_finality_call, msgs_call) {
(2, Some(relay_finality_call), Some(msgs_call)) =>
Some(CallInfo::RelayFinalityAndMsgs(relay_finality_call, msgs_call)),
(1, None, Some(msgs_call)) => Some(CallInfo::Msgs(msgs_call)),
_ => None,
})
}
fn check_obsolete_parsed_call(
call: &CallOf,
) -> Result<&CallOf, TransactionValidityError> {
call.check_obsolete_submit_finality_proof()?;
call.check_obsolete_call()?;
Ok(call)
}
fn additional_call_result_check(
relayer: &Runtime::AccountId,
call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool {
// check if relay chain state has been updated
if let Some(finality_proof_info) = call_info.submit_finality_proof_info() {
if !SubmitFinalityProofHelper::::was_successful(
finality_proof_info.block_number,
) {
// we only refund relayer if all calls have updated chain state
log::trace!(
target: "runtime::bridge",
"{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof",
Self::Id::STR,
::Id::get(),
relayer,
);
return false
}
// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
// transaction. If relay chain header is mandatory, the GRANDPA pallet returns
// `Pays::No`, because such transaction is mandatory for operating the bridge. But
// `utility.batchAll` transaction always requires payment. But in both cases we'll
// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
// to submit dedicated transaction.
// submitter has means to include extra weight/bytes in the `submit_finality_proof`
// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
*extra_weight = (*extra_weight).saturating_add(finality_proof_info.extra_weight);
*extra_size = (*extra_size).saturating_add(finality_proof_info.extra_size);
}
true
}
}
/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation
/// transactions. Finality transactions are not refunded.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))]
pub struct RefundBridgedMessages(
PhantomData<(
// runtime with `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
Runtime,
// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
// the used `pallet-bridge-messages` pallet and the lane within this pallet
Msgs,
// implementation of the `RefundCalculator` trait, that is used to compute refund that
// we give to relayer for his transaction
Refund,
// getter for per-message `TransactionPriority` boost that we give to message
// delivery transactions
Priority,
// the runtime-unique identifier of this signed extension
Id,
)>,
);
impl RefundTransactionExtension
for RefundBridgedMessages
where
Self: 'static + Send + Sync,
Runtime: MessagesConfig + RelayersConfig,
Msgs: RefundableMessagesLaneId,
Refund: RefundCalculator,
Priority: Get,
Id: StaticStrProvider,
CallOf: Dispatchable
+ MessagesCallSubType,
{
type Runtime = Runtime;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
type Id = Id;
fn expand_call(call: &CallOf) -> Vec<&CallOf> {
vec![call]
}
fn parse_and_check_for_obsolete_call(
call: &CallOf,
) -> Result, TransactionValidityError> {
let call = Self::check_obsolete_parsed_call(call)?;
Ok(call.call_info_for(Msgs::Id::get()).map(CallInfo::Msgs))
}
fn check_obsolete_parsed_call(
call: &CallOf,
) -> Result<&CallOf, TransactionValidityError> {
call.check_obsolete_call()?;
Ok(call)
}
fn additional_call_result_check(
_relayer: &Runtime::AccountId,
_call_info: &CallInfo,
_extra_weight: &mut Weight,
_extra_size: &mut u32,
) -> bool {
// everything is checked by the `RefundTransactionExtension`
true
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{
messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
},
messages_call_ext::{
BaseMessagesProofInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo,
UnrewardedRelayerOccupation,
},
mock::*,
DefaultRefundableParachainId,
};
use bp_header_chain::StoredHeaderDataBuilder;
use bp_messages::{
DeliveredMessages, InboundLaneData, MessageNonce, MessagesOperatingMode, OutboundLaneData,
UnrewardedRelayer, UnrewardedRelayersState,
};
use bp_parachains::{BestParaHeadHash, ParaInfo};
use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
use bp_runtime::{BasicOperatingMode, HeaderId};
use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID};
use frame_support::{
assert_storage_noop, parameter_types,
traits::{fungible::Mutate, ReservableCurrency},
weights::Weight,
};
use pallet_bridge_grandpa::{Call as GrandpaCall, Pallet as GrandpaPallet, StoredAuthoritySet};
use pallet_bridge_messages::{Call as MessagesCall, Pallet as MessagesPallet};
use pallet_bridge_parachains::{
Call as ParachainsCall, Pallet as ParachainsPallet, RelayBlockHash,
};
use sp_runtime::{
traits::{ConstU64, DispatchTransaction, Header as HeaderT},
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
DispatchError,
};
parameter_types! {
pub TestParachain: u32 = 1000;
pub TestLaneId: LaneId = TEST_LANE_ID;
pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
TEST_LANE_ID,
TEST_BRIDGED_CHAIN_ID,
RewardsAccountOwner::ThisChain,
);
pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
TEST_LANE_ID,
TEST_BRIDGED_CHAIN_ID,
RewardsAccountOwner::BridgedChain,
);
}
bp_runtime::generate_static_str_provider!(TestExtension);
type TestMessagesExtensionProvider = RefundBridgedMessages<
TestRuntime,
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund,
ConstU64<1>,
StrTestExtension,
>;
type TestMessagesExtension = RefundTransactionExtensionAdapter;
type TestGrandpaExtensionProvider = RefundBridgedGrandpaMessages<
TestRuntime,
(),
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund,
ConstU64<1>,
StrTestExtension,
>;
type TestGrandpaExtension = RefundTransactionExtensionAdapter;
type TestExtensionProvider = RefundBridgedParachainMessages<
TestRuntime,
DefaultRefundableParachainId<(), TestParachain>,
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund,
ConstU64<1>,
StrTestExtension,
>;
type TestExtension = RefundTransactionExtensionAdapter;
fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
let test_stake: ThisChainBalance = TestStake::get();
ExistentialDeposit::get().saturating_add(test_stake * 100)
}
// in tests, the following accounts are equal (because of how `into_sub_account_truncating`
// works)
fn delivery_rewards_account() -> ThisChainAccountId {
TestPaymentProcedure::rewards_account(MsgProofsRewardsAccount::get())
}
fn confirmation_rewards_account() -> ThisChainAccountId {
TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
}
pub fn relayer_account_at_this_chain() -> ThisChainAccountId {
0
}
fn relayer_account_at_bridged_chain() -> BridgedChainAccountId {
0
}
pub fn initialize_environment(
best_relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) {
let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect();
let best_relay_header = HeaderId(best_relay_header_number, RelayBlockHash::default());
pallet_bridge_grandpa::CurrentAuthoritySet::::put(
StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
);
pallet_bridge_grandpa::BestFinalized::::put(best_relay_header);
pallet_bridge_grandpa::ImportedHeaders::::insert(
best_relay_header.hash(),
bp_test_utils::test_header::(0).build(),
);
let para_id = ParaId(TestParachain::get());
let para_info = ParaInfo {
best_head_hash: BestParaHeadHash {
at_relay_block_number: parachain_head_at_relay_header_number,
head_hash: [parachain_head_at_relay_header_number as u8; 32].into(),
},
next_imported_hash_position: 0,
};
pallet_bridge_parachains::ParasInfo::::insert(para_id, para_info);
let lane_id = TestLaneId::get();
let in_lane_data =
InboundLaneData { last_confirmed_nonce: best_message, ..Default::default() };
pallet_bridge_messages::InboundLanes::::insert(lane_id, in_lane_data);
let out_lane_data =
OutboundLaneData { latest_received_nonce: best_message, ..Default::default() };
pallet_bridge_messages::OutboundLanes::::insert(lane_id, out_lane_data);
Balances::mint_into(&delivery_rewards_account(), ExistentialDeposit::get()).unwrap();
Balances::mint_into(&confirmation_rewards_account(), ExistentialDeposit::get()).unwrap();
Balances::mint_into(
&relayer_account_at_this_chain(),
initial_balance_of_relayer_account_at_this_chain(),
)
.unwrap();
}
fn submit_relay_header_call(relay_header_number: RelayBlockNumber) -> RuntimeCall {
let relay_header = BridgedChainHeader::new(
relay_header_number,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
);
let relay_justification = make_default_justification(&relay_header);
RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof {
finality_target: Box::new(relay_header),
justification: relay_justification,
})
}
pub fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall {
let relay_header = BridgedChainHeader::new(
relay_header_number,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
);
let relay_justification = make_default_justification(&relay_header);
RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof_ex {
finality_target: Box::new(relay_header),
justification: relay_justification,
current_set_id: TEST_GRANDPA_SET_ID,
is_free_execution_expected: false,
})
}
fn submit_parachain_head_call(
parachain_head_at_relay_header_number: RelayBlockNumber,
) -> RuntimeCall {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
parachains: vec![(
ParaId(TestParachain::get()),
[parachain_head_at_relay_header_number as u8; 32].into(),
)],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
})
}
pub fn submit_parachain_head_call_ex(
parachain_head_at_relay_header_number: RelayBlockNumber,
) -> RuntimeCall {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads_ex {
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
parachains: vec![(
ParaId(TestParachain::get()),
[parachain_head_at_relay_header_number as u8; 32].into(),
)],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
is_free_execution_expected: false,
})
}
fn message_delivery_call(best_message: MessageNonce) -> RuntimeCall {
RuntimeCall::BridgeMessages(MessagesCall::receive_messages_proof {
relayer_id_at_bridged_chain: relayer_account_at_bridged_chain(),
proof: FromBridgedChainMessagesProof {
bridged_header_hash: Default::default(),
storage_proof: vec![],
lane: TestLaneId::get(),
nonces_start: pallet_bridge_messages::InboundLanes::::get(
TEST_LANE_ID,
)
.last_delivered_nonce() +
1,
nonces_end: best_message,
},
messages_count: 1,
dispatch_weight: Weight::zero(),
})
}
fn message_confirmation_call(best_message: MessageNonce) -> RuntimeCall {
RuntimeCall::BridgeMessages(MessagesCall::receive_messages_delivery_proof {
proof: FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: Default::default(),
storage_proof: vec![],
lane: TestLaneId::get(),
},
relayers_state: UnrewardedRelayersState {
last_delivered_nonce: best_message,
..Default::default()
},
})
}
fn parachain_finality_and_delivery_batch_call(
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_parachain_head_call(parachain_head_at_relay_header_number),
message_delivery_call(best_message),
],
})
}
fn parachain_finality_and_confirmation_batch_call(
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_parachain_head_call(parachain_head_at_relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn relay_finality_and_delivery_batch_call(
relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call(relay_header_number),
message_delivery_call(best_message),
],
})
}
fn relay_finality_and_delivery_batch_call_ex(
relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
message_delivery_call(best_message),
],
})
}
fn relay_finality_and_confirmation_batch_call(
relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call(relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn relay_finality_and_confirmation_batch_call_ex(
relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn all_finality_and_delivery_batch_call(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call(relay_header_number),
submit_parachain_head_call(parachain_head_at_relay_header_number),
message_delivery_call(best_message),
],
})
}
fn all_finality_and_delivery_batch_call_ex(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
submit_parachain_head_call_ex(parachain_head_at_relay_header_number),
message_delivery_call(best_message),
],
})
}
fn all_finality_and_confirmation_batch_call(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call(relay_header_number),
submit_parachain_head_call(parachain_head_at_relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn all_finality_and_confirmation_batch_call_ex(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
submit_parachain_head_call_ex(parachain_head_at_relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn all_finality_pre_dispatch_data() -> PreDispatchData {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::AllFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
SubmitParachainHeadsInfo {
at_relay_block: (200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(),
free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(),
},
}),
),
}
}
fn all_finality_pre_dispatch_data_ex() -> PreDispatchData {
let mut data = all_finality_pre_dispatch_data();
data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
Some(TEST_GRANDPA_SET_ID);
data
}
fn all_finality_confirmation_pre_dispatch_data() -> PreDispatchData {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::AllFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
SubmitParachainHeadsInfo {
at_relay_block: (200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
)),
),
}
}
fn all_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData {
let mut data = all_finality_confirmation_pre_dispatch_data();
data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
Some(TEST_GRANDPA_SET_ID);
data
}
fn relay_finality_pre_dispatch_data() -> PreDispatchData {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::RelayFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(),
free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(),
},
}),
),
}
}
fn relay_finality_pre_dispatch_data_ex() -> PreDispatchData {
let mut data = relay_finality_pre_dispatch_data();
data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
Some(TEST_GRANDPA_SET_ID);
data
}
fn relay_finality_confirmation_pre_dispatch_data() -> PreDispatchData {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::RelayFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
)),
),
}
}
fn relay_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData {
let mut data = relay_finality_confirmation_pre_dispatch_data();
data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
Some(TEST_GRANDPA_SET_ID);
data
}
fn parachain_finality_pre_dispatch_data() -> PreDispatchData {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo {
at_relay_block: (200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(),
free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(),
},
}),
),
}
}
fn parachain_finality_confirmation_pre_dispatch_data() -> PreDispatchData {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo {
at_relay_block: (200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
)),
),
}
}
fn delivery_pre_dispatch_data() -> PreDispatchData {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::Msgs(MessagesCallInfo::ReceiveMessagesProof(
ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(),
free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(),
},
},
)),
}
}
fn confirmation_pre_dispatch_data() -> PreDispatchData {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::Msgs(MessagesCallInfo::ReceiveMessagesDeliveryProof(
ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
}),
)),
}
}
fn set_bundled_range_end(
mut pre_dispatch_data: PreDispatchData,
end: MessageNonce,
) -> PreDispatchData {
let msg_info = match pre_dispatch_data.call_info {
CallInfo::AllFinalityAndMsgs(_, _, ref mut info) => info,
CallInfo::RelayFinalityAndMsgs(_, ref mut info) => info,
CallInfo::ParachainFinalityAndMsgs(_, ref mut info) => info,
CallInfo::Msgs(ref mut info) => info,
};
if let MessagesCallInfo::ReceiveMessagesProof(ref mut msg_info) = msg_info {
msg_info.base.bundled_range = *msg_info.base.bundled_range.start()..=end
}
pre_dispatch_data
}
fn run_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestExtension =
RefundTransactionExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
extension
.validate_only(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|res| res.0)
}
fn run_grandpa_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestGrandpaExtension =
RefundTransactionExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
extension
.validate_only(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|res| res.0)
}
fn run_messages_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestMessagesExtension =
RefundTransactionExtensionAdapter(RefundBridgedMessages(PhantomData));
extension
.validate_only(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|res| res.0)
}
fn ignore_priority(tx: TransactionValidity) -> TransactionValidity {
tx.map(|mut tx| {
tx.priority = 0;
tx
})
}
fn run_pre_dispatch(
call: RuntimeCall,
) -> Result>, TransactionValidityError> {
let extension: TestExtension =
RefundTransactionExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
extension
.validate_and_prepare(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|(pre, _)| pre)
}
fn run_grandpa_pre_dispatch(
call: RuntimeCall,
) -> Result >, TransactionValidityError> {
let extension: TestGrandpaExtension =
RefundTransactionExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
extension
.validate_and_prepare(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|(pre, _)| pre)
}
fn run_messages_pre_dispatch(
call: RuntimeCall,
) -> Result >, TransactionValidityError> {
let extension: TestMessagesExtension =
RefundTransactionExtensionAdapter(RefundBridgedMessages(PhantomData));
extension
.validate_and_prepare(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|(pre, _)| pre)
}
fn dispatch_info() -> DispatchInfo {
DispatchInfo {
weight: Weight::from_parts(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND,
0,
),
class: frame_support::dispatch::DispatchClass::Normal,
pays_fee: frame_support::dispatch::Pays::Yes,
}
}
fn post_dispatch_info() -> PostDispatchInfo {
PostDispatchInfo { actual_weight: None, pays_fee: frame_support::dispatch::Pays::Yes }
}
fn run_post_dispatch(
pre_dispatch_data: Option>,
dispatch_result: DispatchResult,
) {
let post_dispatch_result = TestExtension::post_dispatch(
pre_dispatch_data,
&dispatch_info(),
&post_dispatch_info(),
1024,
&dispatch_result,
&(),
);
assert_eq!(post_dispatch_result, Ok(()));
}
fn expected_delivery_reward() -> ThisChainBalance {
let mut post_dispatch_info = post_dispatch_info();
let extra_weight = ::WeightInfo::extra_weight_of_successful_receive_messages_proof_call();
post_dispatch_info.actual_weight =
Some(dispatch_info().weight.saturating_sub(extra_weight));
pallet_transaction_payment::Pallet::::compute_actual_fee(
1024,
&dispatch_info(),
&post_dispatch_info,
Zero::zero(),
)
}
fn expected_confirmation_reward() -> ThisChainBalance {
pallet_transaction_payment::Pallet::::compute_actual_fee(
1024,
&dispatch_info(),
&post_dispatch_info(),
Zero::zero(),
)
}
#[test]
fn validate_doesnt_boost_transaction_priority_if_relayer_is_not_registered() {
run_test(|| {
initialize_environment(100, 100, 100);
Balances::set_balance(&relayer_account_at_this_chain(), ExistentialDeposit::get());
// message delivery is failing
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
assert_eq!(f(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
f(parachain_finality_and_delivery_batch_call(200, 200)),
Ok(Default::default()),
);
assert_eq!(
f(all_finality_and_delivery_batch_call(200, 200, 200)),
Ok(Default::default()),
);
assert_eq!(
f(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Ok(Default::default()),
);
}
// message confirmation validation is passing
assert_eq!(
ignore_priority(run_validate(message_confirmation_call(200))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_messages_validate(message_confirmation_call(200))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call(
200, 200
))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
200, 200, 200
))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex(
200, 200, 200
))),
Ok(Default::default()),
);
});
}
#[test]
fn validate_boosts_priority_of_message_delivery_transactons() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
let priority_of_100_messages_delivery =
f(message_delivery_call(200)).unwrap().priority;
let priority_of_200_messages_delivery =
f(message_delivery_call(300)).unwrap().priority;
assert!(
priority_of_200_messages_delivery > priority_of_100_messages_delivery,
"Invalid priorities: {} for 200 messages vs {} for 100 messages",
priority_of_200_messages_delivery,
priority_of_100_messages_delivery,
);
let priority_of_100_messages_confirmation =
f(message_confirmation_call(200)).unwrap().priority;
let priority_of_200_messages_confirmation =
f(message_confirmation_call(300)).unwrap().priority;
assert_eq!(
priority_of_100_messages_confirmation,
priority_of_200_messages_confirmation
);
}
});
}
#[test]
fn validate_does_not_boost_priority_of_message_delivery_transactons_with_too_many_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
let priority_of_max_messages_delivery =
f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get()))
.unwrap()
.priority;
let priority_of_more_than_max_messages_delivery =
f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1))
.unwrap()
.priority;
assert!(
priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery,
"Invalid priorities: {} for MAX messages vs {} for MAX+1 messages",
priority_of_max_messages_delivery,
priority_of_more_than_max_messages_delivery,
);
}
});
}
#[test]
fn validate_allows_non_obsolete_transactions() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
ignore_priority(run_validate(message_delivery_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(message_confirmation_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_messages_validate(message_delivery_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_messages_validate(message_confirmation_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_delivery_batch_call(200, 200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call(
200, 200
))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_delivery_batch_call(200, 200, 200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_delivery_batch_call_ex(
200, 200, 200
))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
200, 200, 200
))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex(
200, 200, 200
))),
Ok(ValidTransaction::default()),
);
});
}
#[test]
fn ext_rejects_batch_with_obsolete_relay_chain_header() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(100, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(100, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call(100, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call_ex(100, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn ext_rejects_batch_with_obsolete_parachain_head() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(101, 100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(101, 100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call(101, 100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call_ex(101, 100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(parachain_finality_and_delivery_batch_call(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn ext_rejects_batch_with_obsolete_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_confirmation_batch_call(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_confirmation_batch_call_ex(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_confirmation_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(parachain_finality_and_delivery_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(parachain_finality_and_confirmation_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn ext_rejects_batch_with_grandpa_finality_proof_when_grandpa_pallet_is_halted() {
run_test(|| {
initialize_environment(100, 100, 100);
GrandpaPallet::::set_operating_mode(
RuntimeOrigin::root(),
BasicOperatingMode::Halted,
)
.unwrap();
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
});
}
#[test]
fn ext_rejects_batch_with_parachain_finality_proof_when_parachains_pallet_is_halted() {
run_test(|| {
initialize_environment(100, 100, 100);
ParachainsPallet::::set_operating_mode(
RuntimeOrigin::root(),
BasicOperatingMode::Halted,
)
.unwrap();
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_confirmation_batch_call(200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
});
}
#[test]
fn ext_rejects_transaction_when_messages_pallet_is_halted() {
run_test(|| {
initialize_environment(100, 100, 100);
MessagesPallet::::set_operating_mode(
RuntimeOrigin::root(),
MessagesOperatingMode::Basic(BasicOperatingMode::Halted),
)
.unwrap();
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_confirmation_batch_call(200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(message_delivery_call(200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(message_confirmation_call(200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
});
}
#[test]
fn pre_dispatch_parses_batch_with_relay_chain_and_parachain_headers() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
Ok(Some(all_finality_pre_dispatch_data())),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Ok(Some(all_finality_pre_dispatch_data_ex())),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)),
Ok(Some(all_finality_confirmation_pre_dispatch_data())),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)),
Ok(Some(all_finality_confirmation_pre_dispatch_data_ex())),
);
});
}
#[test]
fn pre_dispatch_parses_batch_with_parachain_header() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)),
Ok(Some(parachain_finality_pre_dispatch_data())),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_confirmation_batch_call(200, 200)),
Ok(Some(parachain_finality_confirmation_pre_dispatch_data())),
);
});
}
#[test]
fn pre_dispatch_fails_to_parse_batch_with_multiple_parachain_headers() {
run_test(|| {
initialize_environment(100, 100, 100);
let call = RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (100, RelayBlockHash::default()),
parachains: vec![
(ParaId(TestParachain::get()), [1u8; 32].into()),
(ParaId(TestParachain::get() + 1), [1u8; 32].into()),
],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
}),
message_delivery_call(200),
],
});
assert_eq!(run_pre_dispatch(call), Ok(None),);
});
}
#[test]
fn pre_dispatch_parses_message_transaction() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(message_delivery_call(200)),
Ok(Some(delivery_pre_dispatch_data())),
);
assert_eq!(
run_pre_dispatch(message_confirmation_call(200)),
Ok(Some(confirmation_pre_dispatch_data())),
);
});
}
#[test]
fn post_dispatch_ignores_unknown_transaction() {
run_test(|| {
assert_storage_noop!(run_post_dispatch(None, Ok(())));
});
}
#[test]
fn post_dispatch_ignores_failed_transaction() {
run_test(|| {
assert_storage_noop!(run_post_dispatch(
Some(all_finality_pre_dispatch_data()),
Err(DispatchError::BadOrigin)
));
});
}
#[test]
fn post_dispatch_ignores_transaction_that_has_not_updated_relay_chain_state() {
run_test(|| {
initialize_environment(100, 200, 200);
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
});
}
#[test]
fn post_dispatch_ignores_transaction_that_has_not_updated_parachain_state() {
run_test(|| {
initialize_environment(200, 100, 200);
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_pre_dispatch_data()),
Ok(())
));
});
}
#[test]
fn post_dispatch_ignores_transaction_that_has_not_delivered_any_messages() {
run_test(|| {
initialize_environment(200, 200, 100);
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(all_finality_confirmation_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_confirmation_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(Some(confirmation_pre_dispatch_data()), Ok(())));
});
}
#[test]
fn post_dispatch_ignores_transaction_that_has_not_delivered_all_messages() {
run_test(|| {
initialize_environment(200, 200, 150);
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(all_finality_confirmation_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_confirmation_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(Some(confirmation_pre_dispatch_data()), Ok(())));
});
}
#[test]
fn post_dispatch_refunds_relayer_in_all_finality_batch_with_extra_weight() {
run_test(|| {
initialize_environment(200, 200, 200);
let mut dispatch_info = dispatch_info();
dispatch_info.weight = Weight::from_parts(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND * 2,
0,
);
// without any size/weight refund: we expect regular reward
let pre_dispatch_data = all_finality_pre_dispatch_data();
let regular_reward = expected_delivery_reward();
run_post_dispatch(Some(pre_dispatch_data), Ok(()));
assert_eq!(
RelayersPallet::::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
Some(regular_reward),
);
// now repeat the same with size+weight refund: we expect smaller reward
let mut pre_dispatch_data = all_finality_pre_dispatch_data();
match pre_dispatch_data.call_info {
CallInfo::AllFinalityAndMsgs(ref mut info, ..) => {
info.extra_weight.set_ref_time(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND,
);
info.extra_size = 32;
},
_ => unreachable!(),
}
run_post_dispatch(Some(pre_dispatch_data), Ok(()));
let reward_after_two_calls = RelayersPallet::::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get(),
)
.unwrap();
assert!(
reward_after_two_calls < 2 * regular_reward,
"{} must be < 2 * {}",
reward_after_two_calls,
2 * regular_reward,
);
});
}
#[test]
fn post_dispatch_refunds_relayer_in_all_finality_batch() {
run_test(|| {
initialize_environment(200, 200, 200);
run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
Some(expected_delivery_reward()),
);
run_post_dispatch(Some(all_finality_confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::::relayer_reward(
relayer_account_at_this_chain(),
MsgDeliveryProofsRewardsAccount::get()
),
Some(expected_confirmation_reward()),
);
});
}
#[test]
fn post_dispatch_refunds_relayer_in_parachain_finality_batch() {
run_test(|| {
initialize_environment(200, 200, 200);
run_post_dispatch(Some(parachain_finality_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
Some(expected_delivery_reward()),
);
run_post_dispatch(Some(parachain_finality_confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::::relayer_reward(
relayer_account_at_this_chain(),
MsgDeliveryProofsRewardsAccount::get()
),
Some(expected_confirmation_reward()),
);
});
}
#[test]
fn post_dispatch_refunds_relayer_in_message_transaction() {
run_test(|| {
initialize_environment(200, 200, 200);
run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
Some(expected_delivery_reward()),
);
run_post_dispatch(Some(confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::::relayer_reward(
relayer_account_at_this_chain(),
MsgDeliveryProofsRewardsAccount::get()
),
Some(expected_confirmation_reward()),
);
});
}
#[test]
fn post_dispatch_slashing_relayer_stake() {
run_test(|| {
initialize_environment(200, 200, 100);
let delivery_rewards_account_balance =
Balances::free_balance(delivery_rewards_account());
let test_stake: ThisChainBalance = TestStake::get();
Balances::set_balance(
&relayer_account_at_this_chain(),
ExistentialDeposit::get() + test_stake * 10,
);
// slashing works for message delivery calls
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), 0);
assert_eq!(
delivery_rewards_account_balance + test_stake,
Balances::free_balance(delivery_rewards_account())
);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(parachain_finality_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), 0);
assert_eq!(
delivery_rewards_account_balance + test_stake * 2,
Balances::free_balance(delivery_rewards_account())
);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), 0);
assert_eq!(
delivery_rewards_account_balance + test_stake * 3,
Balances::free_balance(delivery_rewards_account())
);
// reserve doesn't work for message confirmation calls
let confirmation_rewards_account_balance =
Balances::free_balance(confirmation_rewards_account());
Balances::reserve(&relayer_account_at_this_chain(), test_stake).unwrap();
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
assert_eq!(
confirmation_rewards_account_balance,
Balances::free_balance(confirmation_rewards_account())
);
run_post_dispatch(Some(confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(parachain_finality_confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(all_finality_confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
// check that unreserve has happened, not slashing
assert_eq!(
delivery_rewards_account_balance + test_stake * 3,
Balances::free_balance(delivery_rewards_account())
);
assert_eq!(
confirmation_rewards_account_balance,
Balances::free_balance(confirmation_rewards_account())
);
});
}
fn run_analyze_call_result(
pre_dispatch_data: PreDispatchData,
dispatch_result: DispatchResult,
) -> RelayerAccountAction {
TestExtensionProvider::analyze_call_result(
Some(Some(pre_dispatch_data)),
&dispatch_info(),
&post_dispatch_info(),
1024,
&dispatch_result,
)
}
#[test]
fn analyze_call_result_shall_not_slash_for_transactions_with_too_many_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
// the `analyze_call_result` should return slash if number of bundled messages is
// within reasonable limits
assert_eq!(
run_analyze_call_result(all_finality_pre_dispatch_data(), Ok(())),
RelayerAccountAction::Slash(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
);
assert_eq!(
run_analyze_call_result(parachain_finality_pre_dispatch_data(), Ok(())),
RelayerAccountAction::Slash(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
);
assert_eq!(
run_analyze_call_result(delivery_pre_dispatch_data(), Ok(())),
RelayerAccountAction::Slash(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
);
// the `analyze_call_result` should not return slash if number of bundled messages is
// larger than the
assert_eq!(
run_analyze_call_result(
set_bundled_range_end(all_finality_pre_dispatch_data(), 1_000_000),
Ok(())
),
RelayerAccountAction::None,
);
assert_eq!(
run_analyze_call_result(
set_bundled_range_end(parachain_finality_pre_dispatch_data(), 1_000_000),
Ok(())
),
RelayerAccountAction::None,
);
assert_eq!(
run_analyze_call_result(
set_bundled_range_end(delivery_pre_dispatch_data(), 1_000_000),
Ok(())
),
RelayerAccountAction::None,
);
});
}
#[test]
fn messages_ext_only_parses_standalone_transactions() {
run_test(|| {
initialize_environment(100, 100, 100);
// relay + parachain + message delivery calls batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// relay + parachain + message confirmation calls batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// parachain + message delivery call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
¶chain_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
// parachain + message confirmation call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
¶chain_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
// relay + message delivery call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call_ex(200, 200)
),
Ok(None),
);
// relay + message confirmation call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call_ex(200, 200)
),
Ok(None),
);
// message delivery call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&message_delivery_call(200)
),
Ok(Some(delivery_pre_dispatch_data().call_info)),
);
// message confirmation call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&message_confirmation_call(200)
),
Ok(Some(confirmation_pre_dispatch_data().call_info)),
);
});
}
#[test]
fn messages_ext_rejects_calls_with_obsolete_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_messages_pre_dispatch(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_pre_dispatch(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_validate(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_validate(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn messages_ext_accepts_calls_with_new_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_messages_pre_dispatch(message_delivery_call(200)),
Ok(Some(delivery_pre_dispatch_data())),
);
assert_eq!(
run_messages_pre_dispatch(message_confirmation_call(200)),
Ok(Some(confirmation_pre_dispatch_data())),
);
assert_eq!(run_messages_validate(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
run_messages_validate(message_confirmation_call(200)),
Ok(Default::default()),
);
});
}
#[test]
fn grandpa_ext_only_parses_valid_batches() {
run_test(|| {
initialize_environment(100, 100, 100);
// relay + parachain + message delivery calls batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// relay + parachain + message confirmation calls batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// parachain + message delivery call batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
¶chain_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
// parachain + message confirmation call batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
¶chain_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
// relay + message delivery call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call(200, 200)
),
Ok(Some(relay_finality_pre_dispatch_data().call_info)),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call_ex(200, 200)
),
Ok(Some(relay_finality_pre_dispatch_data_ex().call_info)),
);
// relay + message confirmation call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call(200, 200)
),
Ok(Some(relay_finality_confirmation_pre_dispatch_data().call_info)),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call_ex(200, 200)
),
Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex().call_info)),
);
// message delivery call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&message_delivery_call(200)
),
Ok(Some(delivery_pre_dispatch_data().call_info)),
);
// message confirmation call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&message_confirmation_call(200)
),
Ok(Some(confirmation_pre_dispatch_data().call_info)),
);
});
}
#[test]
fn grandpa_ext_rejects_batch_with_obsolete_relay_chain_header() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn grandpa_ext_rejects_calls_with_obsolete_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn grandpa_ext_accepts_calls_with_new_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 200)),
Ok(Some(relay_finality_pre_dispatch_data()),)
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 200)),
Ok(Some(relay_finality_pre_dispatch_data_ex()),)
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 200)),
Ok(Some(relay_finality_confirmation_pre_dispatch_data())),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 200)),
Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex())),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_grandpa_pre_dispatch(message_delivery_call(200)),
Ok(Some(delivery_pre_dispatch_data())),
);
assert_eq!(
run_grandpa_pre_dispatch(message_confirmation_call(200)),
Ok(Some(confirmation_pre_dispatch_data())),
);
assert_eq!(run_grandpa_validate(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
run_grandpa_validate(message_confirmation_call(200)),
Ok(Default::default()),
);
});
}
#[test]
fn does_not_panic_on_boosting_priority_of_empty_message_delivery_transaction() {
run_test(|| {
let best_delivered_message = MaxUnconfirmedMessagesAtInboundLane::get();
initialize_environment(100, 100, best_delivered_message);
// register relayer so it gets priority boost
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
// allow empty message delivery transactions
let lane_id = TestLaneId::get();
let in_lane_data = InboundLaneData {
last_confirmed_nonce: 0,
relayers: vec![UnrewardedRelayer {
relayer: relayer_account_at_bridged_chain(),
messages: DeliveredMessages { begin: 1, end: best_delivered_message },
}]
.into(),
};
pallet_bridge_messages::InboundLanes::::insert(lane_id, in_lane_data);
// now check that the priority of empty tx is the same as priority of 1-message tx
let priority_of_zero_messages_delivery =
run_validate(message_delivery_call(best_delivered_message)).unwrap().priority;
let priority_of_one_messages_delivery =
run_validate(message_delivery_call(best_delivered_message + 1))
.unwrap()
.priority;
assert_eq!(priority_of_zero_messages_delivery, priority_of_one_messages_delivery);
});
}
}