mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 11:07:56 +00:00
Remove bridges subtree
This commit is contained in:
committed by
Bastian Köcher
parent
d38f6e6728
commit
9a3e2c8c5a
@@ -1,348 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Integrity tests for chain constants and pallets configuration.
|
||||
//!
|
||||
//! Most of the tests in this module assume that the bridge is using standard (see `crate::messages`
|
||||
//! module for details) configuration.
|
||||
|
||||
use crate::{messages, messages::MessageBridge};
|
||||
|
||||
use bp_messages::{InboundLaneData, MessageNonce};
|
||||
use bp_runtime::{Chain, ChainId};
|
||||
use codec::Encode;
|
||||
use frame_support::{storage::generator::StorageValue, traits::Get, weights::Weight};
|
||||
use frame_system::limits;
|
||||
use pallet_bridge_messages::WeightInfoExt as _;
|
||||
|
||||
/// Macro that ensures that the runtime configuration and chain primitives crate are sharing
|
||||
/// the same types (nonce, block number, hash, hasher, account id and header).
|
||||
#[macro_export]
|
||||
macro_rules! assert_chain_types(
|
||||
( runtime: $r:path, this_chain: $this:path ) => {
|
||||
{
|
||||
// if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard
|
||||
// configuration is used), or something has broke existing configuration (meaning that all bridged chains
|
||||
// and relays will stop functioning)
|
||||
use frame_system::{Config as SystemConfig, pallet_prelude::{BlockNumberFor, HeaderFor}};
|
||||
use static_assertions::assert_type_eq_all;
|
||||
|
||||
assert_type_eq_all!(<$r as SystemConfig>::Nonce, bp_runtime::NonceOf<$this>);
|
||||
assert_type_eq_all!(BlockNumberFor<$r>, bp_runtime::BlockNumberOf<$this>);
|
||||
assert_type_eq_all!(<$r as SystemConfig>::Hash, bp_runtime::HashOf<$this>);
|
||||
assert_type_eq_all!(<$r as SystemConfig>::Hashing, bp_runtime::HasherOf<$this>);
|
||||
assert_type_eq_all!(<$r as SystemConfig>::AccountId, bp_runtime::AccountIdOf<$this>);
|
||||
assert_type_eq_all!(HeaderFor<$r>, bp_runtime::HeaderOf<$this>);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/// Macro that ensures that the bridge GRANDPA pallet is configured properly to bridge with given
|
||||
/// chain.
|
||||
#[macro_export]
|
||||
macro_rules! assert_bridge_grandpa_pallet_types(
|
||||
( runtime: $r:path, with_bridged_chain_grandpa_instance: $i:path, bridged_chain: $bridged:path ) => {
|
||||
{
|
||||
// if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard
|
||||
// configuration is used), or something has broke existing configuration (meaning that all bridged chains
|
||||
// and relays will stop functioning)
|
||||
use pallet_bridge_grandpa::Config as GrandpaConfig;
|
||||
use static_assertions::assert_type_eq_all;
|
||||
|
||||
assert_type_eq_all!(<$r as GrandpaConfig<$i>>::BridgedChain, $bridged);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/// Macro that ensures that the bridge messages pallet is configured properly to bridge using given
|
||||
/// configuration.
|
||||
#[macro_export]
|
||||
macro_rules! assert_bridge_messages_pallet_types(
|
||||
(
|
||||
runtime: $r:path,
|
||||
with_bridged_chain_messages_instance: $i:path,
|
||||
bridge: $bridge:path
|
||||
) => {
|
||||
{
|
||||
// if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard
|
||||
// configuration is used), or something has broke existing configuration (meaning that all bridged chains
|
||||
// and relays will stop functioning)
|
||||
use $crate::messages::{
|
||||
source::{FromThisChainMessagePayload, TargetHeaderChainAdapter},
|
||||
target::{FromBridgedChainMessagePayload, SourceHeaderChainAdapter},
|
||||
AccountIdOf, BalanceOf, BridgedChain, ThisChain,
|
||||
};
|
||||
use pallet_bridge_messages::Config as MessagesConfig;
|
||||
use static_assertions::assert_type_eq_all;
|
||||
|
||||
assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundPayload, FromThisChainMessagePayload);
|
||||
|
||||
assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundRelayer, AccountIdOf<BridgedChain<$bridge>>);
|
||||
|
||||
assert_type_eq_all!(<$r as MessagesConfig<$i>>::TargetHeaderChain, TargetHeaderChainAdapter<$bridge>);
|
||||
assert_type_eq_all!(<$r as MessagesConfig<$i>>::SourceHeaderChain, SourceHeaderChainAdapter<$bridge>);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/// Macro that combines four other macro calls - `assert_chain_types`, `assert_bridge_types`,
|
||||
/// `assert_bridge_grandpa_pallet_types` and `assert_bridge_messages_pallet_types`. It may be used
|
||||
/// at the chain that is implementing complete standard messages bridge (i.e. with bridge GRANDPA
|
||||
/// and messages pallets deployed).
|
||||
#[macro_export]
|
||||
macro_rules! assert_complete_bridge_types(
|
||||
(
|
||||
runtime: $r:path,
|
||||
with_bridged_chain_grandpa_instance: $gi:path,
|
||||
with_bridged_chain_messages_instance: $mi:path,
|
||||
bridge: $bridge:path,
|
||||
this_chain: $this:path,
|
||||
bridged_chain: $bridged:path,
|
||||
) => {
|
||||
$crate::assert_chain_types!(runtime: $r, this_chain: $this);
|
||||
$crate::assert_bridge_grandpa_pallet_types!(
|
||||
runtime: $r,
|
||||
with_bridged_chain_grandpa_instance: $gi,
|
||||
bridged_chain: $bridged
|
||||
);
|
||||
$crate::assert_bridge_messages_pallet_types!(
|
||||
runtime: $r,
|
||||
with_bridged_chain_messages_instance: $mi,
|
||||
bridge: $bridge
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/// Parameters for asserting chain-related constants.
|
||||
#[derive(Debug)]
|
||||
pub struct AssertChainConstants {
|
||||
/// Block length limits of the chain.
|
||||
pub block_length: limits::BlockLength,
|
||||
/// Block weight limits of the chain.
|
||||
pub block_weights: limits::BlockWeights,
|
||||
}
|
||||
|
||||
/// Test that our hardcoded, chain-related constants, are matching chain runtime configuration.
|
||||
///
|
||||
/// In particular, this test ensures that:
|
||||
///
|
||||
/// 1) block weight limits are matching;
|
||||
/// 2) block size limits are matching.
|
||||
pub fn assert_chain_constants<R>(params: AssertChainConstants)
|
||||
where
|
||||
R: frame_system::Config,
|
||||
{
|
||||
// we don't check runtime version here, because in our case we'll be building relay from one
|
||||
// repo and runtime will live in another repo, along with outdated relay version. To avoid
|
||||
// unneeded commits, let's not raise an error in case of version mismatch.
|
||||
|
||||
// if one of following assert fails, it means that we may need to upgrade bridged chain and
|
||||
// relay to use updated constants. If constants are now smaller than before, it may lead to
|
||||
// undeliverable messages.
|
||||
|
||||
// `BlockLength` struct is not implementing `PartialEq`, so we compare encoded values here.
|
||||
assert_eq!(
|
||||
R::BlockLength::get().encode(),
|
||||
params.block_length.encode(),
|
||||
"BlockLength from runtime ({:?}) differ from hardcoded: {:?}",
|
||||
R::BlockLength::get(),
|
||||
params.block_length,
|
||||
);
|
||||
// `BlockWeights` struct is not implementing `PartialEq`, so we compare encoded values here
|
||||
assert_eq!(
|
||||
R::BlockWeights::get().encode(),
|
||||
params.block_weights.encode(),
|
||||
"BlockWeights from runtime ({:?}) differ from hardcoded: {:?}",
|
||||
R::BlockWeights::get(),
|
||||
params.block_weights,
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that the constants, used in GRANDPA pallet configuration are valid.
|
||||
pub fn assert_bridge_grandpa_pallet_constants<R, GI>()
|
||||
where
|
||||
R: pallet_bridge_grandpa::Config<GI>,
|
||||
GI: 'static,
|
||||
{
|
||||
assert!(
|
||||
R::HeadersToKeep::get() > 0,
|
||||
"HeadersToKeep ({}) must be larger than zero",
|
||||
R::HeadersToKeep::get(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters for asserting messages pallet constants.
|
||||
#[derive(Debug)]
|
||||
pub struct AssertBridgeMessagesPalletConstants {
|
||||
/// Maximal number of unrewarded relayer entries in a confirmation transaction at the bridged
|
||||
/// chain.
|
||||
pub max_unrewarded_relayers_in_bridged_confirmation_tx: MessageNonce,
|
||||
/// Maximal number of unconfirmed messages in a confirmation transaction at the bridged chain.
|
||||
pub max_unconfirmed_messages_in_bridged_confirmation_tx: MessageNonce,
|
||||
/// Identifier of the bridged chain.
|
||||
pub bridged_chain_id: ChainId,
|
||||
}
|
||||
|
||||
/// Test that the constants, used in messages pallet configuration are valid.
|
||||
pub fn assert_bridge_messages_pallet_constants<R, MI>(params: AssertBridgeMessagesPalletConstants)
|
||||
where
|
||||
R: pallet_bridge_messages::Config<MI>,
|
||||
MI: 'static,
|
||||
{
|
||||
assert!(
|
||||
!R::ActiveOutboundLanes::get().is_empty(),
|
||||
"ActiveOutboundLanes ({:?}) must not be empty",
|
||||
R::ActiveOutboundLanes::get(),
|
||||
);
|
||||
assert!(
|
||||
R::MaxUnrewardedRelayerEntriesAtInboundLane::get() <= params.max_unrewarded_relayers_in_bridged_confirmation_tx,
|
||||
"MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}",
|
||||
R::MaxUnrewardedRelayerEntriesAtInboundLane::get(),
|
||||
params.max_unrewarded_relayers_in_bridged_confirmation_tx,
|
||||
);
|
||||
assert!(
|
||||
R::MaxUnconfirmedMessagesAtInboundLane::get() <= params.max_unconfirmed_messages_in_bridged_confirmation_tx,
|
||||
"MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}",
|
||||
R::MaxUnconfirmedMessagesAtInboundLane::get(),
|
||||
params.max_unconfirmed_messages_in_bridged_confirmation_tx,
|
||||
);
|
||||
assert_eq!(R::BridgedChainId::get(), params.bridged_chain_id);
|
||||
}
|
||||
|
||||
/// Parameters for asserting bridge pallet names.
|
||||
#[derive(Debug)]
|
||||
pub struct AssertBridgePalletNames<'a> {
|
||||
/// Name of the messages pallet, deployed at the bridged chain and used to bridge with this
|
||||
/// chain.
|
||||
pub with_this_chain_messages_pallet_name: &'a str,
|
||||
/// Name of the GRANDPA pallet, deployed at this chain and used to bridge with the bridged
|
||||
/// chain.
|
||||
pub with_bridged_chain_grandpa_pallet_name: &'a str,
|
||||
/// Name of the messages pallet, deployed at this chain and used to bridge with the bridged
|
||||
/// chain.
|
||||
pub with_bridged_chain_messages_pallet_name: &'a str,
|
||||
}
|
||||
|
||||
/// Tests that bridge pallet names used in `construct_runtime!()` macro call are matching constants
|
||||
/// from chain primitives crates.
|
||||
pub fn assert_bridge_pallet_names<B, R, GI, MI>(params: AssertBridgePalletNames)
|
||||
where
|
||||
B: MessageBridge,
|
||||
R: pallet_bridge_grandpa::Config<GI> + pallet_bridge_messages::Config<MI>,
|
||||
GI: 'static,
|
||||
MI: 'static,
|
||||
{
|
||||
assert_eq!(B::BRIDGED_MESSAGES_PALLET_NAME, params.with_this_chain_messages_pallet_name);
|
||||
assert_eq!(
|
||||
pallet_bridge_grandpa::PalletOwner::<R, GI>::storage_value_final_key().to_vec(),
|
||||
bp_runtime::storage_value_key(params.with_bridged_chain_grandpa_pallet_name, "PalletOwner",).0,
|
||||
);
|
||||
assert_eq!(
|
||||
pallet_bridge_messages::PalletOwner::<R, MI>::storage_value_final_key().to_vec(),
|
||||
bp_runtime::storage_value_key(
|
||||
params.with_bridged_chain_messages_pallet_name,
|
||||
"PalletOwner",
|
||||
)
|
||||
.0,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters for asserting complete standard messages bridge.
|
||||
#[derive(Debug)]
|
||||
pub struct AssertCompleteBridgeConstants<'a> {
|
||||
/// Parameters to assert this chain constants.
|
||||
pub this_chain_constants: AssertChainConstants,
|
||||
/// Parameters to assert messages pallet constants.
|
||||
pub messages_pallet_constants: AssertBridgeMessagesPalletConstants,
|
||||
/// Parameters to assert pallet names constants.
|
||||
pub pallet_names: AssertBridgePalletNames<'a>,
|
||||
}
|
||||
|
||||
/// All bridge-related constants tests for the complete standard messages bridge (i.e. with bridge
|
||||
/// GRANDPA and messages pallets deployed).
|
||||
pub fn assert_complete_bridge_constants<R, GI, MI, B>(params: AssertCompleteBridgeConstants)
|
||||
where
|
||||
R: frame_system::Config
|
||||
+ pallet_bridge_grandpa::Config<GI>
|
||||
+ pallet_bridge_messages::Config<MI>,
|
||||
GI: 'static,
|
||||
MI: 'static,
|
||||
B: MessageBridge,
|
||||
{
|
||||
assert_chain_constants::<R>(params.this_chain_constants);
|
||||
assert_bridge_grandpa_pallet_constants::<R, GI>();
|
||||
assert_bridge_messages_pallet_constants::<R, MI>(params.messages_pallet_constants);
|
||||
assert_bridge_pallet_names::<B, R, GI, MI>(params.pallet_names);
|
||||
}
|
||||
|
||||
/// Check that the message lane weights are correct.
|
||||
pub fn check_message_lane_weights<
|
||||
C: Chain,
|
||||
T: frame_system::Config + pallet_bridge_messages::Config<MessagesPalletInstance>,
|
||||
MessagesPalletInstance: 'static,
|
||||
>(
|
||||
bridged_chain_extra_storage_proof_size: u32,
|
||||
this_chain_max_unrewarded_relayers: MessageNonce,
|
||||
this_chain_max_unconfirmed_messages: MessageNonce,
|
||||
// whether `RefundBridgedParachainMessages` extension is deployed at runtime and is used for
|
||||
// refunding this bridge transactions?
|
||||
//
|
||||
// in other words: pass true for all known production chains
|
||||
runtime_includes_refund_extension: bool,
|
||||
) {
|
||||
type Weights<T, MI> = <T as pallet_bridge_messages::Config<MI>>::WeightInfo;
|
||||
|
||||
// check basic weight assumptions
|
||||
pallet_bridge_messages::ensure_weights_are_correct::<Weights<T, MessagesPalletInstance>>();
|
||||
|
||||
// check that weights allow us to receive messages
|
||||
let max_incoming_message_proof_size = bridged_chain_extra_storage_proof_size
|
||||
.saturating_add(messages::target::maximal_incoming_message_size(C::max_extrinsic_size()));
|
||||
pallet_bridge_messages::ensure_able_to_receive_message::<Weights<T, MessagesPalletInstance>>(
|
||||
C::max_extrinsic_size(),
|
||||
C::max_extrinsic_weight(),
|
||||
max_incoming_message_proof_size,
|
||||
messages::target::maximal_incoming_message_dispatch_weight(C::max_extrinsic_weight()),
|
||||
);
|
||||
|
||||
// check that weights allow us to receive delivery confirmations
|
||||
let max_incoming_inbound_lane_data_proof_size =
|
||||
InboundLaneData::<()>::encoded_size_hint_u32(this_chain_max_unrewarded_relayers as _);
|
||||
pallet_bridge_messages::ensure_able_to_receive_confirmation::<Weights<T, MessagesPalletInstance>>(
|
||||
C::max_extrinsic_size(),
|
||||
C::max_extrinsic_weight(),
|
||||
max_incoming_inbound_lane_data_proof_size,
|
||||
this_chain_max_unrewarded_relayers,
|
||||
this_chain_max_unconfirmed_messages,
|
||||
);
|
||||
|
||||
// check that extra weights of delivery/confirmation transactions include the weight
|
||||
// of `RefundBridgedParachainMessages` operations. This signed extension assumes the worst case
|
||||
// (i.e. slashing if delivery transaction was invalid) and refunds some weight if
|
||||
// assumption was wrong (i.e. if we did refund instead of slashing). This check
|
||||
// ensures the extension will not refund weight when it doesn't need to (i.e. if pallet
|
||||
// weights do not account weights of refund extension).
|
||||
if runtime_includes_refund_extension {
|
||||
assert_ne!(
|
||||
Weights::<T, MessagesPalletInstance>::receive_messages_proof_overhead_from_runtime(),
|
||||
Weight::zero()
|
||||
);
|
||||
assert_ne!(
|
||||
Weights::<T, MessagesPalletInstance>::receive_messages_delivery_proof_overhead_from_runtime(),
|
||||
Weight::zero()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Common types/functions that may be used by runtimes of all bridged chains.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use crate::messages_call_ext::MessagesCallSubType;
|
||||
use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType;
|
||||
use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype;
|
||||
use sp_runtime::transaction_validity::TransactionValidity;
|
||||
|
||||
pub mod messages;
|
||||
pub mod messages_api;
|
||||
pub mod messages_benchmarking;
|
||||
pub mod messages_call_ext;
|
||||
pub mod messages_generation;
|
||||
pub mod messages_xcm_extension;
|
||||
pub mod parachains_benchmarking;
|
||||
pub mod priority_calculator;
|
||||
pub mod refund_relayer_extension;
|
||||
|
||||
mod mock;
|
||||
|
||||
#[cfg(feature = "integrity-test")]
|
||||
pub mod integrity;
|
||||
|
||||
const LOG_TARGET_BRIDGE_DISPATCH: &str = "runtime::bridge-dispatch";
|
||||
|
||||
/// A duplication of the `FilterCall` trait.
|
||||
///
|
||||
/// We need this trait in order to be able to implement it for the messages pallet,
|
||||
/// since the implementation is done outside of the pallet crate.
|
||||
pub trait BridgeRuntimeFilterCall<Call> {
|
||||
/// Checks if a runtime call is valid.
|
||||
fn validate(call: &Call) -> TransactionValidity;
|
||||
}
|
||||
|
||||
impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall> for pallet_bridge_grandpa::Pallet<T, I>
|
||||
where
|
||||
T: pallet_bridge_grandpa::Config<I>,
|
||||
T::RuntimeCall: GrandpaCallSubType<T, I>,
|
||||
{
|
||||
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
|
||||
GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
|
||||
for pallet_bridge_parachains::Pallet<T, I>
|
||||
where
|
||||
T: pallet_bridge_parachains::Config<I>,
|
||||
T::RuntimeCall: ParachainsCallSubtype<T, I>,
|
||||
{
|
||||
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
|
||||
ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: pallet_bridge_messages::Config<I>, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
|
||||
for pallet_bridge_messages::Pallet<T, I>
|
||||
where
|
||||
T::RuntimeCall: MessagesCallSubType<T, I>,
|
||||
{
|
||||
/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
|
||||
/// transactions, that are delivering outdated messages/confirmations. Without this validation,
|
||||
/// even honest relayers may lose their funds if there are multiple relays running and
|
||||
/// submitting the same messages/confirmations.
|
||||
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
|
||||
call.check_obsolete_call()
|
||||
}
|
||||
}
|
||||
|
||||
/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```nocompile
|
||||
/// generate_bridge_reject_obsolete_headers_and_messages!{
|
||||
/// Call, AccountId
|
||||
/// BridgeRococoGrandpa, BridgeRococoMessages,
|
||||
/// BridgeRococoParachains
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged
|
||||
/// headers and messages. Without that extension, even honest relayers may lose their funds if
|
||||
/// there are multiple relays running and submitting the same information.
|
||||
#[macro_export]
|
||||
macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
|
||||
($call:ty, $account_id:ty, $($filter_call:ty),*) => {
|
||||
#[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)]
|
||||
pub struct BridgeRejectObsoleteHeadersAndMessages;
|
||||
impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages {
|
||||
const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
|
||||
type AccountId = $account_id;
|
||||
type Call = $call;
|
||||
type AdditionalSigned = ();
|
||||
type Pre = ();
|
||||
|
||||
fn additional_signed(&self) -> sp_std::result::Result<
|
||||
(),
|
||||
sp_runtime::transaction_validity::TransactionValidityError,
|
||||
> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate(
|
||||
&self,
|
||||
_who: &Self::AccountId,
|
||||
call: &Self::Call,
|
||||
_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
|
||||
_len: usize,
|
||||
) -> sp_runtime::transaction_validity::TransactionValidity {
|
||||
let valid = sp_runtime::transaction_validity::ValidTransaction::default();
|
||||
$(
|
||||
let valid = valid
|
||||
.combine_with(<$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?);
|
||||
)*
|
||||
Ok(valid)
|
||||
}
|
||||
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
who: &Self::AccountId,
|
||||
call: &Self::Call,
|
||||
info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
|
||||
len: usize,
|
||||
) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
|
||||
self.validate(who, call, info, len).map(drop)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::BridgeRuntimeFilterCall;
|
||||
use frame_support::{assert_err, assert_ok};
|
||||
use sp_runtime::{
|
||||
traits::SignedExtension,
|
||||
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
|
||||
};
|
||||
|
||||
pub struct MockCall {
|
||||
data: u32,
|
||||
}
|
||||
|
||||
impl sp_runtime::traits::Dispatchable for MockCall {
|
||||
type RuntimeOrigin = ();
|
||||
type Config = ();
|
||||
type Info = ();
|
||||
type PostInfo = ();
|
||||
|
||||
fn dispatch(
|
||||
self,
|
||||
_origin: Self::RuntimeOrigin,
|
||||
) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
struct FirstFilterCall;
|
||||
impl BridgeRuntimeFilterCall<MockCall> for FirstFilterCall {
|
||||
fn validate(call: &MockCall) -> TransactionValidity {
|
||||
if call.data <= 1 {
|
||||
return InvalidTransaction::Custom(1).into()
|
||||
}
|
||||
|
||||
Ok(ValidTransaction { priority: 1, ..Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
struct SecondFilterCall;
|
||||
impl BridgeRuntimeFilterCall<MockCall> for SecondFilterCall {
|
||||
fn validate(call: &MockCall) -> TransactionValidity {
|
||||
if call.data <= 2 {
|
||||
return InvalidTransaction::Custom(2).into()
|
||||
}
|
||||
|
||||
Ok(ValidTransaction { priority: 2, ..Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
generate_bridge_reject_obsolete_headers_and_messages!(
|
||||
MockCall,
|
||||
(),
|
||||
FirstFilterCall,
|
||||
SecondFilterCall
|
||||
);
|
||||
|
||||
assert_err!(
|
||||
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0),
|
||||
InvalidTransaction::Custom(1)
|
||||
);
|
||||
|
||||
assert_err!(
|
||||
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0),
|
||||
InvalidTransaction::Custom(2)
|
||||
);
|
||||
|
||||
assert_ok!(
|
||||
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0),
|
||||
ValidTransaction { priority: 3, ..Default::default() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,701 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Types that allow runtime to act as a source/target endpoint of message lanes.
|
||||
//!
|
||||
//! Messages are assumed to be encoded `Call`s of the target chain. Call-dispatch
|
||||
//! pallet is used to dispatch incoming messages. Message identified by a tuple
|
||||
//! of to elements - message lane id and message nonce.
|
||||
|
||||
pub use bp_runtime::{RangeInclusiveExt, UnderlyingChainOf, UnderlyingChainProvider};
|
||||
|
||||
use bp_header_chain::HeaderChain;
|
||||
use bp_messages::{
|
||||
source_chain::TargetHeaderChain,
|
||||
target_chain::{ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
|
||||
InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData,
|
||||
VerificationError,
|
||||
};
|
||||
use bp_runtime::{Chain, RawStorageProof, Size, StorageProofChecker};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{traits::Get, weights::Weight};
|
||||
use hash_db::Hasher;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::{convert::TryFrom, marker::PhantomData, vec::Vec};
|
||||
|
||||
/// Bidirectional message bridge.
|
||||
pub trait MessageBridge {
|
||||
/// Name of the paired messages pallet instance at the Bridged chain.
|
||||
///
|
||||
/// Should be the name that is used in the `construct_runtime!()` macro.
|
||||
const BRIDGED_MESSAGES_PALLET_NAME: &'static str;
|
||||
|
||||
/// This chain in context of message bridge.
|
||||
type ThisChain: ThisChainWithMessages;
|
||||
/// Bridged chain in context of message bridge.
|
||||
type BridgedChain: BridgedChainWithMessages;
|
||||
/// Bridged header chain.
|
||||
type BridgedHeaderChain: HeaderChain<UnderlyingChainOf<Self::BridgedChain>>;
|
||||
}
|
||||
|
||||
/// This chain that has `pallet-bridge-messages` module.
|
||||
pub trait ThisChainWithMessages: UnderlyingChainProvider {
|
||||
/// Call origin on the chain.
|
||||
type RuntimeOrigin;
|
||||
}
|
||||
|
||||
/// Bridged chain that has `pallet-bridge-messages` module.
|
||||
pub trait BridgedChainWithMessages: UnderlyingChainProvider {}
|
||||
|
||||
/// This chain in context of message bridge.
|
||||
pub type ThisChain<B> = <B as MessageBridge>::ThisChain;
|
||||
/// Bridged chain in context of message bridge.
|
||||
pub type BridgedChain<B> = <B as MessageBridge>::BridgedChain;
|
||||
/// Hash used on the chain.
|
||||
pub type HashOf<C> = bp_runtime::HashOf<<C as UnderlyingChainProvider>::Chain>;
|
||||
/// Hasher used on the chain.
|
||||
pub type HasherOf<C> = bp_runtime::HasherOf<UnderlyingChainOf<C>>;
|
||||
/// Account id used on the chain.
|
||||
pub type AccountIdOf<C> = bp_runtime::AccountIdOf<UnderlyingChainOf<C>>;
|
||||
/// Type of balances that is used on the chain.
|
||||
pub type BalanceOf<C> = bp_runtime::BalanceOf<UnderlyingChainOf<C>>;
|
||||
|
||||
/// Sub-module that is declaring types required for processing This -> Bridged chain messages.
|
||||
pub mod source {
|
||||
use super::*;
|
||||
|
||||
/// Message payload for This -> Bridged chain messages.
|
||||
pub type FromThisChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
|
||||
|
||||
/// Maximal size of outbound message payload.
|
||||
pub struct FromThisChainMaximalOutboundPayloadSize<B>(PhantomData<B>);
|
||||
|
||||
impl<B: MessageBridge> Get<u32> for FromThisChainMaximalOutboundPayloadSize<B> {
|
||||
fn get() -> u32 {
|
||||
maximal_message_size::<B>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages delivery proof from bridged chain:
|
||||
///
|
||||
/// - hash of finalized header;
|
||||
/// - storage proof of inbound lane state;
|
||||
/// - lane id.
|
||||
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
|
||||
/// Hash of the bridge header the proof is for.
|
||||
pub bridged_header_hash: BridgedHeaderHash,
|
||||
/// Storage trie proof generated for [`Self::bridged_header_hash`].
|
||||
pub storage_proof: RawStorageProof,
|
||||
/// Lane id of which messages were delivered and the proof is for.
|
||||
pub lane: LaneId,
|
||||
}
|
||||
|
||||
impl<BridgedHeaderHash> Size for FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
|
||||
fn size(&self) -> u32 {
|
||||
u32::try_from(
|
||||
self.storage_proof
|
||||
.iter()
|
||||
.fold(0usize, |sum, node| sum.saturating_add(node.len())),
|
||||
)
|
||||
.unwrap_or(u32::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
/// 'Parsed' message delivery proof - inbound lane id and its state.
|
||||
pub type ParsedMessagesDeliveryProofFromBridgedChain<B> =
|
||||
(LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>);
|
||||
|
||||
/// Return maximal message size of This -> Bridged chain message.
|
||||
pub fn maximal_message_size<B: MessageBridge>() -> u32 {
|
||||
super::target::maximal_incoming_message_size(
|
||||
UnderlyingChainOf::<BridgedChain<B>>::max_extrinsic_size(),
|
||||
)
|
||||
}
|
||||
|
||||
/// `TargetHeaderChain` implementation that is using default types and perform default checks.
|
||||
pub struct TargetHeaderChainAdapter<B>(PhantomData<B>);
|
||||
|
||||
impl<B: MessageBridge> TargetHeaderChain<FromThisChainMessagePayload, AccountIdOf<ThisChain<B>>>
|
||||
for TargetHeaderChainAdapter<B>
|
||||
{
|
||||
type MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>;
|
||||
|
||||
fn verify_message(payload: &FromThisChainMessagePayload) -> Result<(), VerificationError> {
|
||||
verify_chain_message::<B>(payload)
|
||||
}
|
||||
|
||||
fn verify_messages_delivery_proof(
|
||||
proof: Self::MessagesDeliveryProof,
|
||||
) -> Result<(LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>), VerificationError> {
|
||||
verify_messages_delivery_proof::<B>(proof)
|
||||
}
|
||||
}
|
||||
|
||||
/// Do basic Bridged-chain specific verification of This -> Bridged chain message.
|
||||
///
|
||||
/// Ok result from this function means that the delivery transaction with this message
|
||||
/// may be 'mined' by the target chain.
|
||||
pub fn verify_chain_message<B: MessageBridge>(
|
||||
payload: &FromThisChainMessagePayload,
|
||||
) -> Result<(), VerificationError> {
|
||||
// IMPORTANT: any error that is returned here is fatal for the bridge, because
|
||||
// this code is executed at the bridge hub and message sender actually lives
|
||||
// at some sibling parachain. So we are failing **after** the message has been
|
||||
// sent and we can't report it back to sender (unless error report mechanism is
|
||||
// embedded into message and its dispatcher).
|
||||
|
||||
// apart from maximal message size check (see below), we should also check the message
|
||||
// dispatch weight here. But we assume that the bridged chain will just push the message
|
||||
// to some queue (XCMP, UMP, DMP), so the weight is constant and fits the block.
|
||||
|
||||
// The maximal size of extrinsic at Substrate-based chain depends on the
|
||||
// `frame_system::Config::MaximumBlockLength` and
|
||||
// `frame_system::Config::AvailableBlockRatio` constants. This check is here to be sure that
|
||||
// the lane won't stuck because message is too large to fit into delivery transaction.
|
||||
//
|
||||
// **IMPORTANT NOTE**: the delivery transaction contains storage proof of the message, not
|
||||
// the message itself. The proof is always larger than the message. But unless chain state
|
||||
// is enormously large, it should be several dozens/hundreds of bytes. The delivery
|
||||
// transaction also contains signatures and signed extensions. Because of this, we reserve
|
||||
// 1/3 of the the maximal extrinsic size for this data.
|
||||
if payload.len() > maximal_message_size::<B>() as usize {
|
||||
return Err(VerificationError::MessageTooLarge)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify proof of This -> Bridged chain messages delivery.
|
||||
///
|
||||
/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
|
||||
/// parachains, please use the `verify_messages_delivery_proof_from_parachain`.
|
||||
pub fn verify_messages_delivery_proof<B: MessageBridge>(
|
||||
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>,
|
||||
) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<B>, VerificationError> {
|
||||
let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } =
|
||||
proof;
|
||||
let mut storage =
|
||||
B::BridgedHeaderChain::storage_proof_checker(bridged_header_hash, storage_proof)
|
||||
.map_err(VerificationError::HeaderChain)?;
|
||||
// Messages delivery proof is just proof of single storage key read => any error
|
||||
// is fatal.
|
||||
let storage_inbound_lane_data_key = bp_messages::storage_keys::inbound_lane_data_key(
|
||||
B::BRIDGED_MESSAGES_PALLET_NAME,
|
||||
&lane,
|
||||
);
|
||||
let inbound_lane_data = storage
|
||||
.read_and_decode_mandatory_value(storage_inbound_lane_data_key.0.as_ref())
|
||||
.map_err(VerificationError::InboundLaneStorage)?;
|
||||
|
||||
// check that the storage proof doesn't have any untouched trie nodes
|
||||
storage.ensure_no_unused_nodes().map_err(VerificationError::StorageProof)?;
|
||||
|
||||
Ok((lane, inbound_lane_data))
|
||||
}
|
||||
}
|
||||
|
||||
/// Sub-module that is declaring types required for processing Bridged -> This chain messages.
|
||||
pub mod target {
|
||||
use super::*;
|
||||
|
||||
/// Decoded Bridged -> This message payload.
|
||||
pub type FromBridgedChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
|
||||
|
||||
/// Messages proof from bridged chain:
|
||||
///
|
||||
/// - hash of finalized header;
|
||||
/// - storage proof of messages and (optionally) outbound lane state;
|
||||
/// - lane id;
|
||||
/// - nonces (inclusive range) of messages which are included in this proof.
|
||||
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct FromBridgedChainMessagesProof<BridgedHeaderHash> {
|
||||
/// Hash of the finalized bridged header the proof is for.
|
||||
pub bridged_header_hash: BridgedHeaderHash,
|
||||
/// A storage trie proof of messages being delivered.
|
||||
pub storage_proof: RawStorageProof,
|
||||
/// Messages in this proof are sent over this lane.
|
||||
pub lane: LaneId,
|
||||
/// Nonce of the first message being delivered.
|
||||
pub nonces_start: MessageNonce,
|
||||
/// Nonce of the last message being delivered.
|
||||
pub nonces_end: MessageNonce,
|
||||
}
|
||||
|
||||
impl<BridgedHeaderHash> Size for FromBridgedChainMessagesProof<BridgedHeaderHash> {
|
||||
fn size(&self) -> u32 {
|
||||
u32::try_from(
|
||||
self.storage_proof
|
||||
.iter()
|
||||
.fold(0usize, |sum, node| sum.saturating_add(node.len())),
|
||||
)
|
||||
.unwrap_or(u32::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return maximal dispatch weight of the message we're able to receive.
|
||||
pub fn maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
|
||||
maximal_extrinsic_weight / 2
|
||||
}
|
||||
|
||||
/// Return maximal message size given maximal extrinsic size.
|
||||
pub fn maximal_incoming_message_size(maximal_extrinsic_size: u32) -> u32 {
|
||||
maximal_extrinsic_size / 3 * 2
|
||||
}
|
||||
|
||||
/// `SourceHeaderChain` implementation that is using default types and perform default checks.
|
||||
pub struct SourceHeaderChainAdapter<B>(PhantomData<B>);
|
||||
|
||||
impl<B: MessageBridge> SourceHeaderChain for SourceHeaderChainAdapter<B> {
|
||||
type MessagesProof = FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>;
|
||||
|
||||
fn verify_messages_proof(
|
||||
proof: Self::MessagesProof,
|
||||
messages_count: u32,
|
||||
) -> Result<ProvedMessages<Message>, VerificationError> {
|
||||
verify_messages_proof::<B>(proof, messages_count)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify proof of Bridged -> This chain messages.
|
||||
///
|
||||
/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
|
||||
/// parachains, please use the `verify_messages_proof_from_parachain`.
|
||||
///
|
||||
/// The `messages_count` argument verification (sane limits) is supposed to be made
|
||||
/// outside of this function. This function only verifies that the proof declares exactly
|
||||
/// `messages_count` messages.
|
||||
pub fn verify_messages_proof<B: MessageBridge>(
|
||||
proof: FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>,
|
||||
messages_count: u32,
|
||||
) -> Result<ProvedMessages<Message>, VerificationError> {
|
||||
let FromBridgedChainMessagesProof {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane,
|
||||
nonces_start,
|
||||
nonces_end,
|
||||
} = proof;
|
||||
let storage =
|
||||
B::BridgedHeaderChain::storage_proof_checker(bridged_header_hash, storage_proof)
|
||||
.map_err(VerificationError::HeaderChain)?;
|
||||
let mut parser = StorageProofCheckerAdapter::<_, B> { storage, _dummy: Default::default() };
|
||||
let nonces_range = nonces_start..=nonces_end;
|
||||
|
||||
// receiving proofs where end < begin is ok (if proof includes outbound lane state)
|
||||
let messages_in_the_proof = nonces_range.checked_len().unwrap_or(0);
|
||||
if messages_in_the_proof != MessageNonce::from(messages_count) {
|
||||
return Err(VerificationError::MessagesCountMismatch)
|
||||
}
|
||||
|
||||
// Read messages first. All messages that are claimed to be in the proof must
|
||||
// be in the proof. So any error in `read_value`, or even missing value is fatal.
|
||||
//
|
||||
// Mind that we allow proofs with no messages if outbound lane state is proved.
|
||||
let mut messages = Vec::with_capacity(messages_in_the_proof as _);
|
||||
for nonce in nonces_range {
|
||||
let message_key = MessageKey { lane_id: lane, nonce };
|
||||
let message_payload = parser.read_and_decode_message_payload(&message_key)?;
|
||||
messages.push(Message { key: message_key, payload: message_payload });
|
||||
}
|
||||
|
||||
// Now let's check if proof contains outbound lane state proof. It is optional, so
|
||||
// we simply ignore `read_value` errors and missing value.
|
||||
let proved_lane_messages = ProvedLaneMessages {
|
||||
lane_state: parser.read_and_decode_outbound_lane_data(&lane)?,
|
||||
messages,
|
||||
};
|
||||
|
||||
// Now we may actually check if the proof is empty or not.
|
||||
if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() {
|
||||
return Err(VerificationError::EmptyMessageProof)
|
||||
}
|
||||
|
||||
// check that the storage proof doesn't have any untouched trie nodes
|
||||
parser
|
||||
.storage
|
||||
.ensure_no_unused_nodes()
|
||||
.map_err(VerificationError::StorageProof)?;
|
||||
|
||||
// We only support single lane messages in this generated_schema
|
||||
let mut proved_messages = ProvedMessages::new();
|
||||
proved_messages.insert(lane, proved_lane_messages);
|
||||
|
||||
Ok(proved_messages)
|
||||
}
|
||||
|
||||
struct StorageProofCheckerAdapter<H: Hasher, B> {
|
||||
storage: StorageProofChecker<H>,
|
||||
_dummy: sp_std::marker::PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<H: Hasher, B: MessageBridge> StorageProofCheckerAdapter<H, B> {
|
||||
fn read_and_decode_outbound_lane_data(
|
||||
&mut self,
|
||||
lane_id: &LaneId,
|
||||
) -> Result<Option<OutboundLaneData>, VerificationError> {
|
||||
let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key(
|
||||
B::BRIDGED_MESSAGES_PALLET_NAME,
|
||||
lane_id,
|
||||
);
|
||||
|
||||
self.storage
|
||||
.read_and_decode_opt_value(storage_outbound_lane_data_key.0.as_ref())
|
||||
.map_err(VerificationError::OutboundLaneStorage)
|
||||
}
|
||||
|
||||
fn read_and_decode_message_payload(
|
||||
&mut self,
|
||||
message_key: &MessageKey,
|
||||
) -> Result<MessagePayload, VerificationError> {
|
||||
let storage_message_key = bp_messages::storage_keys::message_key(
|
||||
B::BRIDGED_MESSAGES_PALLET_NAME,
|
||||
&message_key.lane_id,
|
||||
message_key.nonce,
|
||||
);
|
||||
self.storage
|
||||
.read_and_decode_mandatory_value(storage_message_key.0.as_ref())
|
||||
.map_err(VerificationError::MessageStorage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `BridgeMessagesCall` used by a chain.
|
||||
pub type BridgeMessagesCallOf<C> = bp_messages::BridgeMessagesCall<
|
||||
bp_runtime::AccountIdOf<C>,
|
||||
target::FromBridgedChainMessagesProof<bp_runtime::HashOf<C>>,
|
||||
source::FromBridgedChainMessagesDeliveryProof<bp_runtime::HashOf<C>>,
|
||||
>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
messages_generation::{
|
||||
encode_all_messages, encode_lane_data, prepare_messages_storage_proof,
|
||||
},
|
||||
mock::*,
|
||||
};
|
||||
use bp_header_chain::{HeaderChainError, StoredHeaderDataBuilder};
|
||||
use bp_runtime::{HeaderId, StorageProofError};
|
||||
use codec::Encode;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::traits::Header as _;
|
||||
|
||||
#[test]
|
||||
fn verify_chain_message_rejects_message_with_too_large_declared_weight() {
|
||||
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
|
||||
42;
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT -
|
||||
1
|
||||
])
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_chain_message_rejects_message_too_large_message() {
|
||||
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
|
||||
0;
|
||||
source::maximal_message_size::<OnThisChainBridge>()
|
||||
as usize + 1
|
||||
],)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_chain_message_accepts_maximal_message() {
|
||||
assert_eq!(
|
||||
source::verify_chain_message::<OnThisChainBridge>(&vec![
|
||||
0;
|
||||
source::maximal_message_size::<OnThisChainBridge>()
|
||||
as _
|
||||
],),
|
||||
Ok(()),
|
||||
);
|
||||
}
|
||||
|
||||
fn using_messages_proof<R>(
|
||||
nonces_end: MessageNonce,
|
||||
outbound_lane_data: Option<OutboundLaneData>,
|
||||
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
|
||||
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
|
||||
test: impl Fn(target::FromBridgedChainMessagesProof<H256>) -> R,
|
||||
) -> R {
|
||||
let (state_root, storage_proof) = prepare_messages_storage_proof::<OnThisChainBridge>(
|
||||
TEST_LANE_ID,
|
||||
1..=nonces_end,
|
||||
outbound_lane_data,
|
||||
bp_runtime::StorageProofSize::Minimal(0),
|
||||
vec![42],
|
||||
encode_message,
|
||||
encode_outbound_lane_data,
|
||||
);
|
||||
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(move || {
|
||||
let bridged_header = BridgedChainHeader::new(
|
||||
0,
|
||||
Default::default(),
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let bridged_header_hash = bridged_header.hash();
|
||||
|
||||
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(HeaderId(
|
||||
0,
|
||||
bridged_header_hash,
|
||||
));
|
||||
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
bridged_header.build(),
|
||||
);
|
||||
test(target::FromBridgedChainMessagesProof {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane: TEST_LANE_ID,
|
||||
nonces_start: 1,
|
||||
nonces_end,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn messages_proof_is_rejected_if_declared_less_than_actual_number_of_messages() {
|
||||
assert_eq!(
|
||||
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
|
||||
target::verify_messages_proof::<OnThisChainBridge>(proof, 5)
|
||||
}),
|
||||
Err(VerificationError::MessagesCountMismatch),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() {
|
||||
assert_eq!(
|
||||
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
|
||||
target::verify_messages_proof::<OnThisChainBridge>(proof, 15)
|
||||
}),
|
||||
Err(VerificationError::MessagesCountMismatch),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_header_is_missing_from_the_chain() {
|
||||
assert_eq!(
|
||||
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
|
||||
let bridged_header_hash =
|
||||
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
|
||||
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::remove(bridged_header_hash);
|
||||
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
|
||||
}),
|
||||
Err(VerificationError::HeaderChain(HeaderChainError::UnknownHeader)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_header_state_root_mismatches() {
|
||||
assert_eq!(
|
||||
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
|
||||
let bridged_header_hash =
|
||||
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
|
||||
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
BridgedChainHeader::new(
|
||||
0,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
.build(),
|
||||
);
|
||||
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
|
||||
}),
|
||||
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
|
||||
StorageProofError::StorageRootMismatch
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_it_has_duplicate_trie_nodes() {
|
||||
assert_eq!(
|
||||
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |mut proof| {
|
||||
let node = proof.storage_proof.pop().unwrap();
|
||||
proof.storage_proof.push(node.clone());
|
||||
proof.storage_proof.push(node);
|
||||
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
|
||||
},),
|
||||
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
|
||||
StorageProofError::DuplicateNodesInProof
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_it_has_unused_trie_nodes() {
|
||||
assert_eq!(
|
||||
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |mut proof| {
|
||||
proof.storage_proof.push(vec![42]);
|
||||
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
|
||||
},),
|
||||
Err(VerificationError::StorageProof(StorageProofError::UnusedNodesInTheProof)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_required_message_is_missing() {
|
||||
matches!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
|n, m| if n != 5 { Some(m.encode()) } else { None },
|
||||
encode_lane_data,
|
||||
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
|
||||
),
|
||||
Err(VerificationError::MessageStorage(StorageProofError::StorageValueEmpty)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_message_decode_fails() {
|
||||
matches!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
|n, m| {
|
||||
let mut m = m.encode();
|
||||
if n == 5 {
|
||||
m = vec![42]
|
||||
}
|
||||
Some(m)
|
||||
},
|
||||
encode_lane_data,
|
||||
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
|
||||
),
|
||||
Err(VerificationError::MessageStorage(StorageProofError::StorageValueDecodeFailed(_))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() {
|
||||
matches!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
Some(OutboundLaneData {
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
encode_all_messages,
|
||||
|d| {
|
||||
let mut d = d.encode();
|
||||
d.truncate(1);
|
||||
d
|
||||
},
|
||||
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
|
||||
),
|
||||
Err(VerificationError::OutboundLaneStorage(
|
||||
StorageProofError::StorageValueDecodeFailed(_)
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_it_is_empty() {
|
||||
assert_eq!(
|
||||
using_messages_proof(0, None, encode_all_messages, encode_lane_data, |proof| {
|
||||
target::verify_messages_proof::<OnThisChainBridge>(proof, 0)
|
||||
},),
|
||||
Err(VerificationError::EmptyMessageProof),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_empty_message_proof_without_messages_is_accepted() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
0,
|
||||
Some(OutboundLaneData {
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 0),
|
||||
),
|
||||
Ok(vec![(
|
||||
TEST_LANE_ID,
|
||||
ProvedLaneMessages {
|
||||
lane_state: Some(OutboundLaneData {
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
messages: Vec::new(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_empty_message_proof_is_accepted() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
1,
|
||||
Some(OutboundLaneData {
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 1),
|
||||
),
|
||||
Ok(vec![(
|
||||
TEST_LANE_ID,
|
||||
ProvedLaneMessages {
|
||||
lane_state: Some(OutboundLaneData {
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
messages: vec![Message {
|
||||
key: MessageKey { lane_id: TEST_LANE_ID, nonce: 1 },
|
||||
payload: vec![42],
|
||||
}],
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_messages_proof_does_not_panic_if_messages_count_mismatches() {
|
||||
assert_eq!(
|
||||
using_messages_proof(1, None, encode_all_messages, encode_lane_data, |mut proof| {
|
||||
proof.nonces_end = u64::MAX;
|
||||
target::verify_messages_proof::<OnThisChainBridge>(proof, u32::MAX)
|
||||
},),
|
||||
Err(VerificationError::MessagesCountMismatch),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helpers for implementing various message-related runtime API methods.
|
||||
|
||||
use bp_messages::{
|
||||
InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails,
|
||||
};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
/// Implementation of the `To*OutboundLaneApi::message_details`.
|
||||
pub fn outbound_message_details<Runtime, MessagesPalletInstance>(
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
) -> Vec<OutboundMessageDetails>
|
||||
where
|
||||
Runtime: pallet_bridge_messages::Config<MessagesPalletInstance>,
|
||||
MessagesPalletInstance: 'static,
|
||||
{
|
||||
(begin..=end)
|
||||
.filter_map(|nonce| {
|
||||
let message_data =
|
||||
pallet_bridge_messages::Pallet::<Runtime, MessagesPalletInstance>::outbound_message_data(lane, nonce)?;
|
||||
Some(OutboundMessageDetails {
|
||||
nonce,
|
||||
// dispatch message weight is always zero at the source chain, since we're paying for
|
||||
// dispatch at the target chain
|
||||
dispatch_weight: frame_support::weights::Weight::zero(),
|
||||
size: message_data.len() as _,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Implementation of the `To*InboundLaneApi::message_details`.
|
||||
pub fn inbound_message_details<Runtime, MessagesPalletInstance>(
|
||||
lane: LaneId,
|
||||
messages: Vec<(MessagePayload, OutboundMessageDetails)>,
|
||||
) -> Vec<InboundMessageDetails>
|
||||
where
|
||||
Runtime: pallet_bridge_messages::Config<MessagesPalletInstance>,
|
||||
MessagesPalletInstance: 'static,
|
||||
{
|
||||
messages
|
||||
.into_iter()
|
||||
.map(|(payload, details)| {
|
||||
pallet_bridge_messages::Pallet::<Runtime, MessagesPalletInstance>::inbound_message_data(
|
||||
lane, payload, details,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Everything required to run benchmarks of messages module, based on
|
||||
//! `bridge_runtime_common::messages` implementation.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::{
|
||||
messages::{
|
||||
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
|
||||
AccountIdOf, BridgedChain, HashOf, MessageBridge, ThisChain,
|
||||
},
|
||||
messages_generation::{
|
||||
encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof,
|
||||
prepare_messages_storage_proof,
|
||||
},
|
||||
};
|
||||
|
||||
use bp_messages::MessagePayload;
|
||||
use bp_polkadot_core::parachains::ParaHash;
|
||||
use bp_runtime::{Chain, Parachain, StorageProofSize, UnderlyingChainOf};
|
||||
use codec::Encode;
|
||||
use frame_support::weights::Weight;
|
||||
use pallet_bridge_messages::benchmarking::{MessageDeliveryProofParams, MessageProofParams};
|
||||
use sp_runtime::traits::{Header, Zero};
|
||||
use sp_std::prelude::*;
|
||||
use xcm::latest::prelude::*;
|
||||
|
||||
/// Prepare inbound bridge message according to given message proof parameters.
|
||||
fn prepare_inbound_message(
|
||||
params: &MessageProofParams,
|
||||
successful_dispatch_message_generator: impl Fn(usize) -> MessagePayload,
|
||||
) -> MessagePayload {
|
||||
// we only care about **this** message size when message proof needs to be `Minimal`
|
||||
let expected_size = match params.size {
|
||||
StorageProofSize::Minimal(size) => size as usize,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
// if we don't need a correct message, then we may just return some random blob
|
||||
if !params.is_successful_dispatch_expected {
|
||||
return vec![0u8; expected_size]
|
||||
}
|
||||
|
||||
// else let's prepare successful message.
|
||||
let msg = successful_dispatch_message_generator(expected_size);
|
||||
assert!(
|
||||
msg.len() >= expected_size,
|
||||
"msg.len(): {} does not match expected_size: {}",
|
||||
expected_size,
|
||||
msg.len()
|
||||
);
|
||||
msg
|
||||
}
|
||||
|
||||
/// Prepare proof of messages for the `receive_messages_proof` call.
|
||||
///
|
||||
/// In addition to returning valid messages proof, environment is prepared to verify this message
|
||||
/// proof.
|
||||
///
|
||||
/// This method is intended to be used when benchmarking pallet, linked to the chain that
|
||||
/// uses GRANDPA finality. For parachains, please use the `prepare_message_proof_from_parachain`
|
||||
/// function.
|
||||
pub fn prepare_message_proof_from_grandpa_chain<R, FI, B>(
|
||||
params: MessageProofParams,
|
||||
message_generator: impl Fn(usize) -> MessagePayload,
|
||||
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>, Weight)
|
||||
where
|
||||
R: pallet_bridge_grandpa::Config<FI, BridgedChain = UnderlyingChainOf<BridgedChain<B>>>,
|
||||
FI: 'static,
|
||||
B: MessageBridge,
|
||||
{
|
||||
// prepare storage proof
|
||||
let (state_root, storage_proof) = prepare_messages_storage_proof::<B>(
|
||||
params.lane,
|
||||
params.message_nonces.clone(),
|
||||
params.outbound_lane_data.clone(),
|
||||
params.size,
|
||||
prepare_inbound_message(¶ms, message_generator),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
);
|
||||
|
||||
// update runtime storage
|
||||
let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::<R, FI>(state_root);
|
||||
|
||||
(
|
||||
FromBridgedChainMessagesProof {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane: params.lane,
|
||||
nonces_start: *params.message_nonces.start(),
|
||||
nonces_end: *params.message_nonces.end(),
|
||||
},
|
||||
Weight::MAX / 1000,
|
||||
)
|
||||
}
|
||||
|
||||
/// Prepare proof of messages for the `receive_messages_proof` call.
|
||||
///
|
||||
/// In addition to returning valid messages proof, environment is prepared to verify this message
|
||||
/// proof.
|
||||
///
|
||||
/// This method is intended to be used when benchmarking pallet, linked to the chain that
|
||||
/// uses parachain finality. For GRANDPA chains, please use the
|
||||
/// `prepare_message_proof_from_grandpa_chain` function.
|
||||
pub fn prepare_message_proof_from_parachain<R, PI, B>(
|
||||
params: MessageProofParams,
|
||||
message_generator: impl Fn(usize) -> MessagePayload,
|
||||
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>, Weight)
|
||||
where
|
||||
R: pallet_bridge_parachains::Config<PI>,
|
||||
PI: 'static,
|
||||
B: MessageBridge,
|
||||
UnderlyingChainOf<BridgedChain<B>>: Chain<Hash = ParaHash> + Parachain,
|
||||
{
|
||||
// prepare storage proof
|
||||
let (state_root, storage_proof) = prepare_messages_storage_proof::<B>(
|
||||
params.lane,
|
||||
params.message_nonces.clone(),
|
||||
params.outbound_lane_data.clone(),
|
||||
params.size,
|
||||
prepare_inbound_message(¶ms, message_generator),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
);
|
||||
|
||||
// update runtime storage
|
||||
let (_, bridged_header_hash) =
|
||||
insert_header_to_parachains_pallet::<R, PI, UnderlyingChainOf<BridgedChain<B>>>(state_root);
|
||||
|
||||
(
|
||||
FromBridgedChainMessagesProof {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane: params.lane,
|
||||
nonces_start: *params.message_nonces.start(),
|
||||
nonces_end: *params.message_nonces.end(),
|
||||
},
|
||||
Weight::MAX / 1000,
|
||||
)
|
||||
}
|
||||
|
||||
/// Prepare proof of messages delivery for the `receive_messages_delivery_proof` call.
|
||||
///
|
||||
/// This method is intended to be used when benchmarking pallet, linked to the chain that
|
||||
/// uses GRANDPA finality. For parachains, please use the
|
||||
/// `prepare_message_delivery_proof_from_parachain` function.
|
||||
pub fn prepare_message_delivery_proof_from_grandpa_chain<R, FI, B>(
|
||||
params: MessageDeliveryProofParams<AccountIdOf<ThisChain<B>>>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>
|
||||
where
|
||||
R: pallet_bridge_grandpa::Config<FI, BridgedChain = UnderlyingChainOf<BridgedChain<B>>>,
|
||||
FI: 'static,
|
||||
B: MessageBridge,
|
||||
{
|
||||
// prepare storage proof
|
||||
let lane = params.lane;
|
||||
let (state_root, storage_proof) = prepare_message_delivery_storage_proof::<B>(
|
||||
params.lane,
|
||||
params.inbound_lane_data,
|
||||
params.size,
|
||||
);
|
||||
|
||||
// update runtime storage
|
||||
let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::<R, FI>(state_root);
|
||||
|
||||
FromBridgedChainMessagesDeliveryProof {
|
||||
bridged_header_hash: bridged_header_hash.into(),
|
||||
storage_proof,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare proof of messages delivery for the `receive_messages_delivery_proof` call.
|
||||
///
|
||||
/// This method is intended to be used when benchmarking pallet, linked to the chain that
|
||||
/// uses parachain finality. For GRANDPA chains, please use the
|
||||
/// `prepare_message_delivery_proof_from_grandpa_chain` function.
|
||||
pub fn prepare_message_delivery_proof_from_parachain<R, PI, B>(
|
||||
params: MessageDeliveryProofParams<AccountIdOf<ThisChain<B>>>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>
|
||||
where
|
||||
R: pallet_bridge_parachains::Config<PI>,
|
||||
PI: 'static,
|
||||
B: MessageBridge,
|
||||
UnderlyingChainOf<BridgedChain<B>>: Chain<Hash = ParaHash> + Parachain,
|
||||
{
|
||||
// prepare storage proof
|
||||
let lane = params.lane;
|
||||
let (state_root, storage_proof) = prepare_message_delivery_storage_proof::<B>(
|
||||
params.lane,
|
||||
params.inbound_lane_data,
|
||||
params.size,
|
||||
);
|
||||
|
||||
// update runtime storage
|
||||
let (_, bridged_header_hash) =
|
||||
insert_header_to_parachains_pallet::<R, PI, UnderlyingChainOf<BridgedChain<B>>>(state_root);
|
||||
|
||||
FromBridgedChainMessagesDeliveryProof {
|
||||
bridged_header_hash: bridged_header_hash.into(),
|
||||
storage_proof,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert header to the bridge GRANDPA pallet.
|
||||
pub(crate) fn insert_header_to_grandpa_pallet<R, GI>(
|
||||
state_root: bp_runtime::HashOf<R::BridgedChain>,
|
||||
) -> (bp_runtime::BlockNumberOf<R::BridgedChain>, bp_runtime::HashOf<R::BridgedChain>)
|
||||
where
|
||||
R: pallet_bridge_grandpa::Config<GI>,
|
||||
GI: 'static,
|
||||
R::BridgedChain: bp_runtime::Chain,
|
||||
{
|
||||
let bridged_block_number = Zero::zero();
|
||||
let bridged_header = bp_runtime::HeaderOf::<R::BridgedChain>::new(
|
||||
bridged_block_number,
|
||||
Default::default(),
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let bridged_header_hash = bridged_header.hash();
|
||||
pallet_bridge_grandpa::initialize_for_benchmarks::<R, GI>(bridged_header);
|
||||
(bridged_block_number, bridged_header_hash)
|
||||
}
|
||||
|
||||
/// Insert header to the bridge parachains pallet.
|
||||
pub(crate) fn insert_header_to_parachains_pallet<R, PI, PC>(
|
||||
state_root: bp_runtime::HashOf<PC>,
|
||||
) -> (bp_runtime::BlockNumberOf<PC>, bp_runtime::HashOf<PC>)
|
||||
where
|
||||
R: pallet_bridge_parachains::Config<PI>,
|
||||
PI: 'static,
|
||||
PC: Chain<Hash = ParaHash> + Parachain,
|
||||
{
|
||||
let bridged_block_number = Zero::zero();
|
||||
let bridged_header = bp_runtime::HeaderOf::<PC>::new(
|
||||
bridged_block_number,
|
||||
Default::default(),
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let bridged_header_hash = bridged_header.hash();
|
||||
pallet_bridge_parachains::initialize_for_benchmarks::<R, PI, PC>(bridged_header);
|
||||
(bridged_block_number, bridged_header_hash)
|
||||
}
|
||||
|
||||
/// Returns callback which generates `BridgeMessage` from Polkadot XCM builder based on
|
||||
/// `expected_message_size` for benchmark.
|
||||
pub fn generate_xcm_builder_bridge_message_sample(
|
||||
destination: InteriorLocation,
|
||||
) -> impl Fn(usize) -> MessagePayload {
|
||||
move |expected_message_size| -> MessagePayload {
|
||||
// For XCM bridge hubs, it is the message that
|
||||
// will be pushed further to some XCM queue (XCMP/UMP)
|
||||
let location = xcm::VersionedInteriorLocation::V4(destination.clone());
|
||||
let location_encoded_size = location.encoded_size();
|
||||
|
||||
// we don't need to be super-precise with `expected_size` here
|
||||
let xcm_size = expected_message_size.saturating_sub(location_encoded_size);
|
||||
let xcm_data_size = xcm_size.saturating_sub(
|
||||
// minus empty instruction size
|
||||
Instruction::<()>::ExpectPallet {
|
||||
index: 0,
|
||||
name: vec![],
|
||||
module_name: vec![],
|
||||
crate_major: 0,
|
||||
min_crate_minor: 0,
|
||||
}
|
||||
.encoded_size(),
|
||||
);
|
||||
|
||||
log::trace!(
|
||||
target: "runtime::bridge-benchmarks",
|
||||
"generate_xcm_builder_bridge_message_sample with expected_message_size: {}, location_encoded_size: {}, xcm_size: {}, xcm_data_size: {}",
|
||||
expected_message_size, location_encoded_size, xcm_size, xcm_data_size,
|
||||
);
|
||||
|
||||
let xcm = xcm::VersionedXcm::<()>::V4(
|
||||
vec![Instruction::<()>::ExpectPallet {
|
||||
index: 0,
|
||||
name: vec![42; xcm_data_size],
|
||||
module_name: vec![],
|
||||
crate_major: 0,
|
||||
min_crate_minor: 0,
|
||||
}]
|
||||
.into(),
|
||||
);
|
||||
|
||||
// this is the `BridgeMessage` from polkadot xcm builder, but it has no constructor
|
||||
// or public fields, so just tuple
|
||||
// (double encoding, because `.encode()` is called on original Xcm BLOB when it is pushed
|
||||
// to the storage)
|
||||
(location, xcm).encode().encode()
|
||||
}
|
||||
}
|
||||
@@ -1,692 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Signed extension for the `pallet-bridge-messages` that is able to reject obsolete
|
||||
//! (and some other invalid) transactions.
|
||||
|
||||
use crate::messages::{
|
||||
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
|
||||
};
|
||||
use bp_messages::{target_chain::MessageDispatch, InboundLaneData, LaneId, MessageNonce};
|
||||
use bp_runtime::OwnedBridgeModule;
|
||||
use frame_support::{
|
||||
dispatch::CallableCallFor,
|
||||
traits::{Get, IsSubType},
|
||||
};
|
||||
use pallet_bridge_messages::{Config, Pallet};
|
||||
use sp_runtime::{transaction_validity::TransactionValidity, RuntimeDebug};
|
||||
use sp_std::ops::RangeInclusive;
|
||||
|
||||
/// Generic info about a messages delivery/confirmation proof.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub struct BaseMessagesProofInfo {
|
||||
/// Message lane, used by the call.
|
||||
pub lane_id: LaneId,
|
||||
/// Nonces of messages, included in the call.
|
||||
///
|
||||
/// For delivery transaction, it is nonces of bundled messages. For confirmation
|
||||
/// transaction, it is nonces that are to be confirmed during the call.
|
||||
pub bundled_range: RangeInclusive<MessageNonce>,
|
||||
/// Nonce of the best message, stored by this chain before the call is dispatched.
|
||||
///
|
||||
/// For delivery transaction, it is the nonce of best delivered message before the call.
|
||||
/// For confirmation transaction, it is the nonce of best confirmed message before the call.
|
||||
pub best_stored_nonce: MessageNonce,
|
||||
}
|
||||
|
||||
impl BaseMessagesProofInfo {
|
||||
/// Returns true if `bundled_range` continues the `0..=best_stored_nonce` range.
|
||||
fn appends_to_stored_nonce(&self) -> bool {
|
||||
Some(*self.bundled_range.start()) == self.best_stored_nonce.checked_add(1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Occupation state of the unrewarded relayers vector.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
#[cfg_attr(test, derive(Default))]
|
||||
pub struct UnrewardedRelayerOccupation {
|
||||
/// The number of remaining unoccupied entries for new relayers.
|
||||
pub free_relayer_slots: MessageNonce,
|
||||
/// The number of messages that we are ready to accept.
|
||||
pub free_message_slots: MessageNonce,
|
||||
}
|
||||
|
||||
/// Info about a `ReceiveMessagesProof` call which tries to update a single lane.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub struct ReceiveMessagesProofInfo {
|
||||
/// Base messages proof info
|
||||
pub base: BaseMessagesProofInfo,
|
||||
/// State of unrewarded relayers vector.
|
||||
pub unrewarded_relayers: UnrewardedRelayerOccupation,
|
||||
}
|
||||
|
||||
impl ReceiveMessagesProofInfo {
|
||||
/// Returns true if:
|
||||
///
|
||||
/// - either inbound lane is ready to accept bundled messages;
|
||||
///
|
||||
/// - or there are no bundled messages, but the inbound lane is blocked by too many unconfirmed
|
||||
/// messages and/or unrewarded relayers.
|
||||
fn is_obsolete(&self, is_dispatcher_active: bool) -> bool {
|
||||
// if dispatcher is inactive, we don't accept any delivery transactions
|
||||
if !is_dispatcher_active {
|
||||
return true
|
||||
}
|
||||
|
||||
// transactions with zero bundled nonces are not allowed, unless they're message
|
||||
// delivery transactions, which brings reward confirmations required to unblock
|
||||
// the lane
|
||||
if self.base.bundled_range.is_empty() {
|
||||
let empty_transactions_allowed =
|
||||
// we allow empty transactions when we can't accept delivery from new relayers
|
||||
self.unrewarded_relayers.free_relayer_slots == 0 ||
|
||||
// or if we can't accept new messages at all
|
||||
self.unrewarded_relayers.free_message_slots == 0;
|
||||
|
||||
return !empty_transactions_allowed
|
||||
}
|
||||
|
||||
// otherwise we require bundled messages to continue stored range
|
||||
!self.base.appends_to_stored_nonce()
|
||||
}
|
||||
}
|
||||
|
||||
/// Info about a `ReceiveMessagesDeliveryProof` call which tries to update a single lane.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub struct ReceiveMessagesDeliveryProofInfo(pub BaseMessagesProofInfo);
|
||||
|
||||
impl ReceiveMessagesDeliveryProofInfo {
|
||||
/// Returns true if outbound lane is ready to accept confirmations of bundled messages.
|
||||
fn is_obsolete(&self) -> bool {
|
||||
self.0.bundled_range.is_empty() || !self.0.appends_to_stored_nonce()
|
||||
}
|
||||
}
|
||||
|
||||
/// Info about a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call
|
||||
/// which tries to update a single lane.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub enum CallInfo {
|
||||
/// Messages delivery call info.
|
||||
ReceiveMessagesProof(ReceiveMessagesProofInfo),
|
||||
/// Messages delivery confirmation call info.
|
||||
ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo),
|
||||
}
|
||||
|
||||
impl CallInfo {
|
||||
/// Returns range of messages, bundled with the call.
|
||||
pub fn bundled_messages(&self) -> RangeInclusive<MessageNonce> {
|
||||
match *self {
|
||||
Self::ReceiveMessagesProof(ref info) => info.base.bundled_range.clone(),
|
||||
Self::ReceiveMessagesDeliveryProof(ref info) => info.0.bundled_range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct that provides methods for working with a call supported by `CallInfo`.
|
||||
pub struct CallHelper<T: Config<I>, I: 'static> {
|
||||
_phantom_data: sp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> CallHelper<T, I> {
|
||||
/// Returns true if:
|
||||
///
|
||||
/// - call is `receive_messages_proof` and all messages have been delivered;
|
||||
///
|
||||
/// - call is `receive_messages_delivery_proof` and all messages confirmations have been
|
||||
/// received.
|
||||
pub fn was_successful(info: &CallInfo) -> bool {
|
||||
match info {
|
||||
CallInfo::ReceiveMessagesProof(info) => {
|
||||
let inbound_lane_data =
|
||||
pallet_bridge_messages::InboundLanes::<T, I>::get(info.base.lane_id);
|
||||
if info.base.bundled_range.is_empty() {
|
||||
let post_occupation =
|
||||
unrewarded_relayers_occupation::<T, I>(&inbound_lane_data);
|
||||
// we don't care about `free_relayer_slots` here - it is checked in
|
||||
// `is_obsolete` and every relayer has delivered at least one message,
|
||||
// so if relayer slots are released, then message slots are also
|
||||
// released
|
||||
return post_occupation.free_message_slots >
|
||||
info.unrewarded_relayers.free_message_slots
|
||||
}
|
||||
|
||||
inbound_lane_data.last_delivered_nonce() == *info.base.bundled_range.end()
|
||||
},
|
||||
CallInfo::ReceiveMessagesDeliveryProof(info) => {
|
||||
let outbound_lane_data =
|
||||
pallet_bridge_messages::OutboundLanes::<T, I>::get(info.0.lane_id);
|
||||
outbound_lane_data.latest_received_nonce == *info.0.bundled_range.end()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing a call that is a sub type of `pallet_bridge_messages::Call`.
|
||||
pub trait MessagesCallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||
IsSubType<CallableCallFor<Pallet<T, I>, T>>
|
||||
{
|
||||
/// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call.
|
||||
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo>;
|
||||
|
||||
/// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from
|
||||
/// a `ReceiveMessagesDeliveryProof` call.
|
||||
fn receive_messages_delivery_proof_info(&self) -> Option<ReceiveMessagesDeliveryProofInfo>;
|
||||
|
||||
/// Create a new instance of `CallInfo` from a `ReceiveMessagesProof`
|
||||
/// or a `ReceiveMessagesDeliveryProof` call.
|
||||
fn call_info(&self) -> Option<CallInfo>;
|
||||
|
||||
/// Create a new instance of `CallInfo` from a `ReceiveMessagesProof`
|
||||
/// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane.
|
||||
fn call_info_for(&self, lane_id: LaneId) -> Option<CallInfo>;
|
||||
|
||||
/// Ensures that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call:
|
||||
///
|
||||
/// - does not deliver already delivered messages. We require all messages in the
|
||||
/// `ReceiveMessagesProof` call to be undelivered;
|
||||
///
|
||||
/// - does not submit empty `ReceiveMessagesProof` call with zero messages, unless the lane
|
||||
/// needs to be unblocked by providing relayer rewards proof;
|
||||
///
|
||||
/// - brings no new delivery confirmations in a `ReceiveMessagesDeliveryProof` call. We require
|
||||
/// at least one new delivery confirmation in the unrewarded relayers set;
|
||||
///
|
||||
/// - does not violate some basic (easy verifiable) messages pallet rules obsolete (like
|
||||
/// submitting a call when a pallet is halted or delivering messages when a dispatcher is
|
||||
/// inactive).
|
||||
///
|
||||
/// If one of above rules is violated, the transaction is treated as invalid.
|
||||
fn check_obsolete_call(&self) -> TransactionValidity;
|
||||
}
|
||||
|
||||
impl<
|
||||
BridgedHeaderHash,
|
||||
SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain<
|
||||
MessagesProof = FromBridgedChainMessagesProof<BridgedHeaderHash>,
|
||||
>,
|
||||
TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain<
|
||||
<T as Config<I>>::OutboundPayload,
|
||||
<T as frame_system::Config>::AccountId,
|
||||
MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash>,
|
||||
>,
|
||||
Call: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
|
||||
T: frame_system::Config<RuntimeCall = Call>
|
||||
+ Config<I, SourceHeaderChain = SourceHeaderChain, TargetHeaderChain = TargetHeaderChain>,
|
||||
I: 'static,
|
||||
> MessagesCallSubType<T, I> for T::RuntimeCall
|
||||
{
|
||||
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo> {
|
||||
if let Some(pallet_bridge_messages::Call::<T, I>::receive_messages_proof {
|
||||
ref proof,
|
||||
..
|
||||
}) = self.is_sub_type()
|
||||
{
|
||||
let inbound_lane_data = pallet_bridge_messages::InboundLanes::<T, I>::get(proof.lane);
|
||||
|
||||
return Some(ReceiveMessagesProofInfo {
|
||||
base: BaseMessagesProofInfo {
|
||||
lane_id: proof.lane,
|
||||
// we want all messages in this range to be new for us. Otherwise transaction
|
||||
// will be considered obsolete.
|
||||
bundled_range: proof.nonces_start..=proof.nonces_end,
|
||||
best_stored_nonce: inbound_lane_data.last_delivered_nonce(),
|
||||
},
|
||||
unrewarded_relayers: unrewarded_relayers_occupation::<T, I>(&inbound_lane_data),
|
||||
})
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof_info(&self) -> Option<ReceiveMessagesDeliveryProofInfo> {
|
||||
if let Some(pallet_bridge_messages::Call::<T, I>::receive_messages_delivery_proof {
|
||||
ref proof,
|
||||
ref relayers_state,
|
||||
..
|
||||
}) = self.is_sub_type()
|
||||
{
|
||||
let outbound_lane_data = pallet_bridge_messages::OutboundLanes::<T, I>::get(proof.lane);
|
||||
|
||||
return Some(ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
|
||||
lane_id: proof.lane,
|
||||
// there's a time frame between message delivery, message confirmation and reward
|
||||
// confirmation. Because of that, we can't assume that our state has been confirmed
|
||||
// to the bridged chain. So we are accepting any proof that brings new
|
||||
// confirmations.
|
||||
bundled_range: outbound_lane_data.latest_received_nonce + 1..=
|
||||
relayers_state.last_delivered_nonce,
|
||||
best_stored_nonce: outbound_lane_data.latest_received_nonce,
|
||||
}))
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn call_info(&self) -> Option<CallInfo> {
|
||||
if let Some(info) = self.receive_messages_proof_info() {
|
||||
return Some(CallInfo::ReceiveMessagesProof(info))
|
||||
}
|
||||
|
||||
if let Some(info) = self.receive_messages_delivery_proof_info() {
|
||||
return Some(CallInfo::ReceiveMessagesDeliveryProof(info))
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn call_info_for(&self, lane_id: LaneId) -> Option<CallInfo> {
|
||||
self.call_info().filter(|info| {
|
||||
let actual_lane_id = match info {
|
||||
CallInfo::ReceiveMessagesProof(info) => info.base.lane_id,
|
||||
CallInfo::ReceiveMessagesDeliveryProof(info) => info.0.lane_id,
|
||||
};
|
||||
actual_lane_id == lane_id
|
||||
})
|
||||
}
|
||||
|
||||
fn check_obsolete_call(&self) -> TransactionValidity {
|
||||
let is_pallet_halted = Pallet::<T, I>::ensure_not_halted().is_err();
|
||||
match self.call_info() {
|
||||
Some(proof_info) if is_pallet_halted => {
|
||||
log::trace!(
|
||||
target: pallet_bridge_messages::LOG_TARGET,
|
||||
"Rejecting messages transaction on halted pallet: {:?}",
|
||||
proof_info
|
||||
);
|
||||
|
||||
return sp_runtime::transaction_validity::InvalidTransaction::Call.into()
|
||||
},
|
||||
Some(CallInfo::ReceiveMessagesProof(proof_info))
|
||||
if proof_info.is_obsolete(T::MessageDispatch::is_active()) =>
|
||||
{
|
||||
log::trace!(
|
||||
target: pallet_bridge_messages::LOG_TARGET,
|
||||
"Rejecting obsolete messages delivery transaction: {:?}",
|
||||
proof_info
|
||||
);
|
||||
|
||||
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
|
||||
},
|
||||
Some(CallInfo::ReceiveMessagesDeliveryProof(proof_info))
|
||||
if proof_info.is_obsolete() =>
|
||||
{
|
||||
log::trace!(
|
||||
target: pallet_bridge_messages::LOG_TARGET,
|
||||
"Rejecting obsolete messages confirmation transaction: {:?}",
|
||||
proof_info,
|
||||
);
|
||||
|
||||
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Ok(sp_runtime::transaction_validity::ValidTransaction::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns occupation state of unrewarded relayers vector.
|
||||
fn unrewarded_relayers_occupation<T: Config<I>, I: 'static>(
|
||||
inbound_lane_data: &InboundLaneData<T::InboundRelayer>,
|
||||
) -> UnrewardedRelayerOccupation {
|
||||
UnrewardedRelayerOccupation {
|
||||
free_relayer_slots: T::MaxUnrewardedRelayerEntriesAtInboundLane::get()
|
||||
.saturating_sub(inbound_lane_data.relayers.len() as MessageNonce),
|
||||
free_message_slots: {
|
||||
let unconfirmed_messages = inbound_lane_data
|
||||
.last_delivered_nonce()
|
||||
.saturating_sub(inbound_lane_data.last_confirmed_nonce);
|
||||
T::MaxUnconfirmedMessagesAtInboundLane::get().saturating_sub(unconfirmed_messages)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
messages::{
|
||||
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
|
||||
},
|
||||
messages_call_ext::MessagesCallSubType,
|
||||
mock::{
|
||||
DummyMessageDispatch, MaxUnconfirmedMessagesAtInboundLane,
|
||||
MaxUnrewardedRelayerEntriesAtInboundLane, TestRuntime, ThisChainRuntimeCall,
|
||||
},
|
||||
};
|
||||
use bp_messages::{DeliveredMessages, UnrewardedRelayer, UnrewardedRelayersState};
|
||||
use sp_std::ops::RangeInclusive;
|
||||
|
||||
fn fill_unrewarded_relayers() {
|
||||
let mut inbound_lane_state =
|
||||
pallet_bridge_messages::InboundLanes::<TestRuntime>::get(LaneId([0, 0, 0, 0]));
|
||||
for n in 0..MaxUnrewardedRelayerEntriesAtInboundLane::get() {
|
||||
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
|
||||
relayer: Default::default(),
|
||||
messages: DeliveredMessages { begin: n + 1, end: n + 1 },
|
||||
});
|
||||
}
|
||||
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(
|
||||
LaneId([0, 0, 0, 0]),
|
||||
inbound_lane_state,
|
||||
);
|
||||
}
|
||||
|
||||
fn fill_unrewarded_messages() {
|
||||
let mut inbound_lane_state =
|
||||
pallet_bridge_messages::InboundLanes::<TestRuntime>::get(LaneId([0, 0, 0, 0]));
|
||||
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
|
||||
relayer: Default::default(),
|
||||
messages: DeliveredMessages {
|
||||
begin: 1,
|
||||
end: MaxUnconfirmedMessagesAtInboundLane::get(),
|
||||
},
|
||||
});
|
||||
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(
|
||||
LaneId([0, 0, 0, 0]),
|
||||
inbound_lane_state,
|
||||
);
|
||||
}
|
||||
|
||||
fn deliver_message_10() {
|
||||
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(
|
||||
LaneId([0, 0, 0, 0]),
|
||||
bp_messages::InboundLaneData { relayers: Default::default(), last_confirmed_nonce: 10 },
|
||||
);
|
||||
}
|
||||
|
||||
fn validate_message_delivery(
|
||||
nonces_start: bp_messages::MessageNonce,
|
||||
nonces_end: bp_messages::MessageNonce,
|
||||
) -> bool {
|
||||
ThisChainRuntimeCall::BridgeMessages(
|
||||
pallet_bridge_messages::Call::<TestRuntime, ()>::receive_messages_proof {
|
||||
relayer_id_at_bridged_chain: 42,
|
||||
messages_count: nonces_end.checked_sub(nonces_start).map(|x| x + 1).unwrap_or(0)
|
||||
as u32,
|
||||
dispatch_weight: frame_support::weights::Weight::zero(),
|
||||
proof: FromBridgedChainMessagesProof {
|
||||
bridged_header_hash: Default::default(),
|
||||
storage_proof: vec![],
|
||||
lane: LaneId([0, 0, 0, 0]),
|
||||
nonces_start,
|
||||
nonces_end,
|
||||
},
|
||||
},
|
||||
)
|
||||
.check_obsolete_call()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_obsolete_messages() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver messages 8..=9
|
||||
// => tx is rejected
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(8, 9));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_same_message() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
// when current best delivered is message#10 and we're trying to import messages 10..=10
|
||||
// => tx is rejected
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(8, 10));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_call_with_some_obsolete_messages() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver messages
|
||||
// 10..=15 => tx is rejected
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(10, 15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_call_with_future_messages() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver messages
|
||||
// 13..=15 => tx is rejected
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(13, 15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_reject_call_when_dispatcher_is_inactive() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver message 11..=15
|
||||
// => tx is accepted, but we have inactive dispatcher, so...
|
||||
deliver_message_10();
|
||||
|
||||
DummyMessageDispatch::deactivate();
|
||||
assert!(!validate_message_delivery(11, 15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_empty_delivery_with_rewards_confirmations_if_there_are_free_relayer_and_message_slots(
|
||||
) {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(10, 9));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_relayer_slots(
|
||||
) {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
deliver_message_10();
|
||||
fill_unrewarded_relayers();
|
||||
assert!(validate_message_delivery(10, 9));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_message_slots(
|
||||
) {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
fill_unrewarded_messages();
|
||||
assert!(validate_message_delivery(
|
||||
MaxUnconfirmedMessagesAtInboundLane::get(),
|
||||
MaxUnconfirmedMessagesAtInboundLane::get() - 1
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_messages() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver message 11..=15
|
||||
// => tx is accepted
|
||||
deliver_message_10();
|
||||
assert!(validate_message_delivery(11, 15));
|
||||
});
|
||||
}
|
||||
|
||||
fn confirm_message_10() {
|
||||
pallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(
|
||||
LaneId([0, 0, 0, 0]),
|
||||
bp_messages::OutboundLaneData {
|
||||
oldest_unpruned_nonce: 0,
|
||||
latest_received_nonce: 10,
|
||||
latest_generated_nonce: 10,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn validate_message_confirmation(last_delivered_nonce: bp_messages::MessageNonce) -> bool {
|
||||
ThisChainRuntimeCall::BridgeMessages(
|
||||
pallet_bridge_messages::Call::<TestRuntime>::receive_messages_delivery_proof {
|
||||
proof: FromBridgedChainMessagesDeliveryProof {
|
||||
bridged_header_hash: Default::default(),
|
||||
storage_proof: Vec::new(),
|
||||
lane: LaneId([0, 0, 0, 0]),
|
||||
},
|
||||
relayers_state: UnrewardedRelayersState {
|
||||
last_delivered_nonce,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
)
|
||||
.check_obsolete_call()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_obsolete_confirmations() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
// when current best confirmed is message#10 and we're trying to confirm message#5 => tx
|
||||
// is rejected
|
||||
confirm_message_10();
|
||||
assert!(!validate_message_confirmation(5));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_same_confirmation() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
// when current best confirmed is message#10 and we're trying to confirm message#10 =>
|
||||
// tx is rejected
|
||||
confirm_message_10();
|
||||
assert!(!validate_message_confirmation(10));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_empty_confirmation_even_if_there_are_no_free_unrewarded_entries() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
confirm_message_10();
|
||||
fill_unrewarded_relayers();
|
||||
assert!(!validate_message_confirmation(10));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_confirmation() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
// when current best confirmed is message#10 and we're trying to confirm message#15 =>
|
||||
// tx is accepted
|
||||
confirm_message_10();
|
||||
assert!(validate_message_confirmation(15));
|
||||
});
|
||||
}
|
||||
|
||||
fn was_message_delivery_successful(
|
||||
bundled_range: RangeInclusive<MessageNonce>,
|
||||
is_empty: bool,
|
||||
) -> bool {
|
||||
CallHelper::<TestRuntime, ()>::was_successful(&CallInfo::ReceiveMessagesProof(
|
||||
ReceiveMessagesProofInfo {
|
||||
base: BaseMessagesProofInfo {
|
||||
lane_id: LaneId([0, 0, 0, 0]),
|
||||
bundled_range,
|
||||
best_stored_nonce: 0, // doesn't matter for `was_successful`
|
||||
},
|
||||
unrewarded_relayers: UnrewardedRelayerOccupation {
|
||||
free_relayer_slots: 0, // doesn't matter for `was_successful`
|
||||
free_message_slots: if is_empty {
|
||||
0
|
||||
} else {
|
||||
MaxUnconfirmedMessagesAtInboundLane::get()
|
||||
},
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
fn was_successful_returns_false_for_failed_reward_confirmation_transaction() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
fill_unrewarded_messages();
|
||||
assert!(!was_message_delivery_successful(10..=9, true));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
fn was_successful_returns_true_for_successful_reward_confirmation_transaction() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
assert!(was_message_delivery_successful(10..=9, true));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_failed_delivery() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
deliver_message_10();
|
||||
assert!(!was_message_delivery_successful(10..=12, false));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_partially_successful_delivery() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
deliver_message_10();
|
||||
assert!(!was_message_delivery_successful(9..=12, false));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_true_for_successful_delivery() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
deliver_message_10();
|
||||
assert!(was_message_delivery_successful(9..=10, false));
|
||||
});
|
||||
}
|
||||
|
||||
fn was_message_confirmation_successful(bundled_range: RangeInclusive<MessageNonce>) -> bool {
|
||||
CallHelper::<TestRuntime, ()>::was_successful(&CallInfo::ReceiveMessagesDeliveryProof(
|
||||
ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
|
||||
lane_id: LaneId([0, 0, 0, 0]),
|
||||
bundled_range,
|
||||
best_stored_nonce: 0, // doesn't matter for `was_successful`
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_failed_confirmation() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
confirm_message_10();
|
||||
assert!(!was_message_confirmation_successful(10..=12));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_partially_successful_confirmation() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
confirm_message_10();
|
||||
assert!(!was_message_confirmation_successful(9..=12));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_true_for_successful_confirmation() {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
confirm_message_10();
|
||||
assert!(was_message_confirmation_successful(9..=10));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helpers for generating message storage proofs, that are used by tests and by benchmarks.
|
||||
|
||||
use crate::messages::{AccountIdOf, BridgedChain, HashOf, HasherOf, MessageBridge, ThisChain};
|
||||
|
||||
use bp_messages::{
|
||||
storage_keys, InboundLaneData, LaneId, MessageKey, MessageNonce, MessagePayload,
|
||||
OutboundLaneData,
|
||||
};
|
||||
use bp_runtime::{record_all_trie_keys, RawStorageProof, StorageProofSize};
|
||||
use codec::Encode;
|
||||
use sp_std::{ops::RangeInclusive, prelude::*};
|
||||
use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
|
||||
|
||||
/// Simple and correct message data encode function.
|
||||
pub fn encode_all_messages(_: MessageNonce, m: &MessagePayload) -> Option<Vec<u8>> {
|
||||
Some(m.encode())
|
||||
}
|
||||
|
||||
/// Simple and correct outbound lane data encode function.
|
||||
pub fn encode_lane_data(d: &OutboundLaneData) -> Vec<u8> {
|
||||
d.encode()
|
||||
}
|
||||
|
||||
/// Prepare storage proof of given messages.
|
||||
///
|
||||
/// Returns state trie root and nodes with prepared messages.
|
||||
pub fn prepare_messages_storage_proof<B>(
|
||||
lane: LaneId,
|
||||
message_nonces: RangeInclusive<MessageNonce>,
|
||||
outbound_lane_data: Option<OutboundLaneData>,
|
||||
size: StorageProofSize,
|
||||
message_payload: MessagePayload,
|
||||
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
|
||||
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
|
||||
) -> (HashOf<BridgedChain<B>>, RawStorageProof)
|
||||
where
|
||||
B: MessageBridge,
|
||||
HashOf<BridgedChain<B>>: Copy + Default,
|
||||
{
|
||||
// prepare Bridged chain storage with messages and (optionally) outbound lane state
|
||||
let message_count = message_nonces.end().saturating_sub(*message_nonces.start()) + 1;
|
||||
let mut storage_keys = Vec::with_capacity(message_count as usize + 1);
|
||||
let mut root = Default::default();
|
||||
let mut mdb = MemoryDB::default();
|
||||
{
|
||||
let mut trie =
|
||||
TrieDBMutBuilderV1::<HasherOf<BridgedChain<B>>>::new(&mut mdb, &mut root).build();
|
||||
|
||||
// insert messages
|
||||
for (i, nonce) in message_nonces.into_iter().enumerate() {
|
||||
let message_key = MessageKey { lane_id: lane, nonce };
|
||||
let message_payload = match encode_message(nonce, &message_payload) {
|
||||
Some(message_payload) =>
|
||||
if i == 0 {
|
||||
grow_trie_leaf_value(message_payload, size)
|
||||
} else {
|
||||
message_payload
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
let storage_key = storage_keys::message_key(
|
||||
B::BRIDGED_MESSAGES_PALLET_NAME,
|
||||
&message_key.lane_id,
|
||||
message_key.nonce,
|
||||
)
|
||||
.0;
|
||||
trie.insert(&storage_key, &message_payload)
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
storage_keys.push(storage_key);
|
||||
}
|
||||
|
||||
// insert outbound lane state
|
||||
if let Some(outbound_lane_data) = outbound_lane_data.as_ref().map(encode_outbound_lane_data)
|
||||
{
|
||||
let storage_key =
|
||||
storage_keys::outbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, &lane).0;
|
||||
trie.insert(&storage_key, &outbound_lane_data)
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
storage_keys.push(storage_key);
|
||||
}
|
||||
}
|
||||
|
||||
// generate storage proof to be delivered to This chain
|
||||
let storage_proof = record_all_trie_keys::<LayoutV1<HasherOf<BridgedChain<B>>>, _>(&mdb, &root)
|
||||
.map_err(|_| "record_all_trie_keys has failed")
|
||||
.expect("record_all_trie_keys should not fail in benchmarks");
|
||||
(root, storage_proof)
|
||||
}
|
||||
|
||||
/// Prepare storage proof of given messages delivery.
|
||||
///
|
||||
/// Returns state trie root and nodes with prepared messages.
|
||||
pub fn prepare_message_delivery_storage_proof<B>(
|
||||
lane: LaneId,
|
||||
inbound_lane_data: InboundLaneData<AccountIdOf<ThisChain<B>>>,
|
||||
size: StorageProofSize,
|
||||
) -> (HashOf<BridgedChain<B>>, RawStorageProof)
|
||||
where
|
||||
B: MessageBridge,
|
||||
{
|
||||
// prepare Bridged chain storage with inbound lane state
|
||||
let storage_key = storage_keys::inbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, &lane).0;
|
||||
let mut root = Default::default();
|
||||
let mut mdb = MemoryDB::default();
|
||||
{
|
||||
let mut trie =
|
||||
TrieDBMutBuilderV1::<HasherOf<BridgedChain<B>>>::new(&mut mdb, &mut root).build();
|
||||
let inbound_lane_data = grow_trie_leaf_value(inbound_lane_data.encode(), size);
|
||||
trie.insert(&storage_key, &inbound_lane_data)
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
}
|
||||
|
||||
// generate storage proof to be delivered to This chain
|
||||
let storage_proof = record_all_trie_keys::<LayoutV1<HasherOf<BridgedChain<B>>>, _>(&mdb, &root)
|
||||
.map_err(|_| "record_all_trie_keys has failed")
|
||||
.expect("record_all_trie_keys should not fail in benchmarks");
|
||||
|
||||
(root, storage_proof)
|
||||
}
|
||||
|
||||
/// Add extra data to the trie leaf value so that it'll be of given size.
|
||||
pub fn grow_trie_leaf_value(mut value: Vec<u8>, size: StorageProofSize) -> Vec<u8> {
|
||||
match size {
|
||||
StorageProofSize::Minimal(_) => (),
|
||||
StorageProofSize::HasLargeLeaf(size) if size as usize > value.len() => {
|
||||
value.extend(sp_std::iter::repeat(42u8).take(size as usize - value.len()));
|
||||
},
|
||||
StorageProofSize::HasLargeLeaf(_) => (),
|
||||
}
|
||||
value
|
||||
}
|
||||
@@ -1,502 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Module provides utilities for easier XCM handling, e.g:
|
||||
//! `XcmExecutor` -> `MessageSender` -> `OutboundMessageQueue`
|
||||
//! |
|
||||
//! `Relayer`
|
||||
//! |
|
||||
//! `XcmRouter` <- `MessageDispatch` <- `InboundMessageQueue`
|
||||
|
||||
use bp_messages::{
|
||||
source_chain::OnMessagesDelivered,
|
||||
target_chain::{DispatchMessage, MessageDispatch},
|
||||
LaneId, MessageNonce,
|
||||
};
|
||||
use bp_runtime::messages::MessageDispatchResult;
|
||||
pub use bp_xcm_bridge_hub::XcmAsPlainPayload;
|
||||
use bp_xcm_bridge_hub_router::XcmChannelStatusProvider;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{traits::Get, weights::Weight, CloneNoBound, EqNoBound, PartialEqNoBound};
|
||||
use pallet_bridge_messages::{
|
||||
Config as MessagesConfig, OutboundLanesCongestedSignals, WeightInfoExt as MessagesPalletWeights,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::SaturatedConversion;
|
||||
use sp_std::{fmt::Debug, marker::PhantomData};
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{DispatchBlob, DispatchBlobError};
|
||||
|
||||
/// Message dispatch result type for single message.
|
||||
#[derive(CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, Debug, TypeInfo)]
|
||||
pub enum XcmBlobMessageDispatchResult {
|
||||
/// We've been unable to decode message payload.
|
||||
InvalidPayload,
|
||||
/// Message has been dispatched.
|
||||
Dispatched,
|
||||
/// Message has **NOT** been dispatched because of given error.
|
||||
NotDispatched(#[codec(skip)] Option<DispatchBlobError>),
|
||||
}
|
||||
|
||||
/// [`XcmBlobMessageDispatch`] is responsible for dispatching received messages
|
||||
///
|
||||
/// It needs to be used at the target bridge hub.
|
||||
pub struct XcmBlobMessageDispatch<DispatchBlob, Weights, Channel> {
|
||||
_marker: sp_std::marker::PhantomData<(DispatchBlob, Weights, Channel)>,
|
||||
}
|
||||
|
||||
impl<
|
||||
BlobDispatcher: DispatchBlob,
|
||||
Weights: MessagesPalletWeights,
|
||||
Channel: XcmChannelStatusProvider,
|
||||
> MessageDispatch for XcmBlobMessageDispatch<BlobDispatcher, Weights, Channel>
|
||||
{
|
||||
type DispatchPayload = XcmAsPlainPayload;
|
||||
type DispatchLevelResult = XcmBlobMessageDispatchResult;
|
||||
|
||||
fn is_active() -> bool {
|
||||
!Channel::is_congested()
|
||||
}
|
||||
|
||||
fn dispatch_weight(message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
|
||||
match message.data.payload {
|
||||
Ok(ref payload) => {
|
||||
let payload_size = payload.encoded_size().saturated_into();
|
||||
Weights::message_dispatch_weight(payload_size)
|
||||
},
|
||||
Err(_) => Weight::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
message: DispatchMessage<Self::DispatchPayload>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
let payload = match message.data.payload {
|
||||
Ok(payload) => payload,
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
|
||||
"[XcmBlobMessageDispatch] payload error: {:?} - message_nonce: {:?}",
|
||||
e,
|
||||
message.key.nonce
|
||||
);
|
||||
return MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
|
||||
}
|
||||
},
|
||||
};
|
||||
let dispatch_level_result = match BlobDispatcher::dispatch_blob(payload) {
|
||||
Ok(_) => {
|
||||
log::debug!(
|
||||
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
|
||||
"[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob was ok - message_nonce: {:?}",
|
||||
message.key.nonce
|
||||
);
|
||||
XcmBlobMessageDispatchResult::Dispatched
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
|
||||
"[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?} - message_nonce: {:?}",
|
||||
e, message.key.nonce
|
||||
);
|
||||
XcmBlobMessageDispatchResult::NotDispatched(Some(e))
|
||||
},
|
||||
};
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result }
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair of sending chain location and message lane, used by this chain to send messages
|
||||
/// over the bridge.
|
||||
#[cfg_attr(feature = "std", derive(Debug, Eq, PartialEq))]
|
||||
pub struct SenderAndLane {
|
||||
/// Sending chain relative location.
|
||||
pub location: Location,
|
||||
/// Message lane, used by the sending chain.
|
||||
pub lane: LaneId,
|
||||
}
|
||||
|
||||
impl SenderAndLane {
|
||||
/// Create new object using provided location and lane.
|
||||
pub fn new(location: Location, lane: LaneId) -> Self {
|
||||
SenderAndLane { location, lane }
|
||||
}
|
||||
}
|
||||
|
||||
/// [`XcmBlobHauler`] is responsible for sending messages to the bridge "point-to-point link" from
|
||||
/// one side, where on the other it can be dispatched by [`XcmBlobMessageDispatch`].
|
||||
pub trait XcmBlobHauler {
|
||||
/// Runtime that has messages pallet deployed.
|
||||
type Runtime: MessagesConfig<Self::MessagesInstance>;
|
||||
/// Instance of the messages pallet that is used to send messages.
|
||||
type MessagesInstance: 'static;
|
||||
|
||||
/// Actual XCM message sender (`HRMP` or `UMP`) to the source chain
|
||||
/// location (`Self::SenderAndLane::get().location`).
|
||||
type ToSourceChainSender: SendXcm;
|
||||
/// An XCM message that is sent to the sending chain when the bridge queue becomes congested.
|
||||
type CongestedMessage: Get<Option<Xcm<()>>>;
|
||||
/// An XCM message that is sent to the sending chain when the bridge queue becomes not
|
||||
/// congested.
|
||||
type UncongestedMessage: Get<Option<Xcm<()>>>;
|
||||
|
||||
/// Returns `true` if we want to handle congestion.
|
||||
fn supports_congestion_detection() -> bool {
|
||||
Self::CongestedMessage::get().is_some() || Self::UncongestedMessage::get().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// XCM bridge adapter which connects [`XcmBlobHauler`] with [`pallet_bridge_messages`] and
|
||||
/// makes sure that XCM blob is sent to the outbound lane to be relayed.
|
||||
///
|
||||
/// It needs to be used at the source bridge hub.
|
||||
pub struct XcmBlobHaulerAdapter<XcmBlobHauler, Lanes>(
|
||||
sp_std::marker::PhantomData<(XcmBlobHauler, Lanes)>,
|
||||
);
|
||||
|
||||
impl<
|
||||
H: XcmBlobHauler,
|
||||
Lanes: Get<sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))>>,
|
||||
> OnMessagesDelivered for XcmBlobHaulerAdapter<H, Lanes>
|
||||
{
|
||||
fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) {
|
||||
if let Some(sender_and_lane) =
|
||||
Lanes::get().iter().find(|link| link.0.lane == lane).map(|link| &link.0)
|
||||
{
|
||||
// notify XCM queue manager about updated lane state
|
||||
LocalXcmQueueManager::<H>::on_bridge_messages_delivered(
|
||||
sender_and_lane,
|
||||
enqueued_messages,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manager of local XCM queues (and indirectly - underlying transport channels) that
|
||||
/// controls the queue state.
|
||||
///
|
||||
/// It needs to be used at the source bridge hub.
|
||||
pub struct LocalXcmQueueManager<H>(PhantomData<H>);
|
||||
|
||||
/// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we
|
||||
/// send a "congestion" XCM message to the sending chain.
|
||||
const OUTBOUND_LANE_CONGESTED_THRESHOLD: MessageNonce = 8_192;
|
||||
|
||||
/// After we have sent "congestion" XCM message to the sending chain, we wait until number
|
||||
/// of messages in the outbound bridge queue drops to this count, before sending `uncongestion`
|
||||
/// XCM message.
|
||||
const OUTBOUND_LANE_UNCONGESTED_THRESHOLD: MessageNonce = 1_024;
|
||||
|
||||
impl<H: XcmBlobHauler> LocalXcmQueueManager<H> {
|
||||
/// Must be called whenever we push a message to the bridge lane.
|
||||
pub fn on_bridge_message_enqueued(
|
||||
sender_and_lane: &SenderAndLane,
|
||||
enqueued_messages: MessageNonce,
|
||||
) {
|
||||
// skip if we dont want to handle congestion
|
||||
if !H::supports_congestion_detection() {
|
||||
return
|
||||
}
|
||||
|
||||
// if we have already sent the congestion signal, we don't want to do anything
|
||||
if Self::is_congested_signal_sent(sender_and_lane.lane) {
|
||||
return
|
||||
}
|
||||
|
||||
// if the bridge queue is not congested, we don't want to do anything
|
||||
let is_congested = enqueued_messages > OUTBOUND_LANE_CONGESTED_THRESHOLD;
|
||||
if !is_congested {
|
||||
return
|
||||
}
|
||||
|
||||
log::info!(
|
||||
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
|
||||
"Sending 'congested' XCM message to {:?} to avoid overloading lane {:?}: there are\
|
||||
{} messages queued at the bridge queue",
|
||||
sender_and_lane.location,
|
||||
sender_and_lane.lane,
|
||||
enqueued_messages,
|
||||
);
|
||||
|
||||
if let Err(e) = Self::send_congested_signal(sender_and_lane) {
|
||||
log::info!(
|
||||
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
|
||||
"Failed to send the 'congested' XCM message to {:?}: {:?}",
|
||||
sender_and_lane.location,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Must be called whenever we receive a message delivery confirmation.
|
||||
pub fn on_bridge_messages_delivered(
|
||||
sender_and_lane: &SenderAndLane,
|
||||
enqueued_messages: MessageNonce,
|
||||
) {
|
||||
// skip if we don't want to handle congestion
|
||||
if !H::supports_congestion_detection() {
|
||||
return
|
||||
}
|
||||
|
||||
// if we have not sent the congestion signal before, we don't want to do anything
|
||||
if !Self::is_congested_signal_sent(sender_and_lane.lane) {
|
||||
return
|
||||
}
|
||||
|
||||
// if the bridge queue is still congested, we don't want to do anything
|
||||
let is_congested = enqueued_messages > OUTBOUND_LANE_UNCONGESTED_THRESHOLD;
|
||||
if is_congested {
|
||||
return
|
||||
}
|
||||
|
||||
log::info!(
|
||||
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
|
||||
"Sending 'uncongested' XCM message to {:?}. Lane {:?}: there are\
|
||||
{} messages queued at the bridge queue",
|
||||
sender_and_lane.location,
|
||||
sender_and_lane.lane,
|
||||
enqueued_messages,
|
||||
);
|
||||
|
||||
if let Err(e) = Self::send_uncongested_signal(sender_and_lane) {
|
||||
log::info!(
|
||||
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
|
||||
"Failed to send the 'uncongested' XCM message to {:?}: {:?}",
|
||||
sender_and_lane.location,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if we have sent "congested" signal to the `sending_chain_location`.
|
||||
fn is_congested_signal_sent(lane: LaneId) -> bool {
|
||||
OutboundLanesCongestedSignals::<H::Runtime, H::MessagesInstance>::get(lane)
|
||||
}
|
||||
|
||||
/// Send congested signal to the `sending_chain_location`.
|
||||
fn send_congested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> {
|
||||
if let Some(msg) = H::CongestedMessage::get() {
|
||||
send_xcm::<H::ToSourceChainSender>(sender_and_lane.location.clone(), msg)?;
|
||||
OutboundLanesCongestedSignals::<H::Runtime, H::MessagesInstance>::insert(
|
||||
sender_and_lane.lane,
|
||||
true,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send `uncongested` signal to the `sending_chain_location`.
|
||||
fn send_uncongested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> {
|
||||
if let Some(msg) = H::UncongestedMessage::get() {
|
||||
send_xcm::<H::ToSourceChainSender>(sender_and_lane.location.clone(), msg)?;
|
||||
OutboundLanesCongestedSignals::<H::Runtime, H::MessagesInstance>::remove(
|
||||
sender_and_lane.lane,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapter for the implementation of `GetVersion`, which attempts to find the minimal
|
||||
/// configured XCM version between the destination `dest` and the bridge hub location provided as
|
||||
/// `Get<Location>`.
|
||||
pub struct XcmVersionOfDestAndRemoteBridge<Version, RemoteBridge>(
|
||||
sp_std::marker::PhantomData<(Version, RemoteBridge)>,
|
||||
);
|
||||
impl<Version: GetVersion, RemoteBridge: Get<Location>> GetVersion
|
||||
for XcmVersionOfDestAndRemoteBridge<Version, RemoteBridge>
|
||||
{
|
||||
fn get_version_for(dest: &Location) -> Option<XcmVersion> {
|
||||
let dest_version = Version::get_version_for(dest);
|
||||
let bridge_hub_version = Version::get_version_for(&RemoteBridge::get());
|
||||
|
||||
match (dest_version, bridge_hub_version) {
|
||||
(Some(dv), Some(bhv)) => Some(sp_std::cmp::min(dv, bhv)),
|
||||
(Some(dv), None) => Some(dv),
|
||||
(None, Some(bhv)) => Some(bhv),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::*;
|
||||
|
||||
use bp_messages::OutboundLaneData;
|
||||
use frame_support::parameter_types;
|
||||
use pallet_bridge_messages::OutboundLanes;
|
||||
|
||||
parameter_types! {
|
||||
pub TestSenderAndLane: SenderAndLane = SenderAndLane {
|
||||
location: Location::new(1, [Parachain(1000)]),
|
||||
lane: TEST_LANE_ID,
|
||||
};
|
||||
pub TestLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))> = sp_std::vec![
|
||||
(TestSenderAndLane::get(), (NetworkId::ByGenesis([0; 32]), InteriorLocation::Here))
|
||||
];
|
||||
pub DummyXcmMessage: Xcm<()> = Xcm::new();
|
||||
}
|
||||
|
||||
struct DummySendXcm;
|
||||
|
||||
impl DummySendXcm {
|
||||
fn messages_sent() -> u32 {
|
||||
frame_support::storage::unhashed::get(b"DummySendXcm").unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SendXcm for DummySendXcm {
|
||||
type Ticket = ();
|
||||
|
||||
fn validate(
|
||||
_destination: &mut Option<Location>,
|
||||
_message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
Ok(((), Default::default()))
|
||||
}
|
||||
|
||||
fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
let messages_sent: u32 = Self::messages_sent();
|
||||
frame_support::storage::unhashed::put(b"DummySendXcm", &(messages_sent + 1));
|
||||
Ok(XcmHash::default())
|
||||
}
|
||||
}
|
||||
|
||||
struct TestBlobHauler;
|
||||
|
||||
impl XcmBlobHauler for TestBlobHauler {
|
||||
type Runtime = TestRuntime;
|
||||
type MessagesInstance = ();
|
||||
|
||||
type ToSourceChainSender = DummySendXcm;
|
||||
type CongestedMessage = DummyXcmMessage;
|
||||
type UncongestedMessage = DummyXcmMessage;
|
||||
}
|
||||
|
||||
type TestBlobHaulerAdapter = XcmBlobHaulerAdapter<TestBlobHauler, TestLanes>;
|
||||
|
||||
fn fill_up_lane_to_congestion() -> MessageNonce {
|
||||
let latest_generated_nonce = OUTBOUND_LANE_CONGESTED_THRESHOLD;
|
||||
OutboundLanes::<TestRuntime, ()>::insert(
|
||||
TEST_LANE_ID,
|
||||
OutboundLaneData {
|
||||
oldest_unpruned_nonce: 0,
|
||||
latest_received_nonce: 0,
|
||||
latest_generated_nonce,
|
||||
},
|
||||
);
|
||||
latest_generated_nonce
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn congested_signal_is_not_sent_twice() {
|
||||
run_test(|| {
|
||||
let enqueued = fill_up_lane_to_congestion();
|
||||
|
||||
// next sent message leads to congested signal
|
||||
LocalXcmQueueManager::<TestBlobHauler>::on_bridge_message_enqueued(
|
||||
&TestSenderAndLane::get(),
|
||||
enqueued + 1,
|
||||
);
|
||||
assert_eq!(DummySendXcm::messages_sent(), 1);
|
||||
|
||||
// next sent message => we don't sent another congested signal
|
||||
LocalXcmQueueManager::<TestBlobHauler>::on_bridge_message_enqueued(
|
||||
&TestSenderAndLane::get(),
|
||||
enqueued,
|
||||
);
|
||||
assert_eq!(DummySendXcm::messages_sent(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn congested_signal_is_not_sent_when_outbound_lane_is_not_congested() {
|
||||
run_test(|| {
|
||||
LocalXcmQueueManager::<TestBlobHauler>::on_bridge_message_enqueued(
|
||||
&TestSenderAndLane::get(),
|
||||
1,
|
||||
);
|
||||
assert_eq!(DummySendXcm::messages_sent(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn congested_signal_is_sent_when_outbound_lane_is_congested() {
|
||||
run_test(|| {
|
||||
let enqueued = fill_up_lane_to_congestion();
|
||||
|
||||
// next sent message leads to congested signal
|
||||
LocalXcmQueueManager::<TestBlobHauler>::on_bridge_message_enqueued(
|
||||
&TestSenderAndLane::get(),
|
||||
enqueued + 1,
|
||||
);
|
||||
assert_eq!(DummySendXcm::messages_sent(), 1);
|
||||
assert!(LocalXcmQueueManager::<TestBlobHauler>::is_congested_signal_sent(TEST_LANE_ID));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncongested_signal_is_not_sent_when_messages_are_delivered_at_other_lane() {
|
||||
run_test(|| {
|
||||
LocalXcmQueueManager::<TestBlobHauler>::send_congested_signal(&TestSenderAndLane::get()).unwrap();
|
||||
assert_eq!(DummySendXcm::messages_sent(), 1);
|
||||
|
||||
// when we receive a delivery report for other lane, we don't send an uncongested signal
|
||||
TestBlobHaulerAdapter::on_messages_delivered(LaneId([42, 42, 42, 42]), 0);
|
||||
assert_eq!(DummySendXcm::messages_sent(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncongested_signal_is_not_sent_when_we_havent_send_congested_signal_before() {
|
||||
run_test(|| {
|
||||
TestBlobHaulerAdapter::on_messages_delivered(TEST_LANE_ID, 0);
|
||||
assert_eq!(DummySendXcm::messages_sent(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncongested_signal_is_not_sent_if_outbound_lane_is_still_congested() {
|
||||
run_test(|| {
|
||||
LocalXcmQueueManager::<TestBlobHauler>::send_congested_signal(&TestSenderAndLane::get()).unwrap();
|
||||
assert_eq!(DummySendXcm::messages_sent(), 1);
|
||||
|
||||
TestBlobHaulerAdapter::on_messages_delivered(
|
||||
TEST_LANE_ID,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1,
|
||||
);
|
||||
assert_eq!(DummySendXcm::messages_sent(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncongested_signal_is_sent_if_outbound_lane_is_uncongested() {
|
||||
run_test(|| {
|
||||
LocalXcmQueueManager::<TestBlobHauler>::send_congested_signal(&TestSenderAndLane::get()).unwrap();
|
||||
assert_eq!(DummySendXcm::messages_sent(), 1);
|
||||
|
||||
TestBlobHaulerAdapter::on_messages_delivered(
|
||||
TEST_LANE_ID,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
|
||||
);
|
||||
assert_eq!(DummySendXcm::messages_sent(), 2);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A mock runtime for testing different stuff in the crate.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::messages::{
|
||||
source::{
|
||||
FromThisChainMaximalOutboundPayloadSize, FromThisChainMessagePayload,
|
||||
TargetHeaderChainAdapter,
|
||||
},
|
||||
target::{FromBridgedChainMessagePayload, SourceHeaderChainAdapter},
|
||||
BridgedChainWithMessages, HashOf, MessageBridge, ThisChainWithMessages,
|
||||
};
|
||||
|
||||
use bp_header_chain::{ChainWithGrandpa, HeaderChain};
|
||||
use bp_messages::{
|
||||
target_chain::{DispatchMessage, MessageDispatch},
|
||||
LaneId, MessageNonce,
|
||||
};
|
||||
use bp_parachains::SingleParaStoredHeaderDataBuilder;
|
||||
use bp_relayers::PayRewardFromAccount;
|
||||
use bp_runtime::{
|
||||
messages::MessageDispatchResult, Chain, ChainId, Parachain, UnderlyingChainProvider,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight},
|
||||
};
|
||||
use pallet_transaction_payment::Multiplier;
|
||||
use sp_runtime::{
|
||||
testing::H256,
|
||||
traits::{BlakeTwo256, ConstU32, ConstU64, ConstU8},
|
||||
FixedPointNumber, Perquintill,
|
||||
};
|
||||
|
||||
/// Account identifier at `ThisChain`.
|
||||
pub type ThisChainAccountId = u64;
|
||||
/// Balance at `ThisChain`.
|
||||
pub type ThisChainBalance = u64;
|
||||
/// Block number at `ThisChain`.
|
||||
pub type ThisChainBlockNumber = u32;
|
||||
/// Hash at `ThisChain`.
|
||||
pub type ThisChainHash = H256;
|
||||
/// Hasher at `ThisChain`.
|
||||
pub type ThisChainHasher = BlakeTwo256;
|
||||
/// Runtime call at `ThisChain`.
|
||||
pub type ThisChainRuntimeCall = RuntimeCall;
|
||||
/// Runtime call origin at `ThisChain`.
|
||||
pub type ThisChainCallOrigin = RuntimeOrigin;
|
||||
/// Header of `ThisChain`.
|
||||
pub type ThisChainHeader = sp_runtime::generic::Header<ThisChainBlockNumber, ThisChainHasher>;
|
||||
/// Block of `ThisChain`.
|
||||
pub type ThisChainBlock = frame_system::mocking::MockBlockU32<TestRuntime>;
|
||||
|
||||
/// Account identifier at the `BridgedChain`.
|
||||
pub type BridgedChainAccountId = u128;
|
||||
/// Balance at the `BridgedChain`.
|
||||
pub type BridgedChainBalance = u128;
|
||||
/// Block number at the `BridgedChain`.
|
||||
pub type BridgedChainBlockNumber = u32;
|
||||
/// Hash at the `BridgedChain`.
|
||||
pub type BridgedChainHash = H256;
|
||||
/// Hasher at the `BridgedChain`.
|
||||
pub type BridgedChainHasher = BlakeTwo256;
|
||||
/// Header of the `BridgedChain`.
|
||||
pub type BridgedChainHeader =
|
||||
sp_runtime::generic::Header<BridgedChainBlockNumber, BridgedChainHasher>;
|
||||
|
||||
/// Rewards payment procedure.
|
||||
pub type TestPaymentProcedure = PayRewardFromAccount<Balances, ThisChainAccountId>;
|
||||
/// Stake that we are using in tests.
|
||||
pub type TestStake = ConstU64<5_000>;
|
||||
/// Stake and slash mechanism to use in tests.
|
||||
pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed<
|
||||
ThisChainAccountId,
|
||||
ThisChainBlockNumber,
|
||||
Balances,
|
||||
ReserveId,
|
||||
TestStake,
|
||||
ConstU32<8>,
|
||||
>;
|
||||
|
||||
/// Message lane used in tests.
|
||||
pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 0]);
|
||||
/// Bridged chain id used in tests.
|
||||
pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg";
|
||||
/// Maximal extrinsic weight at the `BridgedChain`.
|
||||
pub const BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT: usize = 2048;
|
||||
/// Maximal extrinsic size at the `BridgedChain`.
|
||||
pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024;
|
||||
|
||||
frame_support::construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
|
||||
Utility: pallet_utility,
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
|
||||
BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
|
||||
BridgeGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage, Event<T>},
|
||||
BridgeParachains: pallet_bridge_parachains::{Pallet, Call, Storage, Event<T>},
|
||||
BridgeMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
|
||||
}
|
||||
}
|
||||
|
||||
crate::generate_bridge_reject_obsolete_headers_and_messages! {
|
||||
ThisChainRuntimeCall, ThisChainAccountId,
|
||||
BridgeGrandpa, BridgeParachains, BridgeMessages
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID];
|
||||
pub const BridgedChainId: ChainId = TEST_BRIDGED_CHAIN_ID;
|
||||
pub const BridgedParasPalletName: &'static str = "Paras";
|
||||
pub const ExistentialDeposit: ThisChainBalance = 500;
|
||||
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
|
||||
pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25);
|
||||
pub const TransactionBaseFee: ThisChainBalance = 0;
|
||||
pub const TransactionByteFee: ThisChainBalance = 1;
|
||||
pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000);
|
||||
pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128);
|
||||
pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value();
|
||||
pub const MaxUnrewardedRelayerEntriesAtInboundLane: MessageNonce = 16;
|
||||
pub const MaxUnconfirmedMessagesAtInboundLane: MessageNonce = 1_000;
|
||||
pub const ReserveId: [u8; 8] = *b"brdgrlrs";
|
||||
}
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for TestRuntime {
|
||||
type Hash = ThisChainHash;
|
||||
type Hashing = ThisChainHasher;
|
||||
type AccountId = ThisChainAccountId;
|
||||
type Block = ThisChainBlock;
|
||||
type AccountData = pallet_balances::AccountData<ThisChainBalance>;
|
||||
type BlockHashCount = ConstU32<250>;
|
||||
}
|
||||
|
||||
impl pallet_utility::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type PalletsOrigin = OriginCaller;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pallet_balances::Config for TestRuntime {
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)]
|
||||
impl pallet_transaction_payment::Config for TestRuntime {
|
||||
type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter<Balances, ()>;
|
||||
type OperationalFeeMultiplier = ConstU8<5>;
|
||||
type WeightToFee = IdentityFee<ThisChainBalance>;
|
||||
type LengthToFee = ConstantMultiplier<ThisChainBalance, TransactionByteFee>;
|
||||
type FeeMultiplierUpdate = pallet_transaction_payment::TargetedFeeAdjustment<
|
||||
TestRuntime,
|
||||
TargetBlockFullness,
|
||||
AdjustmentVariable,
|
||||
MinimumMultiplier,
|
||||
MaximumMultiplier,
|
||||
>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
impl pallet_bridge_grandpa::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = BridgedUnderlyingChain;
|
||||
type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>;
|
||||
type HeadersToKeep = ConstU32<8>;
|
||||
type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
|
||||
}
|
||||
|
||||
impl pallet_bridge_parachains::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgesGrandpaPalletInstance = ();
|
||||
type ParasPalletName = BridgedParasPalletName;
|
||||
type ParaStoredHeaderDataBuilder =
|
||||
SingleParaStoredHeaderDataBuilder<BridgedUnderlyingParachain>;
|
||||
type HeadsToKeep = ConstU32<8>;
|
||||
type MaxParaHeadDataSize = ConstU32<1024>;
|
||||
type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight<TestRuntime>;
|
||||
}
|
||||
|
||||
impl pallet_bridge_messages::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = pallet_bridge_messages::weights::BridgeWeight<TestRuntime>;
|
||||
type ActiveOutboundLanes = ActiveOutboundLanes;
|
||||
type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane;
|
||||
type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane;
|
||||
|
||||
type MaximalOutboundPayloadSize = FromThisChainMaximalOutboundPayloadSize<OnThisChainBridge>;
|
||||
type OutboundPayload = FromThisChainMessagePayload;
|
||||
|
||||
type InboundPayload = FromBridgedChainMessagePayload;
|
||||
type InboundRelayer = BridgedChainAccountId;
|
||||
type DeliveryPayments = ();
|
||||
|
||||
type TargetHeaderChain = TargetHeaderChainAdapter<OnThisChainBridge>;
|
||||
type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter<
|
||||
TestRuntime,
|
||||
(),
|
||||
ConstU64<100_000>,
|
||||
>;
|
||||
type OnMessagesDelivered = ();
|
||||
|
||||
type SourceHeaderChain = SourceHeaderChainAdapter<OnThisChainBridge>;
|
||||
type MessageDispatch = DummyMessageDispatch;
|
||||
type BridgedChainId = BridgedChainId;
|
||||
}
|
||||
|
||||
impl pallet_bridge_relayers::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Reward = ThisChainBalance;
|
||||
type PaymentProcedure = TestPaymentProcedure;
|
||||
type StakeAndSlash = TestStakeAndSlash;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
/// Dummy message dispatcher.
|
||||
pub struct DummyMessageDispatch;
|
||||
|
||||
impl DummyMessageDispatch {
|
||||
pub fn deactivate() {
|
||||
frame_support::storage::unhashed::put(&b"inactive"[..], &false);
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDispatch for DummyMessageDispatch {
|
||||
type DispatchPayload = Vec<u8>;
|
||||
type DispatchLevelResult = ();
|
||||
|
||||
fn is_active() -> bool {
|
||||
frame_support::storage::unhashed::take::<bool>(&b"inactive"[..]) != Some(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(_message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
_: DispatchMessage<Self::DispatchPayload>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
|
||||
}
|
||||
}
|
||||
|
||||
/// Bridge that is deployed on `ThisChain` and allows sending/receiving messages to/from
|
||||
/// `BridgedChain`.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct OnThisChainBridge;
|
||||
|
||||
impl MessageBridge for OnThisChainBridge {
|
||||
const BRIDGED_MESSAGES_PALLET_NAME: &'static str = "";
|
||||
|
||||
type ThisChain = ThisChain;
|
||||
type BridgedChain = BridgedChain;
|
||||
type BridgedHeaderChain = pallet_bridge_grandpa::GrandpaChainHeaders<TestRuntime, ()>;
|
||||
}
|
||||
|
||||
/// Bridge that is deployed on `BridgedChain` and allows sending/receiving messages to/from
|
||||
/// `ThisChain`.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct OnBridgedChainBridge;
|
||||
|
||||
impl MessageBridge for OnBridgedChainBridge {
|
||||
const BRIDGED_MESSAGES_PALLET_NAME: &'static str = "";
|
||||
|
||||
type ThisChain = BridgedChain;
|
||||
type BridgedChain = ThisChain;
|
||||
type BridgedHeaderChain = ThisHeaderChain;
|
||||
}
|
||||
|
||||
/// Dummy implementation of `HeaderChain` for `ThisChain` at the `BridgedChain`.
|
||||
pub struct ThisHeaderChain;
|
||||
|
||||
impl HeaderChain<ThisUnderlyingChain> for ThisHeaderChain {
|
||||
fn finalized_header_state_root(_hash: HashOf<ThisChain>) -> Option<HashOf<ThisChain>> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Call origin at `BridgedChain`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BridgedChainOrigin;
|
||||
|
||||
impl From<BridgedChainOrigin>
|
||||
for Result<frame_system::RawOrigin<BridgedChainAccountId>, BridgedChainOrigin>
|
||||
{
|
||||
fn from(
|
||||
_origin: BridgedChainOrigin,
|
||||
) -> Result<frame_system::RawOrigin<BridgedChainAccountId>, BridgedChainOrigin> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Underlying chain of `ThisChain`.
|
||||
pub struct ThisUnderlyingChain;
|
||||
|
||||
impl Chain for ThisUnderlyingChain {
|
||||
const ID: ChainId = *b"tuch";
|
||||
|
||||
type BlockNumber = ThisChainBlockNumber;
|
||||
type Hash = ThisChainHash;
|
||||
type Hasher = ThisChainHasher;
|
||||
type Header = ThisChainHeader;
|
||||
type AccountId = ThisChainAccountId;
|
||||
type Balance = ThisChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = sp_runtime::MultiSignature;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
/// The chain where we are in tests.
|
||||
pub struct ThisChain;
|
||||
|
||||
impl UnderlyingChainProvider for ThisChain {
|
||||
type Chain = ThisUnderlyingChain;
|
||||
}
|
||||
|
||||
impl ThisChainWithMessages for ThisChain {
|
||||
type RuntimeOrigin = ThisChainCallOrigin;
|
||||
}
|
||||
|
||||
impl BridgedChainWithMessages for ThisChain {}
|
||||
|
||||
/// Underlying chain of `BridgedChain`.
|
||||
pub struct BridgedUnderlyingChain;
|
||||
/// Some parachain under `BridgedChain` consensus.
|
||||
pub struct BridgedUnderlyingParachain;
|
||||
/// Runtime call of the `BridgedChain`.
|
||||
#[derive(Decode, Encode)]
|
||||
pub struct BridgedChainCall;
|
||||
|
||||
impl Chain for BridgedUnderlyingChain {
|
||||
const ID: ChainId = *b"buch";
|
||||
|
||||
type BlockNumber = BridgedChainBlockNumber;
|
||||
type Hash = BridgedChainHash;
|
||||
type Hasher = BridgedChainHasher;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = BridgedChainAccountId;
|
||||
type Balance = BridgedChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = sp_runtime::MultiSignature;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for BridgedUnderlyingChain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = 16;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 64;
|
||||
}
|
||||
|
||||
impl Chain for BridgedUnderlyingParachain {
|
||||
const ID: ChainId = *b"bupc";
|
||||
|
||||
type BlockNumber = BridgedChainBlockNumber;
|
||||
type Hash = BridgedChainHash;
|
||||
type Hasher = BridgedChainHasher;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = BridgedChainAccountId;
|
||||
type Balance = BridgedChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = sp_runtime::MultiSignature;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Parachain for BridgedUnderlyingParachain {
|
||||
const PARACHAIN_ID: u32 = 42;
|
||||
}
|
||||
|
||||
/// The other, bridged chain, used in tests.
|
||||
pub struct BridgedChain;
|
||||
|
||||
impl UnderlyingChainProvider for BridgedChain {
|
||||
type Chain = BridgedUnderlyingChain;
|
||||
}
|
||||
|
||||
impl ThisChainWithMessages for BridgedChain {
|
||||
type RuntimeOrigin = BridgedChainOrigin;
|
||||
}
|
||||
|
||||
impl BridgedChainWithMessages for BridgedChain {}
|
||||
|
||||
/// Run test within test externalities.
|
||||
pub fn run_test(test: impl FnOnce()) {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(test)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Everything required to run benchmarks of parachains finality module.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::{
|
||||
messages_benchmarking::insert_header_to_grandpa_pallet,
|
||||
messages_generation::grow_trie_leaf_value,
|
||||
};
|
||||
|
||||
use bp_parachains::parachain_head_storage_key_at_source;
|
||||
use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
|
||||
use bp_runtime::{record_all_trie_keys, StorageProofSize};
|
||||
use codec::Encode;
|
||||
use frame_support::traits::Get;
|
||||
use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
|
||||
use sp_std::prelude::*;
|
||||
use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
|
||||
|
||||
/// Prepare proof of messages for the `receive_messages_proof` call.
|
||||
///
|
||||
/// In addition to returning valid messages proof, environment is prepared to verify this message
|
||||
/// proof.
|
||||
pub fn prepare_parachain_heads_proof<R, PI>(
|
||||
parachains: &[ParaId],
|
||||
parachain_head_size: u32,
|
||||
size: StorageProofSize,
|
||||
) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>)
|
||||
where
|
||||
R: pallet_bridge_parachains::Config<PI>
|
||||
+ pallet_bridge_grandpa::Config<R::BridgesGrandpaPalletInstance>,
|
||||
PI: 'static,
|
||||
<R as pallet_bridge_grandpa::Config<R::BridgesGrandpaPalletInstance>>::BridgedChain:
|
||||
bp_runtime::Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash>,
|
||||
{
|
||||
let parachain_head = ParaHead(vec![0u8; parachain_head_size as usize]);
|
||||
|
||||
// insert all heads to the trie
|
||||
let mut parachain_heads = Vec::with_capacity(parachains.len());
|
||||
let mut storage_keys = Vec::with_capacity(parachains.len());
|
||||
let mut state_root = Default::default();
|
||||
let mut mdb = MemoryDB::default();
|
||||
{
|
||||
let mut trie =
|
||||
TrieDBMutBuilderV1::<RelayBlockHasher>::new(&mut mdb, &mut state_root).build();
|
||||
|
||||
// insert parachain heads
|
||||
for (i, parachain) in parachains.into_iter().enumerate() {
|
||||
let storage_key =
|
||||
parachain_head_storage_key_at_source(R::ParasPalletName::get(), *parachain);
|
||||
let leaf_data = if i == 0 {
|
||||
grow_trie_leaf_value(parachain_head.encode(), size)
|
||||
} else {
|
||||
parachain_head.encode()
|
||||
};
|
||||
trie.insert(&storage_key.0, &leaf_data)
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
storage_keys.push(storage_key);
|
||||
parachain_heads.push((*parachain, parachain_head.hash()))
|
||||
}
|
||||
}
|
||||
|
||||
// generate heads storage proof
|
||||
let proof = record_all_trie_keys::<LayoutV1<RelayBlockHasher>, _>(&mdb, &state_root)
|
||||
.map_err(|_| "record_all_trie_keys has failed")
|
||||
.expect("record_all_trie_keys should not fail in benchmarks");
|
||||
|
||||
let (relay_block_number, relay_block_hash) =
|
||||
insert_header_to_grandpa_pallet::<R, R::BridgesGrandpaPalletInstance>(state_root);
|
||||
|
||||
(relay_block_number, relay_block_hash, ParaHeadsProof { storage_proof: proof }, parachain_heads)
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Bridge transaction priority calculator.
|
||||
//!
|
||||
//! We want to prioritize message delivery transactions with more messages over
|
||||
//! transactions with less messages. That's because we reject delivery transactions
|
||||
//! if it contains already delivered message. And if some transaction delivers
|
||||
//! single message with nonce `N`, then the transaction with nonces `N..=N+100` will
|
||||
//! be rejected. This can lower bridge throughput down to one message per block.
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use frame_support::traits::Get;
|
||||
use sp_runtime::transaction_validity::TransactionPriority;
|
||||
|
||||
// reexport everything from `integrity_tests` module
|
||||
#[allow(unused_imports)]
|
||||
pub use integrity_tests::*;
|
||||
|
||||
/// Compute priority boost for message delivery transaction that delivers
|
||||
/// given number of messages.
|
||||
pub fn compute_priority_boost<PriorityBoostPerMessage>(
|
||||
messages: MessageNonce,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
PriorityBoostPerMessage: Get<TransactionPriority>,
|
||||
{
|
||||
// we don't want any boost for transaction with single message => minus one
|
||||
PriorityBoostPerMessage::get().saturating_mul(messages.saturating_sub(1))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "integrity-test"))]
|
||||
mod integrity_tests {}
|
||||
|
||||
#[cfg(feature = "integrity-test")]
|
||||
mod integrity_tests {
|
||||
use super::compute_priority_boost;
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bp_runtime::PreComputedSize;
|
||||
use frame_support::{
|
||||
dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo},
|
||||
traits::Get,
|
||||
};
|
||||
use pallet_bridge_messages::WeightInfoExt;
|
||||
use pallet_transaction_payment::OnChargeTransaction;
|
||||
use sp_runtime::{
|
||||
traits::{Dispatchable, UniqueSaturatedInto, Zero},
|
||||
transaction_validity::TransactionPriority,
|
||||
FixedPointOperand, SaturatedConversion, Saturating,
|
||||
};
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<
|
||||
T,
|
||||
>>::Balance;
|
||||
|
||||
/// Ensures that the value of `PriorityBoostPerMessage` matches the value of
|
||||
/// `tip_boost_per_message`.
|
||||
///
|
||||
/// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have almost
|
||||
/// the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want to be sure
|
||||
/// that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the priority will be close
|
||||
/// to `TX2` as well.
|
||||
pub fn ensure_priority_boost_is_sane<Runtime, MessagesInstance, PriorityBoostPerMessage>(
|
||||
tip_boost_per_message: BalanceOf<Runtime>,
|
||||
) where
|
||||
Runtime:
|
||||
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
|
||||
MessagesInstance: 'static,
|
||||
PriorityBoostPerMessage: Get<TransactionPriority>,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
let priority_boost_per_message = PriorityBoostPerMessage::get();
|
||||
let maximal_messages_in_delivery_transaction =
|
||||
Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
|
||||
for messages in 1..=maximal_messages_in_delivery_transaction {
|
||||
let base_priority = estimate_message_delivery_transaction_priority::<
|
||||
Runtime,
|
||||
MessagesInstance,
|
||||
>(messages, Zero::zero());
|
||||
let priority_boost = compute_priority_boost::<PriorityBoostPerMessage>(messages);
|
||||
let priority_with_boost = base_priority + priority_boost;
|
||||
|
||||
let tip = tip_boost_per_message.saturating_mul((messages - 1).unique_saturated_into());
|
||||
let priority_with_tip =
|
||||
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(1, tip);
|
||||
|
||||
const ERROR_MARGIN: TransactionPriority = 5; // 5%
|
||||
if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) /
|
||||
priority_with_tip >
|
||||
ERROR_MARGIN
|
||||
{
|
||||
panic!(
|
||||
"The PriorityBoostPerMessage value ({}) must be fixed to: {}",
|
||||
priority_boost_per_message,
|
||||
compute_priority_boost_per_message::<Runtime, MessagesInstance>(
|
||||
tip_boost_per_message
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute priority boost that we give to message delivery transaction for additional message.
|
||||
#[cfg(feature = "integrity-test")]
|
||||
fn compute_priority_boost_per_message<Runtime, MessagesInstance>(
|
||||
tip_boost_per_message: BalanceOf<Runtime>,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
Runtime:
|
||||
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
|
||||
MessagesInstance: 'static,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// estimate priority of transaction that delivers one message and has large tip
|
||||
let maximal_messages_in_delivery_transaction =
|
||||
Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
|
||||
let small_with_tip_priority =
|
||||
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(
|
||||
1,
|
||||
tip_boost_per_message
|
||||
.saturating_mul(maximal_messages_in_delivery_transaction.saturated_into()),
|
||||
);
|
||||
// estimate priority of transaction that delivers maximal number of messages, but has no tip
|
||||
let large_without_tip_priority = estimate_message_delivery_transaction_priority::<
|
||||
Runtime,
|
||||
MessagesInstance,
|
||||
>(maximal_messages_in_delivery_transaction, Zero::zero());
|
||||
|
||||
small_with_tip_priority
|
||||
.saturating_sub(large_without_tip_priority)
|
||||
.saturating_div(maximal_messages_in_delivery_transaction - 1)
|
||||
}
|
||||
|
||||
/// Estimate message delivery transaction priority.
|
||||
#[cfg(feature = "integrity-test")]
|
||||
fn estimate_message_delivery_transaction_priority<Runtime, MessagesInstance>(
|
||||
messages: MessageNonce,
|
||||
tip: BalanceOf<Runtime>,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
Runtime:
|
||||
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
|
||||
MessagesInstance: 'static,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// just an estimation of extra transaction bytes that are added to every transaction
|
||||
// (including signature, signed extensions extra and etc + in our case it includes
|
||||
// all call arguments except the proof itself)
|
||||
let base_tx_size = 512;
|
||||
// let's say we are relaying similar small messages and for every message we add more trie
|
||||
// nodes to the proof (x0.5 because we expect some nodes to be reused)
|
||||
let estimated_message_size = 512;
|
||||
// let's say all our messages have the same dispatch weight
|
||||
let estimated_message_dispatch_weight =
|
||||
Runtime::WeightInfo::message_dispatch_weight(estimated_message_size);
|
||||
// messages proof argument size is (for every message) messages size + some additional
|
||||
// trie nodes. Some of them are reused by different messages, so let's take 2/3 of default
|
||||
// "overhead" constant
|
||||
let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size()
|
||||
.saturating_mul(2)
|
||||
.saturating_div(3)
|
||||
.saturating_add(estimated_message_size)
|
||||
.saturating_mul(messages as _);
|
||||
|
||||
// finally we are able to estimate transaction size and weight
|
||||
let transaction_size = base_tx_size.saturating_add(messages_proof_size);
|
||||
let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight(
|
||||
&PreComputedSize(transaction_size as _),
|
||||
messages as _,
|
||||
estimated_message_dispatch_weight.saturating_mul(messages),
|
||||
);
|
||||
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
|
||||
&DispatchInfo {
|
||||
weight: transaction_weight,
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
},
|
||||
transaction_size as _,
|
||||
tip,
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user