Initial version of bridging pallets as git subtree (#2458)

* Initial version of bridges pallet as subtree of https://github.com/paritytech/parity-bridges-common
Added `Bridges subtree files` pr review rule

* Squashed 'bridges/' content from commit d30927c08

git-subtree-dir: bridges
git-subtree-split: d30927c089bd9e73092d1ec1a62895603cb277a3

* Updated REAMDE.md and BRIDGES.md (inspired by original https://github.com/paritytech/polkadot/blob/d22eb62fe40e55e15eb91d375f48cc540d83a47e/BRIDGES.md)

* Squashed 'bridges/' changes from d30927c08..d3970944b

d3970944b Small simplifications (#2050)

git-subtree-dir: bridges
git-subtree-split: d3970944b0cfc4ea5226225e1ca07dab234c3556

* Squashed 'bridges/' changes from d3970944b..2180797fb

2180797fb Removed CODEOWNERS (#2051)

git-subtree-dir: bridges
git-subtree-split: 2180797fbf8a990490c67853dcffd81bc8dd083c

* Squashed 'bridges/' changes from 2180797fbf..4850aac8ce

4850aac8ce Removed relayer_account: &AccountId from MessageDispatch  (#2080)
8c8adafd54 Revert "Fix max-size messages at test chains (#2064)" (#2077)
c01a63efd8 Fixed off-by-one when confirming rewards in messages pallet (#2075)
a298be96aa Update subxt dependencies (#2072)
c0eef51eab Fix max-size messages at test chains (#2064)
3a658e3697 Messages relay fixes (#2073)
0022b5ab22 Slash relayers for invalid transactions (#2025)
198104007f Bump enumflags2 from 0.7.5 to 0.7.7
9229b257e5 [ci] Fix rules for docker build (#2069)
660d791390 [ci] Update buildah command and version (#2058)
e4535c0ca4 fix the way latest_confirmed_nonce_at_source is "calculated" (#2067)
dbc2d37590 select nothing if we have already selected nonces to submit or have submitted something (#2065)
a7eedd21fe [relay-substrate-client] Bump jsonrpsee (#2066)
8875d5aeae Bump clap from 4.2.2 to 4.2.4
25f9cf55e2 Another use of RangeInclusiveExt::checked_len() (#2060)
4942c12a5f submit lane unblock transactions from relay (#2030)
c0325d3c9c Test deployments fixes (#2057)
fc7b9b7ed7 Use the new matrix server (#2056)
63bcb5c10b Fixed delivery alert rule (#2052)

git-subtree-dir: bridges
git-subtree-split: 4850aac8ce6c34e5ca6246b88cd14c873a879cba

* Squashed 'bridges/' changes from 4850aac8ce..66aaf0dd23

66aaf0dd23 Nits (#2083)

git-subtree-dir: bridges
git-subtree-split: 66aaf0dd239dde40b64264061a77c921e2c82568

* Squashed 'bridges/' changes from 66aaf0dd23..557ecbcecc

557ecbcecc Fix sized messages (Follow-up on #2064) (#2103)
54f587a066 Add weight of refund extension post_dispatch to the weights of messages pallet (#2089)
5b1626f8c4 fix pallet param for nightly benchmarks check (#2099)
ae44c6b7a1 Add millau specific messages weights (#2097)
6ad0bd1f1e Add integrity tests to rialto parachain runtiime (#2096)
6919556de5 Bump tokio from 1.27.0 to 1.28.0
58795fcb75 Bump clap from 4.2.4 to 4.2.5
01bf31085b Bump scale-info from 2.5.0 to 2.6.0
8fe383240d Bump anyhow from 1.0.70 to 1.0.71
8d94e82ad5 deployments: add new BEEFY metrics and alarms (#2090)
e9a4749e7e Bump wasmtime from 6.0.1 to 6.0.2
9d9936c0d9 Bump wasmtime from 6.0.1 to 6.0.2 in /tools/runtime-codegen
5d77cd7bee Add more logs to relayer and message pallets (#2082)
75fbb9d3ef Update comment (#2081)
9904d09cf6 Benchmarks for new relayers pallet calls (#2040)

git-subtree-dir: bridges
git-subtree-split: 557ecbcecc585547b744a5ac9fb8d7f3b9de4521

* fmt

* Squashed 'bridges/' changes from 557ecbcecc..04b3dda6aa

04b3dda6aa Remove from subtree (#2111)
f8ff15e7e7 Add `MessagesPalletInstance` for integrity tests (#2107)
92ccef58e6 Use generated runtimes for BHR/BHW (#2106)
b33e0a585b Fix comment (#2105)

git-subtree-dir: bridges
git-subtree-split: 04b3dda6aa38599e612ff637710b6d2cff275ef3

* ".git/.scripts/commands/fmt/fmt.sh"

---------

Co-authored-by: parity-processbot <>
This commit is contained in:
Branislav Kontur
2023-05-04 08:36:58 +02:00
committed by GitHub
parent 587242ec2d
commit d810f65044
114 changed files with 26856 additions and 6 deletions
@@ -0,0 +1,92 @@
[package]
name = "bridge-runtime-common"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
repository = "https://github.com/paritytech/parity-bridges-common/"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] }
hash-db = { version = "0.16.0", default-features = false }
log = { version = "0.4.17", default-features = false }
scale-info = { version = "2.6.0", default-features = false, features = ["derive"] }
static_assertions = { version = "1.1", optional = true }
# Bridge dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-parachains = { path = "../../primitives/parachains", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-relayers = { path = "../../primitives/relayers", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false }
pallet-bridge-messages = { path = "../../modules/messages", default-features = false }
pallet-bridge-parachains = { path = "../../modules/parachains", default-features = false }
pallet-bridge-relayers = { path = "../../modules/relayers", default-features = false }
# Substrate dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
# Polkadot dependencies
pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false }
xcm = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false }
xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false }
xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "master", default-features = false }
[dev-dependencies]
bp-test-utils = { path = "../../primitives/test-utils" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-messages/std",
"bp-parachains/std",
"bp-polkadot-core/std",
"bp-runtime/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"hash-db/std",
"log/std",
"pallet-bridge-grandpa/std",
"pallet-bridge-messages/std",
"pallet-bridge-parachains/std",
"pallet-bridge-relayers/std",
"pallet-transaction-payment/std",
"pallet-utility/std",
"pallet-xcm/std",
"scale-info/std",
"sp-api/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
"xcm/std",
"xcm-builder/std",
"xcm-executor/std",
]
runtime-benchmarks = [
"pallet-bridge-grandpa/runtime-benchmarks",
"pallet-bridge-messages/runtime-benchmarks",
"pallet-bridge-parachains/runtime-benchmarks",
"pallet-xcm/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
]
integrity-test = [
"static_assertions",
]
@@ -0,0 +1,361 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! 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 _;
use sp_runtime::traits::SignedExtension;
/// Macro that ensures that the runtime configuration and chain primitives crate are sharing
/// the same types (index, 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;
use static_assertions::assert_type_eq_all;
assert_type_eq_all!(<$r as SystemConfig>::Index, bp_runtime::IndexOf<$this>);
assert_type_eq_all!(<$r as SystemConfig>::BlockNumber, 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!(<$r as SystemConfig>::Header, 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()
);
}
}
/// Check that the `AdditionalSigned` type of a wrapped runtime is the same as the one of the
/// corresponding actual runtime.
///
/// This method doesn't perform any `assert`. If the condition is not true it will generate a
/// compile-time error.
pub fn check_additional_signed<SignedExt, IndirectSignedExt: SignedExtension>()
where
SignedExt: SignedExtension,
IndirectSignedExt: SignedExtension<AdditionalSigned = SignedExt::AdditionalSigned>,
{
}
@@ -0,0 +1,259 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Common types/functions that may be used by runtimes of all bridged chains.
#![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;
use xcm::v3::NetworkId;
pub mod messages;
pub mod messages_api;
pub mod messages_benchmarking;
pub mod messages_call_ext;
pub mod messages_xcm_extension;
pub mod parachains_benchmarking;
pub mod priority_calculator;
pub mod refund_relayer_extension;
mod messages_generation;
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
/// BridgeRialtoGrandpa, BridgeWestendGrandpa,
/// BridgeRialtoParachains
/// }
/// ```
///
/// 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, frame_support::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)
}
}
};
}
/// A mapping over `NetworkId`.
/// Since `NetworkId` doesn't include `Millau`, `Rialto` and `RialtoParachain`, we create some
/// synthetic associations between these chains and `NetworkId` chains.
pub enum CustomNetworkId {
/// The Millau network ID, associated with Kusama.
Millau,
/// The Rialto network ID, associated with Polkadot.
Rialto,
/// The RialtoParachain network ID, associated with Westend.
RialtoParachain,
}
impl TryFrom<bp_runtime::ChainId> for CustomNetworkId {
type Error = ();
fn try_from(chain: bp_runtime::ChainId) -> Result<Self, Self::Error> {
Ok(match chain {
bp_runtime::MILLAU_CHAIN_ID => Self::Millau,
bp_runtime::RIALTO_CHAIN_ID => Self::Rialto,
bp_runtime::RIALTO_PARACHAIN_CHAIN_ID => Self::RialtoParachain,
_ => return Err(()),
})
}
}
impl CustomNetworkId {
/// Converts self to XCM' network id.
pub const fn as_network_id(&self) -> NetworkId {
match *self {
CustomNetworkId::Millau => NetworkId::Kusama,
CustomNetworkId::Rialto => NetworkId::Polkadot,
CustomNetworkId::RialtoParachain => NetworkId::Westend,
}
}
}
#[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() }
)
}
}
@@ -0,0 +1,779 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! 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, HeaderChainError};
use bp_messages::{
source_chain::{LaneMessageVerifier, TargetHeaderChain},
target_chain::{ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData,
};
use bp_runtime::{Chain, RawStorageProof, Size, StorageProofChecker, StorageProofError};
use codec::{Decode, Encode};
use frame_support::{traits::Get, weights::Weight, RuntimeDebug};
use hash_db::Hasher;
use scale_info::TypeInfo;
use sp_std::{convert::TryFrom, fmt::Debug, 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>>;
/// Type of origin that is used on the chain.
pub type OriginOf<C> = <C as ThisChainWithMessages>::RuntimeOrigin;
/// Error that happens during message verification.
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// The message proof is empty.
EmptyMessageProof,
/// Error returned by the bridged header chain.
HeaderChain(HeaderChainError),
/// Error returned while reading/decoding inbound lane data from the storage proof.
InboundLaneStorage(StorageProofError),
/// The declared message weight is incorrect.
InvalidMessageWeight,
/// Declared messages count doesn't match actual value.
MessagesCountMismatch,
/// Error returned while reading/decoding message data from the storage proof.
MessageStorage(StorageProofError),
/// The message is too large.
MessageTooLarge,
/// Error returned while reading/decoding outbound lane data from the storage proof.
OutboundLaneStorage(StorageProofError),
/// Storage proof related error.
StorageProof(StorageProofError),
}
/// 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>>>);
/// Message verifier that is doing all basic checks.
///
/// This verifier assumes following:
///
/// - all message lanes are equivalent, so all checks are the same;
///
/// Following checks are made:
///
/// - message is rejected if its lane is currently blocked;
/// - message is rejected if there are too many pending (undelivered) messages at the outbound
/// lane;
/// - check that the sender has rights to dispatch the call on target chain using provided
/// dispatch origin;
/// - check that the sender has paid enough funds for both message delivery and dispatch.
#[derive(RuntimeDebug)]
pub struct FromThisChainMessageVerifier<B>(PhantomData<B>);
impl<B> LaneMessageVerifier<OriginOf<ThisChain<B>>, FromThisChainMessagePayload>
for FromThisChainMessageVerifier<B>
where
B: MessageBridge,
// matches requirements from the `frame_system::Config::Origin`
OriginOf<ThisChain<B>>: Clone
+ Into<Result<frame_system::RawOrigin<AccountIdOf<ThisChain<B>>>, OriginOf<ThisChain<B>>>>,
AccountIdOf<ThisChain<B>>: PartialEq + Clone,
{
type Error = &'static str;
fn verify_message(
_submitter: &OriginOf<ThisChain<B>>,
_lane: &LaneId,
_lane_outbound_data: &OutboundLaneData,
_payload: &FromThisChainMessagePayload,
) -> Result<(), Self::Error> {
// 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).
Ok(())
}
}
/// 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 Error = Error;
type MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>;
fn verify_message(payload: &FromThisChainMessagePayload) -> Result<(), Self::Error> {
verify_chain_message::<B>(payload)
}
fn verify_messages_delivery_proof(
proof: Self::MessagesDeliveryProof,
) -> Result<(LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>), Self::Error> {
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. But the lane may have its own checks (e.g. fee
/// check) that would reject message (see `FromThisChainMessageVerifier`).
pub fn verify_chain_message<B: MessageBridge>(
payload: &FromThisChainMessagePayload,
) -> Result<(), Error> {
// 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 weight for this data.
if payload.len() > maximal_message_size::<B>() as usize {
return Err(Error::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>, Error> {
let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } =
proof;
B::BridgedHeaderChain::parse_finalized_storage_proof(
bridged_header_hash,
storage_proof,
|mut storage| {
// 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(Error::InboundLaneStorage)?;
// check that the storage proof doesn't have any untouched trie nodes
storage.ensure_no_unused_nodes().map_err(Error::StorageProof)?;
Ok((lane, inbound_lane_data))
},
)
.map_err(Error::HeaderChain)?
}
}
/// 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 Error = Error;
type MessagesProof = FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>;
fn verify_messages_proof(
proof: Self::MessagesProof,
messages_count: u32,
) -> Result<ProvedMessages<Message>, Self::Error> {
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>, Error> {
let FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane,
nonces_start,
nonces_end,
} = proof;
let nonces_range = nonces_start..=nonces_end;
B::BridgedHeaderChain::parse_finalized_storage_proof(
bridged_header_hash,
storage_proof,
|storage| {
let mut parser =
StorageProofCheckerAdapter::<_, B> { storage, _dummy: Default::default() };
// 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(Error::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(Error::EmptyMessageProof)
}
// check that the storage proof doesn't have any untouched trie nodes
parser.storage.ensure_no_unused_nodes().map_err(Error::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)
},
)
.map_err(Error::HeaderChain)?
}
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>, Error> {
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(Error::OutboundLaneStorage)
}
fn read_and_decode_message_payload(
&mut self,
message_key: &MessageKey,
) -> Result<MessagePayload, Error> {
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(Error::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::StoredHeaderDataBuilder;
use bp_runtime::HeaderId;
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(Error::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(Error::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(Error::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(Error::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(Error::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(Error::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(Error::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(Error::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(Error::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(Error::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(Error::MessagesCountMismatch),
);
}
}
@@ -0,0 +1,66 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Helpers for implementing various message-related runtime API mthods.
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()
}
@@ -0,0 +1,293 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! 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, HasherOf, MessageBridge, ThisChain,
},
messages_generation::{
encode_all_messages, encode_lane_data, grow_trie_leaf_value, prepare_messages_storage_proof,
},
};
use bp_messages::storage_keys;
use bp_polkadot_core::parachains::ParaHash;
use bp_runtime::{
record_all_trie_keys, Chain, Parachain, RawStorageProof, 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 sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
use xcm::v3::prelude::*;
/// Prepare inbound bridge message according to given message proof parameters.
fn prepare_inbound_message(
params: &MessageProofParams,
destination: InteriorMultiLocation,
) -> Vec<u8> {
// 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. For XCM bridge hubs, it is the message that
// will be pushed further to some XCM queue (XCMP/UMP)
let location = xcm::VersionedInteriorMultiLocation::V3(destination);
let location_encoded_size = location.encoded_size();
// we don't need to be super-precise with `expected_size` here
let xcm_size = expected_size.saturating_sub(location_encoded_size);
let xcm = xcm::VersionedXcm::<()>::V3(vec![Instruction::ClearOrigin; xcm_size].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()
}
/// 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_destination: InteriorMultiLocation,
) -> (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(&params, message_destination),
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_destination: InteriorMultiLocation,
) -> (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(&params, message_destination),
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_proof::<B>(params);
// 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_proof::<B>(params);
// 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,
}
}
/// Prepare in-memory message delivery proof, without inserting anything to the runtime storage.
fn prepare_message_delivery_proof<B>(
params: MessageDeliveryProofParams<AccountIdOf<ThisChain<B>>>,
) -> (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, &params.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(params.inbound_lane_data.encode(), params.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)
}
/// 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)
}
@@ -0,0 +1,644 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
};
use bp_messages::{InboundLaneData, LaneId, MessageNonce};
use frame_support::{
dispatch::CallableCallFor,
traits::{Get, IsSubType},
RuntimeDebug,
};
use pallet_bridge_messages::{Config, Pallet};
use sp_runtime::transaction_validity::TransactionValidity;
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 {
*self.bundled_range.start() == self.best_stored_nonce + 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) -> bool {
// 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 {
ReceiveMessagesProof(ReceiveMessagesProofInfo),
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> {
pub _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>;
/// Check that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call is trying
/// to deliver/confirm at least some messages that are better than the ones we know of.
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 {
match self.call_info() {
Some(CallInfo::ReceiveMessagesProof(proof_info)) if proof_info.is_obsolete() => {
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::{
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_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));
});
}
}
@@ -0,0 +1,119 @@
// Copyright 2019-2022 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.
#![cfg(any(feature = "runtime-benchmarks", test))]
use crate::messages::{BridgedChain, HashOf, HasherOf, MessageBridge};
use bp_messages::{
storage_keys, 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(crate) fn encode_all_messages(_: MessageNonce, m: &MessagePayload) -> Option<Vec<u8>> {
Some(m.encode())
}
/// Simple and correct outbound lane data encode function.
pub(crate) 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(crate) 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)
}
/// 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
}
@@ -0,0 +1,152 @@
// Copyright 2023 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::MessagesBridge,
target_chain::{DispatchMessage, MessageDispatch},
LaneId,
};
use bp_runtime::messages::MessageDispatchResult;
use codec::{Decode, Encode};
use frame_support::{dispatch::Weight, CloneNoBound, EqNoBound, PartialEqNoBound};
use pallet_bridge_messages::WeightInfoExt as MessagesPalletWeights;
use scale_info::TypeInfo;
use sp_runtime::SaturatedConversion;
use xcm_builder::{DispatchBlob, DispatchBlobError, HaulBlob, HaulBlobError};
/// Plain "XCM" payload, which we transfer through bridge
pub type XcmAsPlainPayload = sp_std::prelude::Vec<u8>;
/// Message dispatch result type for single message
#[derive(CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, Debug, TypeInfo)]
pub enum XcmBlobMessageDispatchResult {
InvalidPayload,
Dispatched,
NotDispatched(#[codec(skip)] Option<DispatchBlobError>),
}
/// [`XcmBlobMessageDispatch`] is responsible for dispatching received messages
pub struct XcmBlobMessageDispatch<DispatchBlob, Weights> {
_marker: sp_std::marker::PhantomData<(DispatchBlob, Weights)>,
}
impl<BlobDispatcher: DispatchBlob, Weights: MessagesPalletWeights> MessageDispatch
for XcmBlobMessageDispatch<BlobDispatcher, Weights>
{
type DispatchPayload = XcmAsPlainPayload;
type DispatchLevelResult = XcmBlobMessageDispatchResult;
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 }
}
}
/// [`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 message sender adapter.
type MessageSender: MessagesBridge<Self::MessageSenderOrigin, XcmAsPlainPayload>;
/// Runtime message sender origin, which is used by [`Self::MessageSender`].
type MessageSenderOrigin;
/// Our location within the Consensus Universe.
fn message_sender_origin() -> Self::MessageSenderOrigin;
/// Return message lane (as "point-to-point link") used to deliver XCM messages.
fn xcm_lane() -> LaneId;
}
/// XCM bridge adapter which connects [`XcmBlobHauler`] with [`XcmBlobHauler::MessageSender`] and
/// makes sure that XCM blob is sent to the [`pallet_bridge_messages`] queue to be relayed.
pub struct XcmBlobHaulerAdapter<XcmBlobHauler>(sp_std::marker::PhantomData<XcmBlobHauler>);
impl<HaulerOrigin, H: XcmBlobHauler<MessageSenderOrigin = HaulerOrigin>> HaulBlob
for XcmBlobHaulerAdapter<H>
{
fn haul_blob(blob: sp_std::prelude::Vec<u8>) -> Result<(), HaulBlobError> {
let lane = H::xcm_lane();
H::MessageSender::send_message(H::message_sender_origin(), lane, blob)
.map(|artifacts| (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256))
.map(|result| {
log::info!(
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
"haul_blob result - ok: {:?} on lane: {:?}",
result,
lane
)
})
.map_err(|error| {
log::error!(
target: crate::LOG_TARGET_BRIDGE_DISPATCH,
"haul_blob result - error: {:?} on lane: {:?}",
error,
lane
);
HaulBlobError::Transport("MessageSenderError")
})
}
}
@@ -0,0 +1,424 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! A mock runtime for testing different stuff in the crate. We've been using Millau
//! runtime for that before, but it has two drawbacks:
//!
//! - circular dependencies between this crate and Millau runtime;
//!
//! - we can't use (e.g. as git subtree or by copying) this crate in repo without Millau.
#![cfg(test)]
use crate::messages::{
source::{
FromThisChainMaximalOutboundPayloadSize, FromThisChainMessagePayload,
FromThisChainMessageVerifier, TargetHeaderChainAdapter,
},
target::{FromBridgedChainMessagePayload, SourceHeaderChainAdapter},
BridgedChainWithMessages, HashOf, MessageBridge, ThisChainWithMessages,
};
use bp_header_chain::{ChainWithGrandpa, HeaderChain};
use bp_messages::{target_chain::ForbidInboundMessages, LaneId, MessageNonce};
use bp_parachains::SingleParaStoredHeaderDataBuilder;
use bp_relayers::PayRewardFromAccount;
use bp_runtime::{Chain, ChainId, Parachain, UnderlyingChainProvider};
use codec::{Decode, Encode};
use frame_support::{
parameter_types,
weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight},
};
use pallet_transaction_payment::Multiplier;
use sp_runtime::{
testing::H256,
traits::{BlakeTwo256, ConstU32, ConstU64, ConstU8, IdentityLookup},
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::MockBlock<TestRuntime>;
/// Unchecked extrinsic of `ThisChain`.
pub type ThisChainUncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<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 where
Block = ThisChainBlock,
NodeBlock = ThisChainBlock,
UncheckedExtrinsic = ThisChainUncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, 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";
}
impl frame_system::Config for TestRuntime {
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type RuntimeCall = RuntimeCall;
type BlockNumber = ThisChainBlockNumber;
type Hash = ThisChainHash;
type Hashing = ThisChainHasher;
type AccountId = ThisChainAccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = ThisChainHeader;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU32<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<ThisChainBalance>;
type OnNewAccount = ();
type OnKilledAccount = ();
type BaseCallFilter = frame_support::traits::Everything;
type SystemWeightInfo = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = DbWeight;
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
impl pallet_utility::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PalletsOrigin = OriginCaller;
type WeightInfo = ();
}
impl pallet_balances::Config for TestRuntime {
type Balance = ThisChainBalance;
type RuntimeEvent = RuntimeEvent;
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ConstU32<50>;
type MaxReserves = ConstU32<50>;
type ReserveIdentifier = [u8; 8];
type HoldIdentifier = ();
type FreezeIdentifier = ();
type MaxHolds = ConstU32<0>;
type MaxFreezes = ConstU32<0>;
}
impl pallet_transaction_payment::Config for TestRuntime {
type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<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 LaneMessageVerifier = FromThisChainMessageVerifier<OnThisChainBridge>;
type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter<
TestRuntime,
(),
ConstU64<100_000>,
>;
type SourceHeaderChain = SourceHeaderChainAdapter<OnThisChainBridge>;
type MessageDispatch = ForbidInboundMessages<(), FromBridgedChainMessagePayload>;
type BridgedChainId = BridgedChainId;
}
impl pallet_bridge_relayers::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type Reward = ThisChainBalance;
type PaymentProcedure = TestPaymentProcedure;
type StakeAndSlash = TestStakeAndSlash;
type WeightInfo = ();
}
/// 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 {
type BlockNumber = ThisChainBlockNumber;
type Hash = ThisChainHash;
type Hasher = ThisChainHasher;
type Header = ThisChainHeader;
type AccountId = ThisChainAccountId;
type Balance = ThisChainBalance;
type Index = 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 {
type BlockNumber = BridgedChainBlockNumber;
type Hash = BridgedChainHash;
type Hasher = BridgedChainHasher;
type Header = BridgedChainHeader;
type AccountId = BridgedChainAccountId;
type Balance = BridgedChainBalance;
type Index = 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_JUSTIFICATON_ANCESTRY: u32 = 8;
const MAX_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64;
}
impl Chain for BridgedUnderlyingParachain {
type BlockNumber = BridgedChainBlockNumber;
type Hash = BridgedChainHash;
type Hasher = BridgedChainHasher;
type Header = BridgedChainHeader;
type AccountId = BridgedChainAccountId;
type Balance = BridgedChainBalance;
type Index = 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)
}
@@ -0,0 +1,88 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! 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(proof), parachain_heads)
}
@@ -0,0 +1,201 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! 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
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 - 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, Dispatchable, Pays, PostDispatchInfo},
traits::Get,
};
use pallet_bridge_messages::WeightInfoExt;
use pallet_transaction_payment::OnChargeTransaction;
use sp_runtime::{
traits::{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,
{
// esimate 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 extept 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