fix: Complete snowbridge pezpallet rebrand and critical bug fixes
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-beefy"
|
||||
version = "0.1.0"
|
||||
description = "Module implementing BEEFY on-chain light client used for bridging consensus of bizinikiwi-based chains."
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
publish = false
|
||||
documentation = "https://docs.rs/pezpallet-bridge-beefy"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { optional = true, workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-beefy = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-test-utils = { workspace = true, default-features = true }
|
||||
mmr-lib = { workspace = true, default-features = true }
|
||||
pezpallet-beefy-mmr = { workspace = true, default-features = true }
|
||||
pezpallet-mmr = { workspace = true, default-features = true }
|
||||
rand = { workspace = true, default-features = true }
|
||||
pezsp-consensus-beefy = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-beefy/std",
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-beefy-mmr/try-runtime",
|
||||
"pezpallet-mmr/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-beefy/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-beefy-mmr/runtime-benchmarks",
|
||||
"pezpallet-mmr/runtime-benchmarks",
|
||||
"pezsp-consensus-beefy/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,657 @@
|
||||
// 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/>.
|
||||
|
||||
//! BEEFY bridge pezpallet.
|
||||
//!
|
||||
//! This pezpallet is an on-chain BEEFY light client for Bizinikiwi-based chains that are using the
|
||||
//! following pallets bundle: `pezpallet-mmr`, `pezpallet-beefy` and `pezpallet-beefy-mmr`.
|
||||
//!
|
||||
//! The pezpallet is able to verify MMR leaf proofs and BEEFY commitments, so it has access
|
||||
//! to the following data of the bridged chain:
|
||||
//!
|
||||
//! - header hashes
|
||||
//! - changes of BEEFY authorities
|
||||
//! - extra data of MMR leafs
|
||||
//!
|
||||
//! Given the header hash, other pallets are able to verify header-based proofs
|
||||
//! (e.g. storage proofs, transaction inclusion proofs, etc.).
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_beefy::{ChainWithBeefy, InitializationData};
|
||||
use pezsp_std::{boxed::Box, prelude::*};
|
||||
|
||||
// Re-export in crate namespace for `construct_runtime!`
|
||||
pub use pezpallet::*;
|
||||
|
||||
mod utils;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod mock_chain;
|
||||
|
||||
/// The target that will be used when publishing logs related to this pezpallet.
|
||||
pub const LOG_TARGET: &str = "runtime::bridge-beefy";
|
||||
|
||||
/// Configured bridged chain.
|
||||
pub type BridgedChain<T, I> = <T as Config<I>>::BridgedChain;
|
||||
/// Block number, used by configured bridged chain.
|
||||
pub type BridgedBlockNumber<T, I> = pezbp_runtime::BlockNumberOf<BridgedChain<T, I>>;
|
||||
/// Block hash, used by configured bridged chain.
|
||||
pub type BridgedBlockHash<T, I> = pezbp_runtime::HashOf<BridgedChain<T, I>>;
|
||||
|
||||
/// Pezpallet initialization data.
|
||||
pub type InitializationDataOf<T, I> =
|
||||
InitializationData<BridgedBlockNumber<T, I>, bp_beefy::MmrHashOf<BridgedChain<T, I>>>;
|
||||
/// BEEFY commitment hasher, used by configured bridged chain.
|
||||
pub type BridgedBeefyCommitmentHasher<T, I> = bp_beefy::BeefyCommitmentHasher<BridgedChain<T, I>>;
|
||||
/// BEEFY validator id, used by configured bridged chain.
|
||||
pub type BridgedBeefyAuthorityId<T, I> = bp_beefy::BeefyAuthorityIdOf<BridgedChain<T, I>>;
|
||||
/// BEEFY validator set, used by configured bridged chain.
|
||||
pub type BridgedBeefyAuthoritySet<T, I> = bp_beefy::BeefyAuthoritySetOf<BridgedChain<T, I>>;
|
||||
/// BEEFY authority set, used by configured bridged chain.
|
||||
pub type BridgedBeefyAuthoritySetInfo<T, I> = bp_beefy::BeefyAuthoritySetInfoOf<BridgedChain<T, I>>;
|
||||
/// BEEFY signed commitment, used by configured bridged chain.
|
||||
pub type BridgedBeefySignedCommitment<T, I> = bp_beefy::BeefySignedCommitmentOf<BridgedChain<T, I>>;
|
||||
/// MMR hashing algorithm, used by configured bridged chain.
|
||||
pub type BridgedMmrHashing<T, I> = bp_beefy::MmrHashingOf<BridgedChain<T, I>>;
|
||||
/// MMR hashing output type of `BridgedMmrHashing<T, I>`.
|
||||
pub type BridgedMmrHash<T, I> = bp_beefy::MmrHashOf<BridgedChain<T, I>>;
|
||||
/// The type of the MMR leaf extra data used by the configured bridged chain.
|
||||
pub type BridgedBeefyMmrLeafExtra<T, I> = bp_beefy::BeefyMmrLeafExtraOf<BridgedChain<T, I>>;
|
||||
/// BEEFY MMR proof type used by the pezpallet
|
||||
pub type BridgedMmrProof<T, I> = bp_beefy::MmrProofOf<BridgedChain<T, I>>;
|
||||
/// MMR leaf type, used by configured bridged chain.
|
||||
pub type BridgedBeefyMmrLeaf<T, I> = bp_beefy::BeefyMmrLeafOf<BridgedChain<T, I>>;
|
||||
/// Imported commitment data, stored by the pezpallet.
|
||||
pub type ImportedCommitment<T, I> = bp_beefy::ImportedCommitment<
|
||||
BridgedBlockNumber<T, I>,
|
||||
BridgedBlockHash<T, I>,
|
||||
BridgedMmrHash<T, I>,
|
||||
>;
|
||||
|
||||
/// Some high level info about the imported commitments.
|
||||
#[derive(codec::Encode, codec::Decode, scale_info::TypeInfo)]
|
||||
pub struct ImportedCommitmentsInfoData<BlockNumber> {
|
||||
/// Best known block number, provided in a BEEFY commitment. However this is not
|
||||
/// the best proven block. The best proven block is this block's parent.
|
||||
best_block_number: BlockNumber,
|
||||
/// The head of the `ImportedBlockNumbers` ring buffer.
|
||||
next_block_number_index: u32,
|
||||
}
|
||||
|
||||
#[pezframe_support::pezpallet(dev_mode)]
|
||||
pub mod pezpallet {
|
||||
use super::*;
|
||||
use pezbp_runtime::{BasicOperatingMode, OwnedBridgeModule};
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pezpallet::config]
|
||||
pub trait Config<I: 'static = ()>: pezframe_system::Config {
|
||||
/// The upper bound on the number of requests allowed by the pezpallet.
|
||||
///
|
||||
/// A request refers to an action which writes a header to storage.
|
||||
///
|
||||
/// Once this bound is reached the pezpallet will reject all commitments
|
||||
/// until the request count has decreased.
|
||||
#[pezpallet::constant]
|
||||
type MaxRequests: Get<u32>;
|
||||
|
||||
/// Maximal number of imported commitments to keep in the storage.
|
||||
///
|
||||
/// The setting is there to prevent growing the on-chain state indefinitely. Note
|
||||
/// the setting does not relate to block numbers - we will simply keep as much items
|
||||
/// in the storage, so it doesn't guarantee any fixed timeframe for imported commitments.
|
||||
#[pezpallet::constant]
|
||||
type CommitmentsToKeep: Get<u32>;
|
||||
|
||||
/// The chain we are bridging to here.
|
||||
type BridgedChain: ChainWithBeefy;
|
||||
}
|
||||
|
||||
#[pezpallet::pezpallet]
|
||||
#[pezpallet::without_storage_info]
|
||||
pub struct Pezpallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
#[pezpallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pezpallet<T, I> {
|
||||
fn on_initialize(_n: BlockNumberFor<T>) -> pezframe_support::weights::Weight {
|
||||
<RequestCount<T, I>>::mutate(|count| *count = count.saturating_sub(1));
|
||||
|
||||
Weight::from_parts(0, 0)
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pezpallet<T, I> {
|
||||
const LOG_TARGET: &'static str = LOG_TARGET;
|
||||
type OwnerStorage = PalletOwner<T, I>;
|
||||
type OperatingMode = BasicOperatingMode;
|
||||
type OperatingModeStorage = PalletOperatingMode<T, I>;
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I>
|
||||
where
|
||||
BridgedMmrHashing<T, I>: 'static + Send + Sync,
|
||||
{
|
||||
/// Initialize pezpallet with BEEFY authority set and best known finalized block number.
|
||||
#[pezpallet::call_index(0)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(2, 3), DispatchClass::Operational))]
|
||||
pub fn initialize(
|
||||
origin: OriginFor<T>,
|
||||
init_data: InitializationDataOf<T, I>,
|
||||
) -> DispatchResult {
|
||||
Self::ensure_owner_or_root(origin)?;
|
||||
|
||||
let is_initialized = <ImportedCommitmentsInfo<T, I>>::exists();
|
||||
ensure!(!is_initialized, <Error<T, I>>::AlreadyInitialized);
|
||||
|
||||
tracing::info!(target: LOG_TARGET, ?init_data, "Initializing bridge BEEFY pezpallet");
|
||||
Ok(initialize::<T, I>(init_data)?)
|
||||
}
|
||||
|
||||
/// Change `PalletOwner`.
|
||||
///
|
||||
/// May only be called either by root, or by `PalletOwner`.
|
||||
#[pezpallet::call_index(1)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
|
||||
<Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
|
||||
}
|
||||
|
||||
/// Halt or resume all pezpallet operations.
|
||||
///
|
||||
/// May only be called either by root, or by `PalletOwner`.
|
||||
#[pezpallet::call_index(2)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_operating_mode(
|
||||
origin: OriginFor<T>,
|
||||
operating_mode: BasicOperatingMode,
|
||||
) -> DispatchResult {
|
||||
<Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
|
||||
}
|
||||
|
||||
/// Submit a commitment generated by BEEFY authority set.
|
||||
///
|
||||
/// It will use the underlying storage pezpallet to fetch information about the current
|
||||
/// authority set and best finalized block number in order to verify that the commitment
|
||||
/// is valid.
|
||||
///
|
||||
/// If successful in verification, it will update the underlying storage with the data
|
||||
/// provided in the newly submitted commitment.
|
||||
#[pezpallet::call_index(3)]
|
||||
#[pezpallet::weight(0)]
|
||||
pub fn submit_commitment(
|
||||
origin: OriginFor<T>,
|
||||
commitment: BridgedBeefySignedCommitment<T, I>,
|
||||
validator_set: BridgedBeefyAuthoritySet<T, I>,
|
||||
mmr_leaf: Box<BridgedBeefyMmrLeaf<T, I>>,
|
||||
mmr_proof: BridgedMmrProof<T, I>,
|
||||
) -> DispatchResult
|
||||
where
|
||||
BridgedBeefySignedCommitment<T, I>: Clone,
|
||||
{
|
||||
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
|
||||
ensure_signed(origin)?;
|
||||
|
||||
ensure!(Self::request_count() < T::MaxRequests::get(), <Error<T, I>>::TooManyRequests);
|
||||
|
||||
// Ensure that the commitment is for a better block.
|
||||
let commitments_info =
|
||||
ImportedCommitmentsInfo::<T, I>::get().ok_or(Error::<T, I>::NotInitialized)?;
|
||||
ensure!(
|
||||
commitment.commitment.block_number > commitments_info.best_block_number,
|
||||
Error::<T, I>::OldCommitment
|
||||
);
|
||||
|
||||
// Verify commitment and mmr leaf.
|
||||
let current_authority_set_info = CurrentAuthoritySetInfo::<T, I>::get();
|
||||
let mmr_root = utils::verify_commitment::<T, I>(
|
||||
&commitment,
|
||||
¤t_authority_set_info,
|
||||
&validator_set,
|
||||
)?;
|
||||
utils::verify_beefy_mmr_leaf::<T, I>(&mmr_leaf, mmr_proof, mmr_root)?;
|
||||
|
||||
// Update request count.
|
||||
RequestCount::<T, I>::mutate(|count| *count += 1);
|
||||
// Update authority set if needed.
|
||||
if mmr_leaf.beefy_next_authority_set.id > current_authority_set_info.id {
|
||||
CurrentAuthoritySetInfo::<T, I>::put(mmr_leaf.beefy_next_authority_set);
|
||||
}
|
||||
|
||||
// Import commitment.
|
||||
let block_number_index = commitments_info.next_block_number_index;
|
||||
let to_prune = ImportedBlockNumbers::<T, I>::try_get(block_number_index);
|
||||
ImportedCommitments::<T, I>::insert(
|
||||
commitment.commitment.block_number,
|
||||
ImportedCommitment::<T, I> {
|
||||
parent_number_and_hash: mmr_leaf.parent_number_and_hash,
|
||||
mmr_root,
|
||||
},
|
||||
);
|
||||
ImportedBlockNumbers::<T, I>::insert(
|
||||
block_number_index,
|
||||
commitment.commitment.block_number,
|
||||
);
|
||||
ImportedCommitmentsInfo::<T, I>::put(ImportedCommitmentsInfoData {
|
||||
best_block_number: commitment.commitment.block_number,
|
||||
next_block_number_index: (block_number_index + 1) % T::CommitmentsToKeep::get(),
|
||||
});
|
||||
if let Ok(old_block_number) = to_prune {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?old_block_number,
|
||||
"Pruning commitment for old block."
|
||||
);
|
||||
ImportedCommitments::<T, I>::remove(old_block_number);
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
block=?commitment.commitment.block_number,
|
||||
"Successfully imported commitment for block",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The current number of requests which have written to storage.
|
||||
///
|
||||
/// If the `RequestCount` hits `MaxRequests`, no more calls will be allowed to the pezpallet until
|
||||
/// the request capacity is increased.
|
||||
///
|
||||
/// The `RequestCount` is decreased by one at the beginning of every block. This is to ensure
|
||||
/// that the pezpallet can always make progress.
|
||||
#[pezpallet::storage]
|
||||
pub type RequestCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
|
||||
|
||||
/// High level info about the imported commitments.
|
||||
///
|
||||
/// Contains the following info:
|
||||
/// - best known block number of the bridged chain, finalized by BEEFY
|
||||
/// - the head of the `ImportedBlockNumbers` ring buffer
|
||||
#[pezpallet::storage]
|
||||
pub type ImportedCommitmentsInfo<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, ImportedCommitmentsInfoData<BridgedBlockNumber<T, I>>>;
|
||||
|
||||
/// A ring buffer containing the block numbers of the commitments that we have imported,
|
||||
/// ordered by the insertion time.
|
||||
#[pezpallet::storage]
|
||||
pub(super) type ImportedBlockNumbers<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Identity, u32, BridgedBlockNumber<T, I>>;
|
||||
|
||||
/// All the commitments that we have imported and haven't been pruned yet.
|
||||
#[pezpallet::storage]
|
||||
pub type ImportedCommitments<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Blake2_128Concat, BridgedBlockNumber<T, I>, ImportedCommitment<T, I>>;
|
||||
|
||||
/// The current BEEFY authority set at the bridged chain.
|
||||
#[pezpallet::storage]
|
||||
pub type CurrentAuthoritySetInfo<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, BridgedBeefyAuthoritySetInfo<T, I>, ValueQuery>;
|
||||
|
||||
/// Optional pezpallet owner.
|
||||
///
|
||||
/// Pezpallet owner has the right to halt all pezpallet operations and then resume it. If it is
|
||||
/// `None`, then there are no direct ways to halt/resume pezpallet operations, but other
|
||||
/// runtime methods may still be used to do that (i.e. `democracy::referendum` to update halt
|
||||
/// flag directly or calling `set_operating_mode`).
|
||||
#[pezpallet::storage]
|
||||
pub type PalletOwner<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, T::AccountId, OptionQuery>;
|
||||
|
||||
/// The current operating mode of the pezpallet.
|
||||
///
|
||||
/// Depending on the mode either all, or no transactions will be allowed.
|
||||
#[pezpallet::storage]
|
||||
pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, BasicOperatingMode, ValueQuery>;
|
||||
|
||||
#[pezpallet::genesis_config]
|
||||
#[derive(pezframe_support::DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
|
||||
/// Optional module owner account.
|
||||
pub owner: Option<T::AccountId>,
|
||||
/// Optional module initialization data.
|
||||
pub init_data: Option<InitializationDataOf<T, I>>,
|
||||
}
|
||||
|
||||
#[pezpallet::genesis_build]
|
||||
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
|
||||
fn build(&self) {
|
||||
if let Some(ref owner) = self.owner {
|
||||
<PalletOwner<T, I>>::put(owner);
|
||||
}
|
||||
|
||||
if let Some(init_data) = self.init_data.clone() {
|
||||
initialize::<T, I>(init_data)
|
||||
.expect("invalid initialization data of BEEFY bridge pezpallet");
|
||||
} else {
|
||||
// Since the bridge hasn't been initialized we shouldn't allow anyone to perform
|
||||
// transactions.
|
||||
<PalletOperatingMode<T, I>>::put(BasicOperatingMode::Halted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// The pezpallet has not been initialized yet.
|
||||
NotInitialized,
|
||||
/// The pezpallet has already been initialized.
|
||||
AlreadyInitialized,
|
||||
/// Invalid initial authority set.
|
||||
InvalidInitialAuthoritySet,
|
||||
/// There are too many requests for the current window to handle.
|
||||
TooManyRequests,
|
||||
/// The imported commitment is older than the best commitment known to the pezpallet.
|
||||
OldCommitment,
|
||||
/// The commitment is signed by unknown validator set.
|
||||
InvalidCommitmentValidatorSetId,
|
||||
/// The id of the provided validator set is invalid.
|
||||
InvalidValidatorSetId,
|
||||
/// The number of signatures in the commitment is invalid.
|
||||
InvalidCommitmentSignaturesLen,
|
||||
/// The number of validator ids provided is invalid.
|
||||
InvalidValidatorSetLen,
|
||||
/// There aren't enough correct signatures in the commitment to finalize the block.
|
||||
NotEnoughCorrectSignatures,
|
||||
/// MMR root is missing from the commitment.
|
||||
MmrRootMissingFromCommitment,
|
||||
/// MMR proof verification has failed.
|
||||
MmrProofVerificationFailed,
|
||||
/// The validators are not matching the merkle tree root of the authority set.
|
||||
InvalidValidatorSetRoot,
|
||||
/// Error generated by the `OwnedBridgeModule` trait.
|
||||
BridgeModule(pezbp_runtime::OwnedBridgeModuleError),
|
||||
}
|
||||
|
||||
/// Initialize pezpallet with given parameters.
|
||||
pub(super) fn initialize<T: Config<I>, I: 'static>(
|
||||
init_data: InitializationDataOf<T, I>,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
if init_data.authority_set.len == 0 {
|
||||
return Err(Error::<T, I>::InvalidInitialAuthoritySet);
|
||||
}
|
||||
CurrentAuthoritySetInfo::<T, I>::put(init_data.authority_set);
|
||||
|
||||
<PalletOperatingMode<T, I>>::put(init_data.operating_mode);
|
||||
ImportedCommitmentsInfo::<T, I>::put(ImportedCommitmentsInfoData {
|
||||
best_block_number: init_data.best_block_number,
|
||||
next_block_number_index: 0,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// The current number of requests which have written to storage.
|
||||
pub fn request_count() -> u32 {
|
||||
RequestCount::<T, I>::get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezbp_runtime::{BasicOperatingMode, OwnedBridgeModuleError};
|
||||
use bp_test_utils::generate_owned_bridge_module_tests;
|
||||
use pezframe_support::{assert_noop, assert_ok, traits::Get};
|
||||
use mock::*;
|
||||
use mock_chain::*;
|
||||
use pezsp_consensus_beefy::mmr::BeefyAuthoritySet;
|
||||
use pezsp_runtime::DispatchError;
|
||||
|
||||
fn next_block() {
|
||||
use pezframe_support::traits::OnInitialize;
|
||||
|
||||
let current_number = pezframe_system::Pezpallet::<TestRuntime>::block_number();
|
||||
pezframe_system::Pezpallet::<TestRuntime>::set_block_number(current_number + 1);
|
||||
let _ = Pezpallet::<TestRuntime>::on_initialize(current_number);
|
||||
}
|
||||
|
||||
fn import_header_chain(headers: Vec<HeaderAndCommitment>) {
|
||||
for header in headers {
|
||||
if header.commitment.is_some() {
|
||||
assert_ok!(import_commitment(header));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_initialize_if_already_initialized() {
|
||||
run_test_with_initialize(32, || {
|
||||
assert_noop!(
|
||||
Pezpallet::<TestRuntime>::initialize(
|
||||
RuntimeOrigin::root(),
|
||||
InitializationData {
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
best_block_number: 0,
|
||||
authority_set: BeefyAuthoritySet {
|
||||
id: 0,
|
||||
len: 1,
|
||||
keyset_commitment: [0u8; 32].into()
|
||||
}
|
||||
}
|
||||
),
|
||||
Error::<TestRuntime, ()>::AlreadyInitialized,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_initialize_if_authority_set_is_empty() {
|
||||
run_test(|| {
|
||||
assert_noop!(
|
||||
Pezpallet::<TestRuntime>::initialize(
|
||||
RuntimeOrigin::root(),
|
||||
InitializationData {
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
best_block_number: 0,
|
||||
authority_set: BeefyAuthoritySet {
|
||||
id: 0,
|
||||
len: 0,
|
||||
keyset_commitment: [0u8; 32].into()
|
||||
}
|
||||
}
|
||||
),
|
||||
Error::<TestRuntime, ()>::InvalidInitialAuthoritySet,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_import_commitment_if_halted() {
|
||||
run_test_with_initialize(1, || {
|
||||
assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
|
||||
RuntimeOrigin::root(),
|
||||
BasicOperatingMode::Halted
|
||||
));
|
||||
assert_noop!(
|
||||
import_commitment(ChainBuilder::new(1).append_finalized_header().to_header()),
|
||||
Error::<TestRuntime, ()>::BridgeModule(OwnedBridgeModuleError::Halted),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_import_commitment_if_too_many_requests() {
|
||||
run_test_with_initialize(1, || {
|
||||
let max_requests = <<TestRuntime as Config>::MaxRequests as Get<u32>>::get() as u64;
|
||||
let mut chain = ChainBuilder::new(1);
|
||||
for _ in 0..max_requests + 2 {
|
||||
chain = chain.append_finalized_header();
|
||||
}
|
||||
|
||||
// import `max_request` headers
|
||||
for i in 0..max_requests {
|
||||
assert_ok!(import_commitment(chain.header(i + 1)));
|
||||
}
|
||||
|
||||
// try to import next header: it fails because we are no longer accepting commitments
|
||||
assert_noop!(
|
||||
import_commitment(chain.header(max_requests + 1)),
|
||||
Error::<TestRuntime, ()>::TooManyRequests,
|
||||
);
|
||||
|
||||
// when next block is "started", we allow import of next header
|
||||
next_block();
|
||||
assert_ok!(import_commitment(chain.header(max_requests + 1)));
|
||||
|
||||
// but we can't import two headers until next block and so on
|
||||
assert_noop!(
|
||||
import_commitment(chain.header(max_requests + 2)),
|
||||
Error::<TestRuntime, ()>::TooManyRequests,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_import_commitment_if_not_initialized() {
|
||||
run_test(|| {
|
||||
assert_noop!(
|
||||
import_commitment(ChainBuilder::new(1).append_finalized_header().to_header()),
|
||||
Error::<TestRuntime, ()>::NotInitialized,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_works_with_long_chain_with_handoffs() {
|
||||
run_test_with_initialize(3, || {
|
||||
let chain = ChainBuilder::new(3)
|
||||
.append_finalized_header()
|
||||
.append_default_headers(16) // 2..17
|
||||
.append_finalized_header() // 18
|
||||
.append_default_headers(16) // 19..34
|
||||
.append_handoff_header(9) // 35
|
||||
.append_default_headers(8) // 36..43
|
||||
.append_finalized_header() // 44
|
||||
.append_default_headers(8) // 45..52
|
||||
.append_handoff_header(17) // 53
|
||||
.append_default_headers(4) // 54..57
|
||||
.append_finalized_header() // 58
|
||||
.append_default_headers(4); // 59..63
|
||||
import_header_chain(chain.to_chain());
|
||||
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().best_block_number,
|
||||
58
|
||||
);
|
||||
assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().id, 2);
|
||||
assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().len, 17);
|
||||
|
||||
let imported_commitment = ImportedCommitments::<TestRuntime>::get(58).unwrap();
|
||||
assert_eq!(
|
||||
imported_commitment,
|
||||
bp_beefy::ImportedCommitment {
|
||||
parent_number_and_hash: (57, chain.header(57).header.hash()),
|
||||
mmr_root: chain.header(58).mmr_root,
|
||||
},
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commitment_pruning_works() {
|
||||
run_test_with_initialize(3, || {
|
||||
let commitments_to_keep = <TestRuntime as Config<()>>::CommitmentsToKeep::get();
|
||||
let commitments_to_import: Vec<HeaderAndCommitment> = ChainBuilder::new(3)
|
||||
.append_finalized_headers(commitments_to_keep as usize + 2)
|
||||
.to_chain();
|
||||
|
||||
// import exactly `CommitmentsToKeep` commitments
|
||||
for index in 0..commitments_to_keep {
|
||||
next_block();
|
||||
import_commitment(commitments_to_import[index as usize].clone())
|
||||
.expect("must succeed");
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().next_block_number_index,
|
||||
(index + 1) % commitments_to_keep
|
||||
);
|
||||
}
|
||||
|
||||
// ensure that all commitments are in the storage
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().best_block_number,
|
||||
commitments_to_keep as TestBridgedBlockNumber
|
||||
);
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().next_block_number_index,
|
||||
0
|
||||
);
|
||||
for index in 0..commitments_to_keep {
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(
|
||||
index as TestBridgedBlockNumber + 1
|
||||
)
|
||||
.is_some());
|
||||
assert_eq!(
|
||||
ImportedBlockNumbers::<TestRuntime>::get(index),
|
||||
Some(Into::into(index + 1)),
|
||||
);
|
||||
}
|
||||
|
||||
// import next commitment
|
||||
next_block();
|
||||
import_commitment(commitments_to_import[commitments_to_keep as usize].clone())
|
||||
.expect("must succeed");
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().next_block_number_index,
|
||||
1
|
||||
);
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(
|
||||
commitments_to_keep as TestBridgedBlockNumber + 1
|
||||
)
|
||||
.is_some());
|
||||
assert_eq!(
|
||||
ImportedBlockNumbers::<TestRuntime>::get(0),
|
||||
Some(Into::into(commitments_to_keep + 1)),
|
||||
);
|
||||
// the side effect of the import is that the commitment#1 is pruned
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(1).is_none());
|
||||
|
||||
// import next commitment
|
||||
next_block();
|
||||
import_commitment(commitments_to_import[commitments_to_keep as usize + 1].clone())
|
||||
.expect("must succeed");
|
||||
assert_eq!(
|
||||
ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().next_block_number_index,
|
||||
2
|
||||
);
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(
|
||||
commitments_to_keep as TestBridgedBlockNumber + 2
|
||||
)
|
||||
.is_some());
|
||||
assert_eq!(
|
||||
ImportedBlockNumbers::<TestRuntime>::get(1),
|
||||
Some(Into::into(commitments_to_keep + 2)),
|
||||
);
|
||||
// the side effect of the import is that the commitment#2 is pruned
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(1).is_none());
|
||||
assert!(ImportedCommitments::<TestRuntime>::get(2).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// 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/>.
|
||||
|
||||
use crate as beefy;
|
||||
use crate::{
|
||||
utils::get_authorities_mmr_root, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo,
|
||||
BridgedBeefyCommitmentHasher, BridgedBeefyMmrLeafExtra, BridgedBeefySignedCommitment,
|
||||
BridgedMmrHash, BridgedMmrHashing, BridgedMmrProof,
|
||||
};
|
||||
|
||||
use bp_beefy::{BeefyValidatorSignatureOf, ChainWithBeefy, Commitment, MmrDataOrHash};
|
||||
use pezbp_runtime::{BasicOperatingMode, Chain, ChainId};
|
||||
use codec::Encode;
|
||||
use pezframe_support::{construct_runtime, derive_impl, weights::Weight};
|
||||
use pezsp_core::{sr25519::Signature, Pair};
|
||||
use pezsp_runtime::{
|
||||
testing::{Header, H256},
|
||||
traits::{BlakeTwo256, Hash},
|
||||
StateVersion,
|
||||
};
|
||||
|
||||
pub use pezsp_consensus_beefy::ecdsa_crypto::{AuthorityId as BeefyId, Pair as BeefyPair};
|
||||
use pezsp_core::crypto::Wraps;
|
||||
use pezsp_runtime::traits::Keccak256;
|
||||
|
||||
pub type TestAccountId = u64;
|
||||
pub type TestBridgedBlockNumber = u64;
|
||||
pub type TestBridgedBlockHash = H256;
|
||||
pub type TestBridgedHeader = Header;
|
||||
pub type TestBridgedAuthoritySetInfo = BridgedBeefyAuthoritySetInfo<TestRuntime, ()>;
|
||||
pub type TestBridgedValidatorSet = BridgedBeefyAuthoritySet<TestRuntime, ()>;
|
||||
pub type TestBridgedCommitment = BridgedBeefySignedCommitment<TestRuntime, ()>;
|
||||
pub type TestBridgedValidatorSignature = BeefyValidatorSignatureOf<TestBridgedChain>;
|
||||
pub type TestBridgedCommitmentHasher = BridgedBeefyCommitmentHasher<TestRuntime, ()>;
|
||||
pub type TestBridgedMmrHashing = BridgedMmrHashing<TestRuntime, ()>;
|
||||
pub type TestBridgedMmrHash = BridgedMmrHash<TestRuntime, ()>;
|
||||
pub type TestBridgedBeefyMmrLeafExtra = BridgedBeefyMmrLeafExtra<TestRuntime, ()>;
|
||||
pub type TestBridgedMmrProof = BridgedMmrProof<TestRuntime, ()>;
|
||||
pub type TestBridgedRawMmrLeaf = pezsp_consensus_beefy::mmr::MmrLeaf<
|
||||
TestBridgedBlockNumber,
|
||||
TestBridgedBlockHash,
|
||||
TestBridgedMmrHash,
|
||||
TestBridgedBeefyMmrLeafExtra,
|
||||
>;
|
||||
pub type TestBridgedMmrNode = MmrDataOrHash<Keccak256, TestBridgedRawMmrLeaf>;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Beefy: beefy::{Pezpallet},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
impl beefy::Config for TestRuntime {
|
||||
type MaxRequests = pezframe_support::traits::ConstU32<16>;
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type CommitmentsToKeep = pezframe_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBridgedChain;
|
||||
|
||||
impl Chain for TestBridgedChain {
|
||||
const ID: ChainId = *b"tbch";
|
||||
|
||||
type BlockNumber = TestBridgedBlockNumber;
|
||||
type Hash = H256;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = pezsp_runtime::testing::Header;
|
||||
|
||||
type AccountId = TestAccountId;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = Signature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
unreachable!()
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithBeefy for TestBridgedChain {
|
||||
type CommitmentHasher = Keccak256;
|
||||
type MmrHashing = Keccak256;
|
||||
type MmrHash = <Keccak256 as Hash>::Output;
|
||||
type BeefyMmrLeafExtra = ();
|
||||
type AuthorityId = BeefyId;
|
||||
type AuthorityIdToMerkleLeaf = pezpallet_beefy_mmr::BeefyEcdsaToEthereum;
|
||||
}
|
||||
|
||||
/// Run test within test runtime.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
pezsp_io::TestExternalities::new(Default::default()).execute_with(test)
|
||||
}
|
||||
|
||||
/// Initialize pezpallet and run test.
|
||||
pub fn run_test_with_initialize<T>(initial_validators_count: u32, test: impl FnOnce() -> T) -> T {
|
||||
run_test(|| {
|
||||
let validators = validator_ids(0, initial_validators_count);
|
||||
let authority_set = authority_set_info(0, &validators);
|
||||
|
||||
crate::Pezpallet::<TestRuntime>::initialize(
|
||||
RuntimeOrigin::root(),
|
||||
bp_beefy::InitializationData {
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
best_block_number: 0,
|
||||
authority_set,
|
||||
},
|
||||
)
|
||||
.expect("initialization data is correct");
|
||||
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
/// Import given commitment.
|
||||
pub fn import_commitment(
|
||||
header: crate::mock_chain::HeaderAndCommitment,
|
||||
) -> pezsp_runtime::DispatchResult {
|
||||
crate::Pezpallet::<TestRuntime>::submit_commitment(
|
||||
RuntimeOrigin::signed(1),
|
||||
header
|
||||
.commitment
|
||||
.expect("thou shall not call import_commitment on header without commitment"),
|
||||
header.validator_set,
|
||||
Box::new(header.leaf),
|
||||
header.leaf_proof,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn validator_pairs(index: u32, count: u32) -> Vec<BeefyPair> {
|
||||
(index..index + count)
|
||||
.map(|index| {
|
||||
let mut seed = [1u8; 32];
|
||||
seed[0..8].copy_from_slice(&(index as u64).encode());
|
||||
BeefyPair::from_seed(&seed)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return identifiers of validators, starting at given index.
|
||||
pub fn validator_ids(index: u32, count: u32) -> Vec<BeefyId> {
|
||||
validator_pairs(index, count).into_iter().map(|pair| pair.public()).collect()
|
||||
}
|
||||
|
||||
pub fn authority_set_info(id: u64, validators: &[BeefyId]) -> TestBridgedAuthoritySetInfo {
|
||||
let merkle_root = get_authorities_mmr_root::<TestRuntime, (), _>(validators.iter());
|
||||
|
||||
TestBridgedAuthoritySetInfo { id, len: validators.len() as u32, keyset_commitment: merkle_root }
|
||||
}
|
||||
|
||||
/// Sign BEEFY commitment.
|
||||
pub fn sign_commitment(
|
||||
commitment: Commitment<TestBridgedBlockNumber>,
|
||||
validator_pairs: &[BeefyPair],
|
||||
signature_count: usize,
|
||||
) -> TestBridgedCommitment {
|
||||
let total_validators = validator_pairs.len();
|
||||
let random_validators =
|
||||
rand::seq::index::sample(&mut rand::thread_rng(), total_validators, signature_count);
|
||||
|
||||
let commitment_hash = TestBridgedCommitmentHasher::hash(&commitment.encode());
|
||||
let mut signatures = vec![None; total_validators];
|
||||
for validator_idx in random_validators.iter() {
|
||||
let validator = &validator_pairs[validator_idx];
|
||||
signatures[validator_idx] =
|
||||
Some(validator.as_inner_ref().sign_prehashed(commitment_hash.as_fixed_bytes()).into());
|
||||
}
|
||||
|
||||
TestBridgedCommitment { commitment, signatures }
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
// 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/>.
|
||||
|
||||
//! Utilities to build bridged chain and BEEFY+MMR structures.
|
||||
|
||||
use crate::{
|
||||
mock::{
|
||||
sign_commitment, validator_pairs, BeefyPair, TestBridgedBlockNumber, TestBridgedCommitment,
|
||||
TestBridgedHeader, TestBridgedMmrHash, TestBridgedMmrHashing, TestBridgedMmrNode,
|
||||
TestBridgedMmrProof, TestBridgedRawMmrLeaf, TestBridgedValidatorSet,
|
||||
TestBridgedValidatorSignature, TestRuntime,
|
||||
},
|
||||
utils::get_authorities_mmr_root,
|
||||
};
|
||||
|
||||
use bp_beefy::{BeefyPayload, Commitment, ValidatorSetId, MMR_ROOT_PAYLOAD_ID};
|
||||
use codec::Encode;
|
||||
use pezpallet_mmr::NodeIndex;
|
||||
use rand::Rng;
|
||||
use pezsp_consensus_beefy::mmr::{BeefyNextAuthoritySet, MmrLeafVersion};
|
||||
use pezsp_core::Pair;
|
||||
use pezsp_runtime::traits::{Hash, Header as HeaderT};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HeaderAndCommitment {
|
||||
pub header: TestBridgedHeader,
|
||||
pub commitment: Option<TestBridgedCommitment>,
|
||||
pub validator_set: TestBridgedValidatorSet,
|
||||
pub leaf: TestBridgedRawMmrLeaf,
|
||||
pub leaf_proof: TestBridgedMmrProof,
|
||||
pub mmr_root: TestBridgedMmrHash,
|
||||
}
|
||||
|
||||
impl HeaderAndCommitment {
|
||||
pub fn customize_signatures(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Vec<Option<TestBridgedValidatorSignature>>),
|
||||
) {
|
||||
if let Some(commitment) = &mut self.commitment {
|
||||
f(&mut commitment.signatures);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn customize_commitment(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Commitment<TestBridgedBlockNumber>),
|
||||
validator_pairs: &[BeefyPair],
|
||||
signature_count: usize,
|
||||
) {
|
||||
if let Some(mut commitment) = self.commitment.take() {
|
||||
f(&mut commitment.commitment);
|
||||
self.commitment =
|
||||
Some(sign_commitment(commitment.commitment, validator_pairs, signature_count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChainBuilder {
|
||||
headers: Vec<HeaderAndCommitment>,
|
||||
validator_set_id: ValidatorSetId,
|
||||
validator_keys: Vec<BeefyPair>,
|
||||
mmr: mmr_lib::MMR<TestBridgedMmrNode, BridgedMmrHashMerge, BridgedMmrStorage>,
|
||||
}
|
||||
|
||||
struct BridgedMmrStorage {
|
||||
nodes: HashMap<NodeIndex, TestBridgedMmrNode>,
|
||||
}
|
||||
|
||||
impl mmr_lib::MMRStoreReadOps<TestBridgedMmrNode> for BridgedMmrStorage {
|
||||
fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result<Option<TestBridgedMmrNode>> {
|
||||
Ok(self.nodes.get(&pos).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
impl mmr_lib::MMRStoreWriteOps<TestBridgedMmrNode> for BridgedMmrStorage {
|
||||
fn append(&mut self, pos: NodeIndex, elems: Vec<TestBridgedMmrNode>) -> mmr_lib::Result<()> {
|
||||
for (i, elem) in elems.into_iter().enumerate() {
|
||||
self.nodes.insert(pos + i as NodeIndex, elem);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainBuilder {
|
||||
/// Creates new chain builder with given validator set size.
|
||||
pub fn new(initial_validators_count: u32) -> Self {
|
||||
ChainBuilder {
|
||||
headers: Vec::new(),
|
||||
validator_set_id: 0,
|
||||
validator_keys: validator_pairs(0, initial_validators_count),
|
||||
mmr: mmr_lib::MMR::new(0, BridgedMmrStorage { nodes: HashMap::new() }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get header with given number.
|
||||
pub fn header(&self, number: TestBridgedBlockNumber) -> HeaderAndCommitment {
|
||||
self.headers[number as usize - 1].clone()
|
||||
}
|
||||
|
||||
/// Returns single built header.
|
||||
pub fn to_header(&self) -> HeaderAndCommitment {
|
||||
assert_eq!(self.headers.len(), 1);
|
||||
self.headers[0].clone()
|
||||
}
|
||||
|
||||
/// Returns built chain.
|
||||
pub fn to_chain(&self) -> Vec<HeaderAndCommitment> {
|
||||
self.headers.clone()
|
||||
}
|
||||
|
||||
/// Appends header, that has been finalized by BEEFY (so it has a linked signed commitment).
|
||||
pub fn append_finalized_header(self) -> Self {
|
||||
let next_validator_set_id = self.validator_set_id;
|
||||
let next_validator_keys = self.validator_keys.clone();
|
||||
HeaderBuilder::with_chain(self, next_validator_set_id, next_validator_keys).finalize()
|
||||
}
|
||||
|
||||
/// Append multiple finalized headers at once.
|
||||
pub fn append_finalized_headers(mut self, count: usize) -> Self {
|
||||
for _ in 0..count {
|
||||
self = self.append_finalized_header();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Appends header, that enacts new validator set.
|
||||
///
|
||||
/// Such headers are explicitly finalized by BEEFY.
|
||||
pub fn append_handoff_header(self, next_validators_len: u32) -> Self {
|
||||
let new_validator_set_id = self.validator_set_id + 1;
|
||||
let new_validator_pairs =
|
||||
validator_pairs(rand::thread_rng().gen::<u32>() % (u32::MAX / 2), next_validators_len);
|
||||
|
||||
HeaderBuilder::with_chain(self, new_validator_set_id, new_validator_pairs).finalize()
|
||||
}
|
||||
|
||||
/// Append several default header without commitment.
|
||||
pub fn append_default_headers(mut self, count: usize) -> Self {
|
||||
for _ in 0..count {
|
||||
let next_validator_set_id = self.validator_set_id;
|
||||
let next_validator_keys = self.validator_keys.clone();
|
||||
self =
|
||||
HeaderBuilder::with_chain(self, next_validator_set_id, next_validator_keys).build()
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom header builder.
|
||||
pub struct HeaderBuilder {
|
||||
chain: ChainBuilder,
|
||||
header: TestBridgedHeader,
|
||||
leaf: TestBridgedRawMmrLeaf,
|
||||
leaf_proof: Option<TestBridgedMmrProof>,
|
||||
next_validator_set_id: ValidatorSetId,
|
||||
next_validator_keys: Vec<BeefyPair>,
|
||||
}
|
||||
|
||||
impl HeaderBuilder {
|
||||
fn with_chain(
|
||||
chain: ChainBuilder,
|
||||
next_validator_set_id: ValidatorSetId,
|
||||
next_validator_keys: Vec<BeefyPair>,
|
||||
) -> Self {
|
||||
// we're starting with header#1, since header#0 is always finalized
|
||||
let header_number = chain.headers.len() as TestBridgedBlockNumber + 1;
|
||||
let header = TestBridgedHeader::new(
|
||||
header_number,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
chain.headers.last().map(|h| h.header.hash()).unwrap_or_default(),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let next_validators =
|
||||
next_validator_keys.iter().map(|pair| pair.public()).collect::<Vec<_>>();
|
||||
let next_validators_mmr_root =
|
||||
get_authorities_mmr_root::<TestRuntime, (), _>(next_validators.iter());
|
||||
let leaf = pezsp_consensus_beefy::mmr::MmrLeaf {
|
||||
version: MmrLeafVersion::new(1, 0),
|
||||
parent_number_and_hash: (header.number().saturating_sub(1), *header.parent_hash()),
|
||||
beefy_next_authority_set: BeefyNextAuthoritySet {
|
||||
id: next_validator_set_id,
|
||||
len: next_validators.len() as u32,
|
||||
keyset_commitment: next_validators_mmr_root,
|
||||
},
|
||||
leaf_extra: (),
|
||||
};
|
||||
|
||||
HeaderBuilder {
|
||||
chain,
|
||||
header,
|
||||
leaf,
|
||||
leaf_proof: None,
|
||||
next_validator_keys,
|
||||
next_validator_set_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Customize generated proof of header MMR leaf.
|
||||
///
|
||||
/// Can only be called once.
|
||||
pub fn customize_proof(
|
||||
mut self,
|
||||
f: impl FnOnce(TestBridgedMmrProof) -> TestBridgedMmrProof,
|
||||
) -> Self {
|
||||
assert!(self.leaf_proof.is_none());
|
||||
|
||||
let leaf_hash = TestBridgedMmrHashing::hash(&self.leaf.encode());
|
||||
let node = TestBridgedMmrNode::Hash(leaf_hash);
|
||||
let leaf_position = self.chain.mmr.push(node).unwrap();
|
||||
|
||||
let proof = self.chain.mmr.gen_proof(vec![leaf_position]).unwrap();
|
||||
// genesis has no leaf => leaf index is header number minus 1
|
||||
let leaf_index = *self.header.number() - 1;
|
||||
let leaf_count = *self.header.number();
|
||||
self.leaf_proof = Some(f(TestBridgedMmrProof {
|
||||
leaf_indices: vec![leaf_index],
|
||||
leaf_count,
|
||||
items: proof.proof_items().iter().map(|i| i.hash()).collect(),
|
||||
}));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Build header without commitment.
|
||||
pub fn build(mut self) -> ChainBuilder {
|
||||
if self.leaf_proof.is_none() {
|
||||
self = self.customize_proof(|proof| proof);
|
||||
}
|
||||
|
||||
let validators =
|
||||
self.chain.validator_keys.iter().map(|pair| pair.public()).collect::<Vec<_>>();
|
||||
self.chain.headers.push(HeaderAndCommitment {
|
||||
header: self.header,
|
||||
commitment: None,
|
||||
validator_set: TestBridgedValidatorSet::new(validators, self.chain.validator_set_id)
|
||||
.unwrap(),
|
||||
leaf: self.leaf,
|
||||
leaf_proof: self.leaf_proof.expect("guaranteed by the customize_proof call above; qed"),
|
||||
mmr_root: self.chain.mmr.get_root().unwrap().hash(),
|
||||
});
|
||||
|
||||
self.chain.validator_set_id = self.next_validator_set_id;
|
||||
self.chain.validator_keys = self.next_validator_keys;
|
||||
|
||||
self.chain
|
||||
}
|
||||
|
||||
/// Build header with commitment.
|
||||
pub fn finalize(self) -> ChainBuilder {
|
||||
let validator_count = self.chain.validator_keys.len();
|
||||
let current_validator_set_id = self.chain.validator_set_id;
|
||||
let current_validator_set_keys = self.chain.validator_keys.clone();
|
||||
let mut chain = self.build();
|
||||
|
||||
let last_header = chain.headers.last_mut().expect("added by append_header; qed");
|
||||
last_header.commitment = Some(sign_commitment(
|
||||
Commitment {
|
||||
payload: BeefyPayload::from_single_entry(
|
||||
MMR_ROOT_PAYLOAD_ID,
|
||||
chain.mmr.get_root().unwrap().hash().encode(),
|
||||
),
|
||||
block_number: *last_header.header.number(),
|
||||
validator_set_id: current_validator_set_id,
|
||||
},
|
||||
¤t_validator_set_keys,
|
||||
validator_count * 2 / 3 + 1,
|
||||
));
|
||||
|
||||
chain
|
||||
}
|
||||
}
|
||||
|
||||
/// Default Merging & Hashing behavior for MMR.
|
||||
pub struct BridgedMmrHashMerge;
|
||||
|
||||
impl mmr_lib::Merge for BridgedMmrHashMerge {
|
||||
type Item = TestBridgedMmrNode;
|
||||
|
||||
fn merge(left: &Self::Item, right: &Self::Item) -> mmr_lib::Result<Self::Item> {
|
||||
let mut concat = left.hash().as_ref().to_vec();
|
||||
concat.extend_from_slice(right.hash().as_ref());
|
||||
|
||||
Ok(TestBridgedMmrNode::Hash(TestBridgedMmrHashing::hash(&concat)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
use crate::{
|
||||
BridgedBeefyAuthorityId, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo,
|
||||
BridgedBeefyMmrLeaf, BridgedBeefySignedCommitment, BridgedChain, BridgedMmrHash,
|
||||
BridgedMmrHashing, BridgedMmrProof, Config, Error, LOG_TARGET,
|
||||
};
|
||||
use bp_beefy::{merkle_root, verify_mmr_leaves_proof, BeefyAuthorityId, MmrDataOrHash};
|
||||
use codec::Encode;
|
||||
use pezframe_support::ensure;
|
||||
use pezsp_runtime::traits::{Convert, Hash};
|
||||
use pezsp_std::{vec, vec::Vec};
|
||||
|
||||
type BridgedMmrDataOrHash<T, I> = MmrDataOrHash<BridgedMmrHashing<T, I>, BridgedBeefyMmrLeaf<T, I>>;
|
||||
/// A way to encode validator id to the BEEFY merkle tree leaf.
|
||||
type BridgedBeefyAuthorityIdToMerkleLeaf<T, I> =
|
||||
bp_beefy::BeefyAuthorityIdToMerkleLeafOf<BridgedChain<T, I>>;
|
||||
|
||||
/// Get the MMR root for a collection of validators.
|
||||
pub(crate) fn get_authorities_mmr_root<
|
||||
'a,
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
V: Iterator<Item = &'a BridgedBeefyAuthorityId<T, I>>,
|
||||
>(
|
||||
authorities: V,
|
||||
) -> BridgedMmrHash<T, I> {
|
||||
let merkle_leafs = authorities
|
||||
.cloned()
|
||||
.map(BridgedBeefyAuthorityIdToMerkleLeaf::<T, I>::convert)
|
||||
.collect::<Vec<_>>();
|
||||
merkle_root::<BridgedMmrHashing<T, I>, _>(merkle_leafs)
|
||||
}
|
||||
|
||||
fn verify_authority_set<T: Config<I>, I: 'static>(
|
||||
authority_set_info: &BridgedBeefyAuthoritySetInfo<T, I>,
|
||||
authority_set: &BridgedBeefyAuthoritySet<T, I>,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
ensure!(authority_set.id() == authority_set_info.id, Error::<T, I>::InvalidValidatorSetId);
|
||||
ensure!(
|
||||
authority_set.len() == authority_set_info.len as usize,
|
||||
Error::<T, I>::InvalidValidatorSetLen
|
||||
);
|
||||
|
||||
// Ensure that the authority set that signed the commitment is the expected one.
|
||||
let root = get_authorities_mmr_root::<T, I, _>(authority_set.validators().iter());
|
||||
ensure!(root == authority_set_info.keyset_commitment, Error::<T, I>::InvalidValidatorSetRoot);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Number of correct signatures, required from given validators set to accept signed
|
||||
/// commitment.
|
||||
///
|
||||
/// We're using 'conservative' approach here, where signatures of `2/3+1` validators are
|
||||
/// required..
|
||||
pub(crate) fn signatures_required(validators_len: usize) -> usize {
|
||||
validators_len - validators_len.saturating_sub(1) / 3
|
||||
}
|
||||
|
||||
fn verify_signatures<T: Config<I>, I: 'static>(
|
||||
commitment: &BridgedBeefySignedCommitment<T, I>,
|
||||
authority_set: &BridgedBeefyAuthoritySet<T, I>,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
ensure!(
|
||||
commitment.signatures.len() == authority_set.len(),
|
||||
Error::<T, I>::InvalidCommitmentSignaturesLen
|
||||
);
|
||||
|
||||
// Ensure that the commitment was signed by enough authorities.
|
||||
let msg = commitment.commitment.encode();
|
||||
let mut missing_signatures = signatures_required(authority_set.len());
|
||||
for (idx, (authority, maybe_sig)) in
|
||||
authority_set.validators().iter().zip(commitment.signatures.iter()).enumerate()
|
||||
{
|
||||
if let Some(sig) = maybe_sig {
|
||||
if authority.verify(sig, &msg) {
|
||||
missing_signatures = missing_signatures.saturating_sub(1);
|
||||
if missing_signatures == 0 {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
%idx,
|
||||
?authority,
|
||||
?sig,
|
||||
"Signed commitment contains incorrect signature of validator"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ensure!(missing_signatures == 0, Error::<T, I>::NotEnoughCorrectSignatures);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extract MMR root from commitment payload.
|
||||
fn extract_mmr_root<T: Config<I>, I: 'static>(
|
||||
commitment: &BridgedBeefySignedCommitment<T, I>,
|
||||
) -> Result<BridgedMmrHash<T, I>, Error<T, I>> {
|
||||
commitment
|
||||
.commitment
|
||||
.payload
|
||||
.get_decoded(&bp_beefy::MMR_ROOT_PAYLOAD_ID)
|
||||
.ok_or(Error::MmrRootMissingFromCommitment)
|
||||
}
|
||||
|
||||
pub(crate) fn verify_commitment<T: Config<I>, I: 'static>(
|
||||
commitment: &BridgedBeefySignedCommitment<T, I>,
|
||||
authority_set_info: &BridgedBeefyAuthoritySetInfo<T, I>,
|
||||
authority_set: &BridgedBeefyAuthoritySet<T, I>,
|
||||
) -> Result<BridgedMmrHash<T, I>, Error<T, I>> {
|
||||
// Ensure that the commitment is signed by the best known BEEFY validator set.
|
||||
ensure!(
|
||||
commitment.commitment.validator_set_id == authority_set_info.id,
|
||||
Error::<T, I>::InvalidCommitmentValidatorSetId
|
||||
);
|
||||
ensure!(
|
||||
commitment.signatures.len() == authority_set_info.len as usize,
|
||||
Error::<T, I>::InvalidCommitmentSignaturesLen
|
||||
);
|
||||
|
||||
verify_authority_set(authority_set_info, authority_set)?;
|
||||
verify_signatures(commitment, authority_set)?;
|
||||
|
||||
extract_mmr_root(commitment)
|
||||
}
|
||||
|
||||
/// Verify MMR proof of given leaf.
|
||||
pub(crate) fn verify_beefy_mmr_leaf<T: Config<I>, I: 'static>(
|
||||
mmr_leaf: &BridgedBeefyMmrLeaf<T, I>,
|
||||
mmr_proof: BridgedMmrProof<T, I>,
|
||||
mmr_root: BridgedMmrHash<T, I>,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
let mmr_proof_leaf_count = mmr_proof.leaf_count;
|
||||
let mmr_proof_length = mmr_proof.items.len();
|
||||
|
||||
// Verify the mmr proof for the provided leaf.
|
||||
let mmr_leaf_hash = BridgedMmrHashing::<T, I>::hash(&mmr_leaf.encode());
|
||||
verify_mmr_leaves_proof(
|
||||
mmr_root,
|
||||
vec![BridgedMmrDataOrHash::<T, I>::Hash(mmr_leaf_hash)],
|
||||
mmr_proof,
|
||||
)
|
||||
.map_err(|e| {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
?mmr_leaf_hash,
|
||||
root=?mmr_root,
|
||||
leaf_count=%mmr_proof_leaf_count,
|
||||
len=%mmr_proof_length,
|
||||
"MMR proof of leaf verification has failed"
|
||||
);
|
||||
|
||||
Error::<T, I>::MmrProofVerificationFailed
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, mock_chain::*, *};
|
||||
use bp_beefy::{BeefyPayload, MMR_ROOT_PAYLOAD_ID};
|
||||
use pezframe_support::{assert_noop, assert_ok};
|
||||
use pezsp_consensus_beefy::ValidatorSet;
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_checks_metadata() {
|
||||
run_test_with_initialize(8, || {
|
||||
// Fails if `commitment.commitment.validator_set_id` differs.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.customize_commitment(
|
||||
|commitment| {
|
||||
commitment.validator_set_id += 1;
|
||||
},
|
||||
&validator_pairs(0, 8),
|
||||
6,
|
||||
);
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidCommitmentValidatorSetId,
|
||||
);
|
||||
|
||||
// Fails if `commitment.signatures.len()` differs.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.customize_signatures(|signatures| {
|
||||
signatures.pop();
|
||||
});
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidCommitmentSignaturesLen,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_checks_validator_set() {
|
||||
run_test_with_initialize(8, || {
|
||||
// Fails if `ValidatorSet::id` differs.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.validator_set = ValidatorSet::new(validator_ids(0, 8), 1).unwrap();
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidValidatorSetId,
|
||||
);
|
||||
|
||||
// Fails if `ValidatorSet::len()` differs.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.validator_set = ValidatorSet::new(validator_ids(0, 5), 0).unwrap();
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidValidatorSetLen,
|
||||
);
|
||||
|
||||
// Fails if the validators differ.
|
||||
let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
|
||||
header.validator_set = ValidatorSet::new(validator_ids(3, 8), 0).unwrap();
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::InvalidValidatorSetRoot,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_checks_signatures() {
|
||||
run_test_with_initialize(20, || {
|
||||
// Fails when there aren't enough signatures.
|
||||
let mut header = ChainBuilder::new(20).append_finalized_header().to_header();
|
||||
header.customize_signatures(|signatures| {
|
||||
let first_signature_idx = signatures.iter().position(Option::is_some).unwrap();
|
||||
signatures[first_signature_idx] = None;
|
||||
});
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::NotEnoughCorrectSignatures,
|
||||
);
|
||||
|
||||
// Fails when there aren't enough correct signatures.
|
||||
let mut header = ChainBuilder::new(20).append_finalized_header().to_header();
|
||||
header.customize_signatures(|signatures| {
|
||||
let first_signature_idx = signatures.iter().position(Option::is_some).unwrap();
|
||||
let last_signature_idx = signatures.len() -
|
||||
signatures.iter().rev().position(Option::is_some).unwrap() -
|
||||
1;
|
||||
signatures[first_signature_idx] = signatures[last_signature_idx].clone();
|
||||
});
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::NotEnoughCorrectSignatures,
|
||||
);
|
||||
|
||||
// Returns Ok(()) when there are enough signatures, even if some are incorrect.
|
||||
let mut header = ChainBuilder::new(20).append_finalized_header().to_header();
|
||||
header.customize_signatures(|signatures| {
|
||||
let first_signature_idx = signatures.iter().position(Option::is_some).unwrap();
|
||||
let first_missing_signature_idx =
|
||||
signatures.iter().position(Option::is_none).unwrap();
|
||||
signatures[first_missing_signature_idx] = signatures[first_signature_idx].clone();
|
||||
});
|
||||
assert_ok!(import_commitment(header));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_checks_mmr_proof() {
|
||||
run_test_with_initialize(1, || {
|
||||
let validators = validator_pairs(0, 1);
|
||||
|
||||
// Fails if leaf is not for parent.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
header.leaf.parent_number_and_hash.0 += 1;
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrProofVerificationFailed,
|
||||
);
|
||||
|
||||
// Fails if mmr proof is incorrect.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
header.leaf_proof.leaf_indices[0] += 1;
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrProofVerificationFailed,
|
||||
);
|
||||
|
||||
// Fails if mmr root is incorrect.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
// Replace MMR root with zeroes.
|
||||
header.customize_commitment(
|
||||
|commitment| {
|
||||
commitment.payload =
|
||||
BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, [0u8; 32].encode());
|
||||
},
|
||||
&validators,
|
||||
1,
|
||||
);
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrProofVerificationFailed,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_extracts_mmr_root() {
|
||||
run_test_with_initialize(1, || {
|
||||
let validators = validator_pairs(0, 1);
|
||||
|
||||
// Fails if there is no mmr root in the payload.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
// Remove MMR root from the payload.
|
||||
header.customize_commitment(
|
||||
|commitment| {
|
||||
commitment.payload = BeefyPayload::from_single_entry(*b"xy", vec![]);
|
||||
},
|
||||
&validators,
|
||||
1,
|
||||
);
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrRootMissingFromCommitment,
|
||||
);
|
||||
|
||||
// Fails if mmr root can't be decoded.
|
||||
let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
|
||||
// MMR root is a 32-byte array and we have replaced it with single byte
|
||||
header.customize_commitment(
|
||||
|commitment| {
|
||||
commitment.payload =
|
||||
BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, vec![42]);
|
||||
},
|
||||
&validators,
|
||||
1,
|
||||
);
|
||||
assert_noop!(
|
||||
import_commitment(header),
|
||||
Error::<TestRuntime, ()>::MmrRootMissingFromCommitment,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_commitment_stores_valid_data() {
|
||||
run_test_with_initialize(20, || {
|
||||
let header = ChainBuilder::new(20).append_handoff_header(30).to_header();
|
||||
assert_ok!(import_commitment(header.clone()));
|
||||
|
||||
assert_eq!(ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().best_block_number, 1);
|
||||
assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().id, 1);
|
||||
assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().len, 30);
|
||||
assert_eq!(
|
||||
ImportedCommitments::<TestRuntime>::get(1).unwrap(),
|
||||
bp_beefy::ImportedCommitment {
|
||||
parent_number_and_hash: (0, [0; 32].into()),
|
||||
mmr_root: header.mmr_root,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-grandpa"
|
||||
version = "0.7.0"
|
||||
description = "Module implementing GRANDPA on-chain light client used for bridging consensus of bizinikiwi-based chains."
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-bridge-grandpa"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-consensus-grandpa = { features = ["serde"], workspace = true }
|
||||
pezsp-runtime = { features = ["serde"], workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
# Optional Benchmarking Dependencies
|
||||
bp-test-utils = { optional = true, workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezbp-runtime = { features = ["test-helpers"], workspace = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-test-utils/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"scale-info/std",
|
||||
"pezsp-consensus-grandpa/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils",
|
||||
"bp-test-utils?/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezsp-consensus-grandpa/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,101 @@
|
||||
# Bridge GRANDPA Pezpallet
|
||||
|
||||
The bridge GRANDPA pezpallet is a light client for the GRANDPA finality gadget, running at the bridged chain.
|
||||
It may import headers and their GRANDPA finality proofs (justifications) of the bridged chain. Imported
|
||||
headers then may be used to verify storage proofs by other pallets. This makes the bridge GRANDPA pezpallet
|
||||
a basic pezpallet of all bridges with Bizinikiwi-based chains. It is used by all bridge types (bridge between
|
||||
standalone chains, between teyrchains and any combination of those) and is used by other bridge pallets.
|
||||
It is used by the teyrchains light client (bridge teyrchains pezpallet) and by messages pezpallet.
|
||||
|
||||
## A Brief Introduction into GRANDPA Finality
|
||||
|
||||
You can find detailed information on GRANDPA, by exploring its [repository](https://github.com/pezkuwichain/finality-grandpa).
|
||||
Here is the minimal required GRANDPA information to understand how pezpallet works.
|
||||
|
||||
Any Bizinikiwi chain may use different block authorship algorithms (like BABE or Aura) to determine block producers and
|
||||
generate blocks. This has nothing common with finality, though - the task of block authorship is to coordinate
|
||||
blocks generation. Any block may be reverted (if there's a fork) if it is not finalized. The finality solution
|
||||
for (standalone) Bizinikiwi-based chains is the GRANDPA finality gadget. If some block is finalized by the gadget, it
|
||||
can't be reverted.
|
||||
|
||||
In GRANDPA, there are validators, identified by their public keys. They select some generated block and produce
|
||||
signatures on this block hash. If there are enough (more than `2 / 3 * N`, where `N` is number of validators)
|
||||
signatures, then the block is considered finalized. The set of signatures for the block is called justification.
|
||||
Anyone who knows the public keys of validators is able to verify GRANDPA justification and that it is generated
|
||||
for provided header.
|
||||
|
||||
There are two main things in GRANDPA that help building light clients:
|
||||
|
||||
- there's no need to import all headers of the bridged chain. Light client may import finalized headers or just
|
||||
some of finalized headers that it consider useful. While the validators set stays the same, the client may
|
||||
import any header that is finalized by this set;
|
||||
|
||||
- when validators set changes, the GRANDPA gadget adds next set to the header. So light client doesn't need to
|
||||
verify storage proofs when this happens - it only needs to look at the header and see if it changes the set.
|
||||
Once set is changed, all following justifications are generated by the new set. Header that is changing the
|
||||
set is called "mandatory" in the pezpallet. As the name says, the light client need to import all such headers
|
||||
to be able to operate properly.
|
||||
|
||||
## Pezpallet Operations
|
||||
|
||||
The main entrypoint of the pezpallet is the `submit_finality_proof_ex` call. It has three arguments - the finalized
|
||||
headers, associated GRANDPA justification and ID of the authority set that has generated this justification. The
|
||||
call simply verifies the justification using current validators set and checks if header is better than the
|
||||
previous best header. If both checks are passed, the header (only its useful fields) is inserted into the runtime
|
||||
storage and may be used by other pallets to verify storage proofs.
|
||||
|
||||
The submitter pays regular fee for submitting all headers, except for the mandatory header. Since it is
|
||||
required for the pezpallet operations, submitting such header is free. So if you're ok with session-length
|
||||
lags (meaning that there's exactly 1 mandatory header per session), the cost of pezpallet calls is zero.
|
||||
|
||||
When the pezpallet sees mandatory header, it updates the validators set with the set from the header. All
|
||||
following justifications (until next mandatory header) must be generated by this new set.
|
||||
|
||||
## Pezpallet Initialization
|
||||
|
||||
As the previous section states, there are two things that are mandatory for pezpallet operations: best finalized
|
||||
header and the current validators set. Without it the pezpallet can't import any headers. But how to provide
|
||||
initial values for these fields? There are two options.
|
||||
|
||||
First option, while it is easier, doesn't work in all cases. It is to start chain with initial header and
|
||||
validators set specified in the chain specification. This won't work, however, if we want to add bridge
|
||||
to already started chain.
|
||||
|
||||
For the latter case we have the `initialize` call. It accepts the initial header and initial validators set.
|
||||
The call may be called by the governance, root or by the pezpallet owner (if it is set).
|
||||
|
||||
## Non-Essential Functionality
|
||||
|
||||
There may be a special account in every runtime where the bridge GRANDPA module is deployed. This
|
||||
account, named 'module owner', is like a module-level sudo account - he's able to halt and
|
||||
resume all module operations without requiring runtime upgrade. Calls that are related to this
|
||||
account are:
|
||||
|
||||
- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account;
|
||||
|
||||
- `fn set_operating_mode()`: the module owner (or sudo account) may call this function to stop all
|
||||
module operations. After this call, all finality proofs will be rejected until further `set_operating_mode` call'.
|
||||
This call may be used when something extraordinary happens with the bridge;
|
||||
|
||||
- `fn initialize()`: module owner may call this function to initialize the bridge.
|
||||
|
||||
If pezpallet owner is not defined, the governance may be used to make those calls.
|
||||
|
||||
## Signed Extension to Reject Obsolete Headers
|
||||
|
||||
It'd be better for anyone (for chain and for submitters) to reject all transactions that are submitting
|
||||
already known headers to the pezpallet. This way, we leave block space to other useful transactions and
|
||||
we don't charge concurrent submitters for their honest actions.
|
||||
|
||||
To deal with that, we have a [signed extension](./src/call_ext.rs) that may be added to the runtime.
|
||||
It does exactly what is required - rejects all transactions with already known headers. The submitter
|
||||
pays nothing for such transactions - they're simply removed from the transaction pool, when the block
|
||||
is built.
|
||||
|
||||
You may also take a look at the [`generate_bridge_reject_obsolete_headers_and_messages`](../../bin/runtime-common/src/lib.rs)
|
||||
macro that bundles several similar signed extensions in a single one.
|
||||
|
||||
## GRANDPA Finality Relay
|
||||
|
||||
We have an offchain actor, who is watching for GRANDPA justifications and submits them to the bridged chain.
|
||||
It is the finality relay - you may look at the [crate level documentation and the code](../../relays/finality/).
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Benchmarks for the GRANDPA Pezpallet.
|
||||
//!
|
||||
//! The main dispatchable for the GRANDPA pezpallet is `submit_finality_proof_ex`. Our benchmarks
|
||||
//! are based around `submit_finality_proof`, though - from weight PoV they are the same calls.
|
||||
//! There are to main factors which affect finality proof verification:
|
||||
//!
|
||||
//! 1. The number of `votes-ancestries` in the justification
|
||||
//! 2. The number of `pre-commits` in the justification
|
||||
//!
|
||||
//! Vote ancestries are the headers between (`finality_target`, `head_of_chain`], where
|
||||
//! `header_of_chain` is a descendant of `finality_target`.
|
||||
//!
|
||||
//! Pre-commits are messages which are signed by validators at the head of the chain they think is
|
||||
//! the best.
|
||||
//!
|
||||
//! Consider the following:
|
||||
//!
|
||||
//! / B <- C'
|
||||
//! A <- B <- C
|
||||
//!
|
||||
//! The common ancestor of both forks is block A, so this is what GRANDPA will finalize. In order to
|
||||
//! verify this we will have vote ancestries of `[B, C, B', C']` and pre-commits `[C, C']`.
|
||||
//!
|
||||
//! Note that the worst case scenario here would be a justification where each validator has it's
|
||||
//! own fork which is `SESSION_LENGTH` blocks long.
|
||||
|
||||
use crate::*;
|
||||
|
||||
use bp_header_pez_chain::justification::required_justification_precommits;
|
||||
use pezbp_runtime::BasicOperatingMode;
|
||||
use bp_test_utils::{
|
||||
accounts, make_justification_for_header, JustificationGeneratorParams, TEST_GRANDPA_ROUND,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
};
|
||||
use pezframe_benchmarking::{benchmarks_instance_pallet, whitelisted_caller};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_consensus_grandpa::AuthorityId;
|
||||
use pezsp_runtime::traits::{One, Zero};
|
||||
use pezsp_std::vec::Vec;
|
||||
|
||||
/// The maximum number of vote ancestries to include in a justification.
|
||||
///
|
||||
/// In practice this would be limited by the session length (number of blocks a single authority set
|
||||
/// can produce) of a given chain.
|
||||
const MAX_VOTE_ANCESTRIES: u32 = 1000;
|
||||
|
||||
// `1..MAX_VOTE_ANCESTRIES` is too large && benchmarks are running for almost 40m (steps=50,
|
||||
// repeat=20) on a decent laptop, which is too much. Since we're building linear function here,
|
||||
// let's just select some limited subrange for benchmarking.
|
||||
const MAX_VOTE_ANCESTRIES_RANGE_BEGIN: u32 = MAX_VOTE_ANCESTRIES / 20;
|
||||
const MAX_VOTE_ANCESTRIES_RANGE_END: u32 =
|
||||
MAX_VOTE_ANCESTRIES_RANGE_BEGIN + MAX_VOTE_ANCESTRIES_RANGE_BEGIN;
|
||||
|
||||
// the same with validators - if there are too much validators, let's run benchmarks on subrange
|
||||
fn precommits_range_end<T: Config<I>, I: 'static>() -> u32 {
|
||||
let max_bridged_authorities = T::BridgedChain::MAX_AUTHORITIES_COUNT;
|
||||
let max_bridged_authorities = if max_bridged_authorities > 128 {
|
||||
pezsp_std::cmp::max(128, max_bridged_authorities / 5)
|
||||
} else {
|
||||
max_bridged_authorities
|
||||
};
|
||||
|
||||
required_justification_precommits(max_bridged_authorities)
|
||||
}
|
||||
|
||||
/// Prepare header and its justification to submit using `submit_finality_proof`.
|
||||
fn prepare_benchmark_data<T: Config<I>, I: 'static>(
|
||||
precommits: u32,
|
||||
ancestors: u32,
|
||||
) -> (BridgedHeader<T, I>, GrandpaJustification<BridgedHeader<T, I>>) {
|
||||
// going from precommits to total authorities count
|
||||
let total_authorities_count = (3 * precommits - 1) / 2;
|
||||
|
||||
let authority_list = accounts(total_authorities_count as u16)
|
||||
.iter()
|
||||
.map(|id| (AuthorityId::from(*id), 1))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let genesis_header: BridgedHeader<T, I> = bp_test_utils::test_header(Zero::zero());
|
||||
let genesis_hash = genesis_header.hash();
|
||||
let init_data = InitializationData {
|
||||
header: Box::new(genesis_header),
|
||||
authority_list,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
};
|
||||
|
||||
bootstrap_bridge::<T, I>(init_data);
|
||||
assert!(<ImportedHeaders<T, I>>::contains_key(genesis_hash));
|
||||
|
||||
let header: BridgedHeader<T, I> = bp_test_utils::test_header(One::one());
|
||||
let params = JustificationGeneratorParams {
|
||||
header: header.clone(),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: accounts(precommits as u16).iter().map(|k| (*k, 1)).collect::<Vec<_>>(),
|
||||
ancestors,
|
||||
forks: 1,
|
||||
};
|
||||
let justification = make_justification_for_header(params);
|
||||
(header, justification)
|
||||
}
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
// This is the "gold standard" benchmark for this extrinsic, and it's what should be used to
|
||||
// annotate the weight in the pezpallet.
|
||||
submit_finality_proof {
|
||||
let p in 1 .. precommits_range_end::<T, I>();
|
||||
let v in MAX_VOTE_ANCESTRIES_RANGE_BEGIN..MAX_VOTE_ANCESTRIES_RANGE_END;
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let (header, justification) = prepare_benchmark_data::<T, I>(p, v);
|
||||
}: submit_finality_proof(RawOrigin::Signed(caller), Box::new(header), justification)
|
||||
verify {
|
||||
let genesis_header: BridgedHeader<T, I> = bp_test_utils::test_header(Zero::zero());
|
||||
let header: BridgedHeader<T, I> = bp_test_utils::test_header(One::one());
|
||||
let expected_hash = header.hash();
|
||||
|
||||
// check that the header#1 has been inserted
|
||||
assert_eq!(<BestFinalized<T, I>>::get().unwrap().1, expected_hash);
|
||||
assert!(<ImportedHeaders<T, I>>::contains_key(expected_hash));
|
||||
|
||||
// check that the header#0 has been pruned
|
||||
assert!(!<ImportedHeaders<T, I>>::contains_key(genesis_header.hash()));
|
||||
}
|
||||
|
||||
force_set_pallet_state {
|
||||
let set_id = 100;
|
||||
let authorities = accounts(T::BridgedChain::MAX_AUTHORITIES_COUNT as u16)
|
||||
.iter()
|
||||
.map(|id| (AuthorityId::from(*id), 1))
|
||||
.collect::<Vec<_>>();
|
||||
let (header, _) = prepare_benchmark_data::<T, I>(1, 1);
|
||||
let expected_hash = header.hash();
|
||||
}: force_set_pallet_state(RawOrigin::Root, set_id, authorities, Box::new(header))
|
||||
verify {
|
||||
assert_eq!(<BestFinalized<T, I>>::get().unwrap().1, expected_hash);
|
||||
assert_eq!(<CurrentAuthoritySet<T, I>>::get().set_id, set_id);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pezpallet, crate::mock::new_test_ext(), crate::mock::TestRuntime)
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
weights::WeightInfo, BestFinalized, BridgedBlockNumber, BridgedHeader, Config,
|
||||
CurrentAuthoritySet, Error, FreeHeadersRemaining, Pezpallet,
|
||||
};
|
||||
use bp_header_pez_chain::{
|
||||
justification::GrandpaJustification, submit_finality_proof_limits_extras,
|
||||
SubmitFinalityProofInfo,
|
||||
};
|
||||
use pezbp_runtime::{BlockNumberOf, Chain, OwnedBridgeModule};
|
||||
use pezframe_support::{
|
||||
dispatch::CallableCallFor,
|
||||
traits::{Get, IsSubType},
|
||||
weights::Weight,
|
||||
};
|
||||
use pezsp_consensus_grandpa::SetId;
|
||||
use pezsp_runtime::{
|
||||
traits::{CheckedSub, Header, Zero},
|
||||
transaction_validity::{InvalidTransaction, TransactionValidityError},
|
||||
RuntimeDebug, SaturatedConversion,
|
||||
};
|
||||
use pezsp_std::fmt::Debug;
|
||||
|
||||
/// Verified `SubmitFinalityProofInfo<N>`.
|
||||
#[derive(Copy, Clone, PartialEq, RuntimeDebug)]
|
||||
pub struct VerifiedSubmitFinalityProofInfo<N: Debug> {
|
||||
/// Base call information.
|
||||
pub base: SubmitFinalityProofInfo<N>,
|
||||
/// A difference between bundled bridged header and best bridged header known to us
|
||||
/// before the call.
|
||||
pub improved_by: N,
|
||||
}
|
||||
|
||||
/// Helper struct that provides methods for working with the `SubmitFinalityProof` call.
|
||||
pub struct SubmitFinalityProofHelper<T: Config<I>, I: 'static> {
|
||||
_phantom_data: pezsp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
|
||||
/// Returns `true` if we may fit more free headers into the current block. If `false` is
|
||||
/// returned, the call will be paid even if `is_free_execution_expected` has been set
|
||||
/// to `true`.
|
||||
pub fn has_free_header_slots() -> bool {
|
||||
// `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept
|
||||
// this header for free. That is a small cheat - it is `None` if executed outside of
|
||||
// transaction (e.g. during block initialization). Normal relayer would never submit
|
||||
// such calls, but if he did, that is not our problem. During normal transactions,
|
||||
// the `FreeHeadersRemaining` is always `Some(_)`.
|
||||
let free_headers_remaining = FreeHeadersRemaining::<T, I>::get().unwrap_or(u32::MAX);
|
||||
free_headers_remaining > 0
|
||||
}
|
||||
|
||||
/// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the
|
||||
/// best one we know (2) if `current_set_id` matches the current authority set id, if specified
|
||||
/// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected`
|
||||
/// is `true`.
|
||||
///
|
||||
/// Returns number of headers between the current best finalized header, known to the pezpallet
|
||||
/// and the bundled header.
|
||||
pub fn check_obsolete_from_extension(
|
||||
call_info: &SubmitFinalityProofInfo<BlockNumberOf<T::BridgedChain>>,
|
||||
) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
|
||||
// do basic checks first
|
||||
let improved_by = Self::check_obsolete(call_info.block_number, call_info.current_set_id)?;
|
||||
|
||||
// if submitter has NOT specified that it wants free execution, then we are done
|
||||
if !call_info.is_free_execution_expected {
|
||||
return Ok(improved_by);
|
||||
}
|
||||
|
||||
// else - if we can not accept more free headers, "reject" the transaction
|
||||
if !Self::has_free_header_slots() {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
chain_id=?T::BridgedChain::ID,
|
||||
block_number=?call_info.block_number,
|
||||
"Cannot accept free header. No more free slots remaining"
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::FreeHeadersLimitExceded);
|
||||
}
|
||||
|
||||
// ensure that the `improved_by` is larger than the configured free interval
|
||||
if !call_info.is_mandatory {
|
||||
if let Some(free_headers_interval) = T::FreeHeadersInterval::get() {
|
||||
if improved_by < free_headers_interval.into() {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
chain_id=?T::BridgedChain::ID,
|
||||
block_number=?call_info.block_number,
|
||||
?improved_by,
|
||||
%free_headers_interval,
|
||||
"Cannot accept free header. Too small difference between submitted headers"
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::BelowFreeHeaderInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let's also check whether the header submission fits the hardcoded limits. A normal
|
||||
// relayer would check that before submitting a transaction (since limits are constants
|
||||
// and do not depend on a volatile runtime state), but the ckeck itself is cheap, so
|
||||
// let's do it here too
|
||||
if !call_info.fits_limits() {
|
||||
return Err(Error::<T, I>::HeaderOverflowLimits);
|
||||
}
|
||||
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best
|
||||
/// one we know. Additionally, checks if `current_set_id` matches the current authority set
|
||||
/// id, if specified. This method is called by the call code and the transaction extension,
|
||||
/// so it does not check the free execution.
|
||||
///
|
||||
/// Returns number of headers between the current best finalized header, known to the pezpallet
|
||||
/// and the bundled header.
|
||||
pub fn check_obsolete(
|
||||
finality_target: BlockNumberOf<T::BridgedChain>,
|
||||
current_set_id: Option<SetId>,
|
||||
) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
|
||||
let best_finalized = BestFinalized::<T, I>::get().ok_or_else(|| {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
header=?finality_target,
|
||||
"Cannot finalize header because pezpallet is not yet initialized"
|
||||
);
|
||||
<Error<T, I>>::NotInitialized
|
||||
})?;
|
||||
|
||||
let improved_by = match finality_target.checked_sub(&best_finalized.number()) {
|
||||
Some(improved_by) if improved_by > Zero::zero() => improved_by,
|
||||
_ => {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
bundled=?finality_target,
|
||||
best=?best_finalized,
|
||||
"Cannot finalize obsolete header"
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::OldHeader);
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(current_set_id) = current_set_id {
|
||||
let actual_set_id = <CurrentAuthoritySet<T, I>>::get().set_id;
|
||||
if current_set_id != actual_set_id {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
bundled=?current_set_id,
|
||||
best=?actual_set_id,
|
||||
"Cannot finalize header signed by unknown authority set"
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::InvalidAuthoritySetId);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check if the `SubmitFinalityProof` was successfully executed.
|
||||
pub fn was_successful(finality_target: BlockNumberOf<T::BridgedChain>) -> bool {
|
||||
match BestFinalized::<T, I>::get() {
|
||||
Some(best_finalized) => best_finalized.number() == finality_target,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing a call that is a sub type of this pezpallet's call.
|
||||
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||
IsSubType<CallableCallFor<Pezpallet<T, I>, T>>
|
||||
{
|
||||
/// Extract finality proof info from a runtime call.
|
||||
fn submit_finality_proof_info(
|
||||
&self,
|
||||
) -> Option<SubmitFinalityProofInfo<BridgedBlockNumber<T, I>>> {
|
||||
if let Some(crate::Call::<T, I>::submit_finality_proof { finality_target, justification }) =
|
||||
self.is_sub_type()
|
||||
{
|
||||
return Some(submit_finality_proof_info_from_args::<T, I>(
|
||||
finality_target,
|
||||
justification,
|
||||
None,
|
||||
false,
|
||||
));
|
||||
} else if let Some(crate::Call::<T, I>::submit_finality_proof_ex {
|
||||
finality_target,
|
||||
justification,
|
||||
current_set_id,
|
||||
is_free_execution_expected,
|
||||
}) = self.is_sub_type()
|
||||
{
|
||||
return Some(submit_finality_proof_info_from_args::<T, I>(
|
||||
finality_target,
|
||||
justification,
|
||||
Some(*current_set_id),
|
||||
*is_free_execution_expected,
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Validate Grandpa headers in order to avoid "mining" transactions that provide outdated
|
||||
/// bridged chain headers. Without this validation, even honest relayers may lose their funds
|
||||
/// if there are multiple relays running and submitting the same information.
|
||||
///
|
||||
/// Returns `Ok(None)` if the call is not the `submit_finality_proof` call of our pezpallet.
|
||||
/// Returns `Ok(Some(_))` if the call is the `submit_finality_proof` call of our pezpallet and
|
||||
/// we believe the call brings header that improves the pezpallet state.
|
||||
/// Returns `Err(_)` if the call is the `submit_finality_proof` call of our pezpallet and we
|
||||
/// believe that the call will fail.
|
||||
fn check_obsolete_submit_finality_proof(
|
||||
&self,
|
||||
) -> Result<
|
||||
Option<VerifiedSubmitFinalityProofInfo<BridgedBlockNumber<T, I>>>,
|
||||
TransactionValidityError,
|
||||
>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let call_info = match self.submit_finality_proof_info() {
|
||||
Some(finality_proof) => finality_proof,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
if Pezpallet::<T, I>::ensure_not_halted().is_err() {
|
||||
return Err(InvalidTransaction::Call.into());
|
||||
}
|
||||
|
||||
let result = SubmitFinalityProofHelper::<T, I>::check_obsolete_from_extension(&call_info);
|
||||
match result {
|
||||
Ok(improved_by) =>
|
||||
Ok(Some(VerifiedSubmitFinalityProofInfo { base: call_info, improved_by })),
|
||||
Err(Error::<T, I>::OldHeader) => Err(InvalidTransaction::Stale.into()),
|
||||
Err(_) => Err(InvalidTransaction::Call.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> CallSubType<T, I> for T::RuntimeCall where
|
||||
T::RuntimeCall: IsSubType<CallableCallFor<Pezpallet<T, I>, T>>
|
||||
{
|
||||
}
|
||||
|
||||
/// Extract finality proof info from the submitted header and justification.
|
||||
pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
|
||||
finality_target: &BridgedHeader<T, I>,
|
||||
justification: &GrandpaJustification<BridgedHeader<T, I>>,
|
||||
current_set_id: Option<SetId>,
|
||||
is_free_execution_expected: bool,
|
||||
) -> SubmitFinalityProofInfo<BridgedBlockNumber<T, I>> {
|
||||
// check if call exceeds limits. In other words - whether some size or weight is included
|
||||
// in the call
|
||||
let extras =
|
||||
submit_finality_proof_limits_extras::<T::BridgedChain>(finality_target, justification);
|
||||
|
||||
// We do care about extra weight because of more-than-expected headers in the votes
|
||||
// ancestries. But we have problems computing extra weight for additional headers (weight of
|
||||
// additional header is too small, so that our benchmarks aren't detecting that). So if there
|
||||
// are more than expected headers in votes ancestries, we will treat the whole call weight
|
||||
// as an extra weight.
|
||||
let extra_weight = if extras.is_weight_limit_exceeded {
|
||||
let precommits_len = justification.commit.precommits.len().saturated_into();
|
||||
let votes_ancestries_len = justification.votes_ancestries.len().saturated_into();
|
||||
T::WeightInfo::submit_finality_proof(precommits_len, votes_ancestries_len)
|
||||
} else {
|
||||
Weight::zero()
|
||||
};
|
||||
|
||||
SubmitFinalityProofInfo {
|
||||
block_number: *finality_target.number(),
|
||||
current_set_id,
|
||||
is_mandatory: extras.is_mandatory_finality_target,
|
||||
is_free_execution_expected,
|
||||
extra_weight,
|
||||
extra_size: extras.extra_size,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
call_ext::CallSubType,
|
||||
mock::{
|
||||
run_test, test_header, FreeHeadersInterval, RuntimeCall, TestBridgedChain, TestNumber,
|
||||
TestRuntime,
|
||||
},
|
||||
BestFinalized, Config, CurrentAuthoritySet, FreeHeadersRemaining, PalletOperatingMode,
|
||||
StoredAuthoritySet, WeightInfo,
|
||||
};
|
||||
use bp_header_pez_chain::{ChainWithGrandpa, SubmitFinalityProofInfo};
|
||||
use pezbp_runtime::{BasicOperatingMode, HeaderId};
|
||||
use bp_test_utils::{
|
||||
make_default_justification, make_justification_for_header, JustificationGeneratorParams,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
};
|
||||
use codec::Encode;
|
||||
use pezframe_support::weights::Weight;
|
||||
use pezsp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion};
|
||||
|
||||
fn validate_block_submit(num: TestNumber) -> bool {
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(num)),
|
||||
justification: make_default_justification(&test_header(num)),
|
||||
// not initialized => zero
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: false,
|
||||
};
|
||||
RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call,
|
||||
))
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn sync_to_header_10() {
|
||||
let header10_hash = pezsp_core::H256::default();
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(10, header10_hash));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_obsolete_header() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#5 => tx is
|
||||
// rejected
|
||||
sync_to_header_10();
|
||||
assert!(!validate_block_submit(5));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_same_header() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
||||
// rejected
|
||||
sync_to_header_10();
|
||||
assert!(!validate_block_submit(10));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_pallet_is_halted() {
|
||||
run_test(|| {
|
||||
// when pezpallet is halted => tx is rejected
|
||||
sync_to_header_10();
|
||||
PalletOperatingMode::<TestRuntime, ()>::put(BasicOperatingMode::Halted);
|
||||
|
||||
assert!(!validate_block_submit(15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_set_id_is_invalid() {
|
||||
run_test(|| {
|
||||
// when set id is different from the passed one => tx is rejected
|
||||
sync_to_header_10();
|
||||
let next_set = StoredAuthoritySet::<TestRuntime, ()>::try_new(vec![], 0x42).unwrap();
|
||||
CurrentAuthoritySet::<TestRuntime, ()>::put(next_set);
|
||||
|
||||
assert!(!validate_block_submit(15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_free_execution_is_requested_and_free_submissions_are_not_accepted(
|
||||
) {
|
||||
run_test(|| {
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(10 + FreeHeadersInterval::get() as u64)),
|
||||
justification: make_default_justification(&test_header(
|
||||
10 + FreeHeadersInterval::get() as u64,
|
||||
)),
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// when we can accept free headers => Ok
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(2);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
|
||||
// when we can NOT accept free headers => Err
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(0);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
|
||||
// when called outside of transaction => Ok
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::kill();
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call,
|
||||
),)
|
||||
.is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_it_overflow_size_limits() {
|
||||
run_test(|| {
|
||||
let mut large_finality_target = test_header(10 + FreeHeadersInterval::get() as u64);
|
||||
large_finality_target
|
||||
.digest_mut()
|
||||
.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
|
||||
let justification_params = JustificationGeneratorParams {
|
||||
header: large_finality_target.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let large_justification = make_justification_for_header(justification_params);
|
||||
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(large_finality_target),
|
||||
justification: large_justification,
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// if overflow size limits => Err
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(2);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_it_overflow_weight_limits() {
|
||||
run_test(|| {
|
||||
let finality_target = test_header(10 + FreeHeadersInterval::get() as u64);
|
||||
let justification_params = JustificationGeneratorParams {
|
||||
header: finality_target.clone(),
|
||||
ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
|
||||
..Default::default()
|
||||
};
|
||||
let justification = make_justification_for_header(justification_params);
|
||||
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(finality_target),
|
||||
justification,
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// if overflow weight limits => Err
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(2);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_free_execution_is_requested_and_improved_by_is_below_expected(
|
||||
) {
|
||||
run_test(|| {
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(100)),
|
||||
justification: make_default_justification(&test_header(100)),
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// when `improved_by` is less than the free interval
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64 + 1,
|
||||
pezsp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
|
||||
// when `improved_by` is equal to the free interval
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64,
|
||||
pezsp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
|
||||
// when `improved_by` is larger than the free interval
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64 - 1,
|
||||
pezsp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
|
||||
// when `improved_by` is less than the free interval BUT it is a mandatory header
|
||||
let mut mandatory_header = test_header(100);
|
||||
let consensus_log = pezsp_consensus_grandpa::ConsensusLog::<TestNumber>::ScheduledChange(
|
||||
pezsp_consensus_grandpa::ScheduledChange {
|
||||
next_authorities: bp_test_utils::authority_list(),
|
||||
delay: 0,
|
||||
},
|
||||
);
|
||||
mandatory_header.digest = pezsp_runtime::Digest {
|
||||
logs: vec![DigestItem::Consensus(
|
||||
pezsp_consensus_grandpa::GRANDPA_ENGINE_ID,
|
||||
consensus_log.encode(),
|
||||
)],
|
||||
};
|
||||
let justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: mandatory_header.clone(),
|
||||
set_id: 1,
|
||||
..Default::default()
|
||||
});
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(mandatory_header),
|
||||
justification,
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64 + 1,
|
||||
pezsp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_header() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#15 => tx is
|
||||
// accepted
|
||||
sync_to_header_10();
|
||||
assert!(validate_block_submit(15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_finality_proof_info_is_parsed() {
|
||||
// when `submit_finality_proof` is used, `current_set_id` is set to `None`
|
||||
let deprecated_call =
|
||||
RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof {
|
||||
finality_target: Box::new(test_header(42)),
|
||||
justification: make_default_justification(&test_header(42)),
|
||||
});
|
||||
assert_eq!(
|
||||
deprecated_call.submit_finality_proof_info(),
|
||||
Some(SubmitFinalityProofInfo {
|
||||
block_number: 42,
|
||||
current_set_id: None,
|
||||
extra_weight: Weight::zero(),
|
||||
extra_size: 0,
|
||||
is_mandatory: false,
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
);
|
||||
|
||||
// when `submit_finality_proof_ex` is used, `current_set_id` is set to `Some`
|
||||
let deprecated_call =
|
||||
RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(42)),
|
||||
justification: make_default_justification(&test_header(42)),
|
||||
current_set_id: 777,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(
|
||||
deprecated_call.submit_finality_proof_info(),
|
||||
Some(SubmitFinalityProofInfo {
|
||||
block_number: 42,
|
||||
current_set_id: Some(777),
|
||||
extra_weight: Weight::zero(),
|
||||
extra_size: 0,
|
||||
is_mandatory: false,
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_returns_correct_extra_size_if_call_arguments_are_too_large() {
|
||||
// when call arguments are below our limit => no refund
|
||||
let small_finality_target = test_header(1);
|
||||
let justification_params = JustificationGeneratorParams {
|
||||
header: small_finality_target.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let small_justification = make_justification_for_header(justification_params);
|
||||
let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
|
||||
finality_target: Box::new(small_finality_target),
|
||||
justification: small_justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0);
|
||||
|
||||
// when call arguments are too large => partial refund
|
||||
let mut large_finality_target = test_header(1);
|
||||
large_finality_target
|
||||
.digest_mut()
|
||||
.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
|
||||
let justification_params = JustificationGeneratorParams {
|
||||
header: large_finality_target.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let large_justification = make_justification_for_header(justification_params);
|
||||
let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
|
||||
finality_target: Box::new(large_finality_target),
|
||||
justification: large_justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_returns_correct_extra_weight_if_there_are_too_many_headers_in_votes_ancestry() {
|
||||
let finality_target = test_header(1);
|
||||
let mut justification_params = JustificationGeneratorParams {
|
||||
header: finality_target.clone(),
|
||||
ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// when there are `REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY` headers => no refund
|
||||
let justification = make_justification_for_header(justification_params.clone());
|
||||
let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
|
||||
finality_target: Box::new(finality_target.clone()),
|
||||
justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero());
|
||||
|
||||
// when there are `REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY + 1` headers => full refund
|
||||
justification_params.ancestors += 1;
|
||||
let justification = make_justification_for_header(justification_params);
|
||||
let call_weight = <TestRuntime as Config>::WeightInfo::submit_finality_proof(
|
||||
justification.commit.precommits.len().saturated_into(),
|
||||
justification.votes_ancestries.len().saturated_into(),
|
||||
);
|
||||
let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
|
||||
finality_target: Box::new(finality_target),
|
||||
justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_obsolete_submit_finality_proof_returns_correct_improved_by() {
|
||||
run_test(|| {
|
||||
fn make_call(number: u64) -> RuntimeCall {
|
||||
RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(number)),
|
||||
justification: make_default_justification(&test_header(number)),
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
}
|
||||
|
||||
sync_to_header_10();
|
||||
|
||||
// when the difference between headers is 1
|
||||
assert_eq!(
|
||||
RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.improved_by,
|
||||
1,
|
||||
);
|
||||
|
||||
// when the difference between headers is 2
|
||||
assert_eq!(
|
||||
RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.improved_by,
|
||||
2,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_obsolete_submit_finality_proof_ignores_other_calls() {
|
||||
run_test(|| {
|
||||
let call =
|
||||
RuntimeCall::System(pezframe_system::Call::<TestRuntime>::remark { remark: vec![42] });
|
||||
|
||||
assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call), Ok(None));
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// From construct_runtime macro
|
||||
#![allow(clippy::from_over_into)]
|
||||
|
||||
use bp_header_pez_chain::ChainWithGrandpa;
|
||||
use pezbp_runtime::{Chain, ChainId};
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types, pezsp_runtime::StateVersion, traits::Hooks,
|
||||
weights::Weight,
|
||||
};
|
||||
use pezsp_core::sr25519::Signature;
|
||||
|
||||
pub type AccountId = u64;
|
||||
pub type TestHeader = pezsp_runtime::testing::Header;
|
||||
pub type TestNumber = u64;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
pub const MAX_BRIDGED_AUTHORITIES: u32 = 5;
|
||||
|
||||
use crate as grandpa;
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Grandpa: grandpa::{Pezpallet, Call, Event<T>},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxFreeHeadersPerBlock: u32 = 2;
|
||||
pub const FreeHeadersInterval: u32 = 32;
|
||||
pub const HeadersToKeep: u32 = 5;
|
||||
}
|
||||
|
||||
impl grandpa::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type MaxFreeHeadersPerBlock = MaxFreeHeadersPerBlock;
|
||||
type FreeHeadersInterval = FreeHeadersInterval;
|
||||
type HeadersToKeep = HeadersToKeep;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBridgedChain;
|
||||
|
||||
impl Chain for TestBridgedChain {
|
||||
const ID: ChainId = *b"tbch";
|
||||
|
||||
type BlockNumber = pezframe_system::pezpallet_prelude::BlockNumberFor<TestRuntime>;
|
||||
type Hash = <TestRuntime as pezframe_system::Config>::Hash;
|
||||
type Hasher = <TestRuntime as pezframe_system::Config>::Hashing;
|
||||
type Header = TestHeader;
|
||||
|
||||
type AccountId = AccountId;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = Signature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
unreachable!()
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for TestBridgedChain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = MAX_BRIDGED_AUTHORITIES;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 64;
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
pezsp_io::TestExternalities::new(Default::default())
|
||||
}
|
||||
|
||||
/// Return test within default test externalities context.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
let _ = Grandpa::on_initialize(0);
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
/// Return test header with given number.
|
||||
pub fn test_header(num: TestNumber) -> TestHeader {
|
||||
// We wrap the call to avoid explicit type annotations in our tests
|
||||
bp_test_utils::test_header(num)
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Wrappers for public types that are implementing `MaxEncodedLen`
|
||||
|
||||
use crate::{Config, Error};
|
||||
|
||||
use bp_header_pez_chain::{AuthoritySet, ChainWithGrandpa};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{traits::Get, BoundedVec, CloneNoBound, RuntimeDebugNoBound};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// A bounded list of Grandpa authorities with associated weights.
|
||||
pub type StoredAuthorityList<MaxBridgedAuthorities> =
|
||||
BoundedVec<(AuthorityId, AuthorityWeight), MaxBridgedAuthorities>;
|
||||
|
||||
/// Adapter for using `T::BridgedChain::MAX_BRIDGED_AUTHORITIES` in `BoundedVec`.
|
||||
pub struct StoredAuthorityListLimit<T, I>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> Get<u32> for StoredAuthorityListLimit<T, I> {
|
||||
fn get() -> u32 {
|
||||
T::BridgedChain::MAX_AUTHORITIES_COUNT
|
||||
}
|
||||
}
|
||||
|
||||
/// A bounded GRANDPA Authority List and ID.
|
||||
#[derive(CloneNoBound, Decode, Encode, Eq, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)]
|
||||
#[scale_info(skip_type_params(T, I))]
|
||||
pub struct StoredAuthoritySet<T: Config<I>, I: 'static> {
|
||||
/// List of GRANDPA authorities for the current round.
|
||||
pub authorities: StoredAuthorityList<StoredAuthorityListLimit<T, I>>,
|
||||
/// Monotonic identifier of the current GRANDPA authority set.
|
||||
pub set_id: SetId,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> StoredAuthoritySet<T, I> {
|
||||
/// Try to create a new bounded GRANDPA Authority Set from unbounded list.
|
||||
///
|
||||
/// Returns error if number of authorities in the provided list is too large.
|
||||
pub fn try_new(authorities: AuthorityList, set_id: SetId) -> Result<Self, Error<T, I>> {
|
||||
Ok(Self {
|
||||
authorities: TryFrom::try_from(authorities)
|
||||
.map_err(|_| Error::TooManyAuthoritiesInSet)?,
|
||||
set_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns number of bytes that may be subtracted from the PoV component of
|
||||
/// `submit_finality_proof` call, because the actual authorities set is smaller than the maximal
|
||||
/// configured.
|
||||
///
|
||||
/// Maximal authorities set size is configured by the `MaxBridgedAuthorities` constant from
|
||||
/// the pezpallet configuration. The PoV of the call includes the size of maximal authorities
|
||||
/// count. If the actual size is smaller, we may subtract extra bytes from this component.
|
||||
pub fn unused_proof_size(&self) -> u64 {
|
||||
// we can only safely estimate bytes that are occupied by the authority data itself. We have
|
||||
// no means here to compute PoV bytes, occupied by extra trie nodes or extra bytes in the
|
||||
// whole set encoding
|
||||
let single_authority_max_encoded_len =
|
||||
<(AuthorityId, AuthorityWeight)>::max_encoded_len() as u64;
|
||||
let extra_authorities =
|
||||
T::BridgedChain::MAX_AUTHORITIES_COUNT.saturating_sub(self.authorities.len() as _);
|
||||
single_authority_max_encoded_len.saturating_mul(extra_authorities as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> PartialEq for StoredAuthoritySet<T, I> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.set_id == other.set_id && self.authorities == other.authorities
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for StoredAuthoritySet<T, I> {
|
||||
fn default() -> Self {
|
||||
StoredAuthoritySet { authorities: BoundedVec::default(), set_id: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> From<StoredAuthoritySet<T, I>> for AuthoritySet {
|
||||
fn from(t: StoredAuthoritySet<T, I>) -> Self {
|
||||
AuthoritySet { authorities: t.authorities.into(), set_id: t.set_id }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::mock::{TestRuntime, MAX_BRIDGED_AUTHORITIES};
|
||||
use bp_test_utils::authority_list;
|
||||
|
||||
type StoredAuthoritySet = super::StoredAuthoritySet<TestRuntime, ()>;
|
||||
|
||||
#[test]
|
||||
fn unused_proof_size_works() {
|
||||
let authority_entry = authority_list().pop().unwrap();
|
||||
|
||||
// when we have exactly `MaxBridgedAuthorities` authorities
|
||||
assert_eq!(
|
||||
StoredAuthoritySet::try_new(
|
||||
vec![authority_entry.clone(); MAX_BRIDGED_AUTHORITIES as usize],
|
||||
0,
|
||||
)
|
||||
.unwrap()
|
||||
.unused_proof_size(),
|
||||
0,
|
||||
);
|
||||
|
||||
// when we have less than `MaxBridgedAuthorities` authorities
|
||||
assert_eq!(
|
||||
StoredAuthoritySet::try_new(
|
||||
vec![authority_entry; MAX_BRIDGED_AUTHORITIES as usize - 1],
|
||||
0,
|
||||
)
|
||||
.unwrap()
|
||||
.unused_proof_size(),
|
||||
40,
|
||||
);
|
||||
|
||||
// and we can't have more than `MaxBridgedAuthorities` authorities in the bounded vec, so
|
||||
// no test for this case
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_bridge_grandpa
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-03-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/unknown-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_bridge_grandpa
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/grandpa/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pezpallet_bridge_grandpa.
|
||||
pub trait WeightInfo {
|
||||
fn submit_finality_proof(p: u32, v: u32) -> Weight;
|
||||
fn force_set_pallet_state() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_bridge_grandpa` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
/// Storage: BridgeUnknownGrandpa PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa RequestCount (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa RequestCount (max_values: Some(1), max_size: Some(4), added:
|
||||
/// 499, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa BestFinalized (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa BestFinalized (max_values: Some(1), max_size: Some(36), added:
|
||||
/// 531, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa CurrentAuthoritySet (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa CurrentAuthoritySet (max_values: Some(1), max_size: Some(209),
|
||||
/// added: 704, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHashesPointer (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHashesPointer (max_values: Some(1), max_size: Some(4),
|
||||
/// added: 499, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHashes (max_values: Some(14400), max_size: Some(36),
|
||||
/// added: 2016, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `p` is `[1, 4]`.
|
||||
///
|
||||
/// The range of component `v` is `[50, 100]`.
|
||||
fn submit_finality_proof(p: u32, v: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `394 + p * (60 ±0)`
|
||||
// Estimated: `4745`
|
||||
// Minimum execution time: 228_072 nanoseconds.
|
||||
Weight::from_parts(57_853_228, 4745)
|
||||
// Standard Error: 149_421
|
||||
.saturating_add(Weight::from_parts(36_708_702, 0).saturating_mul(p.into()))
|
||||
// Standard Error: 10_625
|
||||
.saturating_add(Weight::from_parts(1_469_032, 0).saturating_mul(v.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(6_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(6_u64))
|
||||
}
|
||||
|
||||
/// Storage: `BridgeZagrosGrandpa::CurrentAuthoritySet` (r:1 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::CurrentAuthoritySet` (`max_values`: Some(1), `max_size`:
|
||||
/// Some(50250), added: 50745, mode: `MaxEncodedLen`)
|
||||
/// Storage: `BridgeZagrosGrandpa::ImportedHashesPointer` (r:1 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::ImportedHashesPointer` (`max_values`: Some(1), `max_size`:
|
||||
/// Some(4), added: 499, mode: `MaxEncodedLen`) Storage: `BridgeZagrosGrandpa::ImportedHashes`
|
||||
/// (r:1 w:1) Proof: `BridgeZagrosGrandpa::ImportedHashes` (`max_values`: Some(1024),
|
||||
/// `max_size`: Some(36), added: 1521, mode: `MaxEncodedLen`)
|
||||
/// Storage: `BridgeZagrosGrandpa::BestFinalized` (r:0 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::BestFinalized` (`max_values`: Some(1), `max_size`: Some(36),
|
||||
/// added: 531, mode: `MaxEncodedLen`) Storage: `BridgeZagrosGrandpa::ImportedHeaders` (r:0
|
||||
/// w:2) Proof: `BridgeZagrosGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`:
|
||||
/// Some(68), added: 1553, mode: `MaxEncodedLen`)
|
||||
fn force_set_pallet_state() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `452`
|
||||
// Estimated: `51735`
|
||||
// Minimum execution time: 62_232_000 picoseconds.
|
||||
Weight::from_parts(78_755_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 51735))
|
||||
.saturating_add(RocksDbWeight::get().reads(3))
|
||||
.saturating_add(RocksDbWeight::get().writes(6))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: BridgeUnknownGrandpa PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa RequestCount (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa RequestCount (max_values: Some(1), max_size: Some(4), added:
|
||||
/// 499, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa BestFinalized (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa BestFinalized (max_values: Some(1), max_size: Some(36), added:
|
||||
/// 531, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa CurrentAuthoritySet (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa CurrentAuthoritySet (max_values: Some(1), max_size: Some(209),
|
||||
/// added: 704, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHashesPointer (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHashesPointer (max_values: Some(1), max_size: Some(4),
|
||||
/// added: 499, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHashes (max_values: Some(14400), max_size: Some(36),
|
||||
/// added: 2016, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `p` is `[1, 4]`.
|
||||
///
|
||||
/// The range of component `v` is `[50, 100]`.
|
||||
fn submit_finality_proof(p: u32, v: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `394 + p * (60 ±0)`
|
||||
// Estimated: `4745`
|
||||
// Minimum execution time: 228_072 nanoseconds.
|
||||
Weight::from_parts(57_853_228, 4745)
|
||||
// Standard Error: 149_421
|
||||
.saturating_add(Weight::from_parts(36_708_702, 0).saturating_mul(p.into()))
|
||||
// Standard Error: 10_625
|
||||
.saturating_add(Weight::from_parts(1_469_032, 0).saturating_mul(v.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(6_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(6_u64))
|
||||
}
|
||||
|
||||
/// Storage: `BridgeZagrosGrandpa::CurrentAuthoritySet` (r:1 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::CurrentAuthoritySet` (`max_values`: Some(1), `max_size`:
|
||||
/// Some(50250), added: 50745, mode: `MaxEncodedLen`)
|
||||
/// Storage: `BridgeZagrosGrandpa::ImportedHashesPointer` (r:1 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::ImportedHashesPointer` (`max_values`: Some(1), `max_size`:
|
||||
/// Some(4), added: 499, mode: `MaxEncodedLen`) Storage: `BridgeZagrosGrandpa::ImportedHashes`
|
||||
/// (r:1 w:1) Proof: `BridgeZagrosGrandpa::ImportedHashes` (`max_values`: Some(1024),
|
||||
/// `max_size`: Some(36), added: 1521, mode: `MaxEncodedLen`)
|
||||
/// Storage: `BridgeZagrosGrandpa::BestFinalized` (r:0 w:1)
|
||||
/// Proof: `BridgeZagrosGrandpa::BestFinalized` (`max_values`: Some(1), `max_size`: Some(36),
|
||||
/// added: 531, mode: `MaxEncodedLen`) Storage: `BridgeZagrosGrandpa::ImportedHeaders` (r:0
|
||||
/// w:2) Proof: `BridgeZagrosGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`:
|
||||
/// Some(68), added: 1553, mode: `MaxEncodedLen`)
|
||||
fn force_set_pallet_state() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `452`
|
||||
// Estimated: `51735`
|
||||
// Minimum execution time: 62_232_000 picoseconds.
|
||||
Weight::from_parts(78_755_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 51735))
|
||||
.saturating_add(RocksDbWeight::get().reads(3))
|
||||
.saturating_add(RocksDbWeight::get().writes(6))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Weight-related utilities.
|
||||
|
||||
use crate::weights::{BridgeWeight, WeightInfo};
|
||||
|
||||
use pezframe_support::weights::Weight;
|
||||
|
||||
/// Extended weight info.
|
||||
pub trait WeightInfoExt: WeightInfo {
|
||||
// Our configuration assumes that the runtime has special signed extensions used to:
|
||||
//
|
||||
// 1) boost priority of `submit_finality_proof` transactions;
|
||||
//
|
||||
// 2) slash relayer if he submits an invalid transaction.
|
||||
//
|
||||
// We read and update storage values of other pallets (`pezpallet-bridge-relayers` and
|
||||
// balances/assets pezpallet). So we need to add this weight to the weight of our call.
|
||||
// Hence two following methods.
|
||||
|
||||
/// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions
|
||||
/// that are declared at runtime level.
|
||||
fn submit_finality_proof_overhead_from_runtime() -> Weight;
|
||||
|
||||
// Functions that are directly mapped to extrinsics weights.
|
||||
|
||||
/// Weight of message delivery extrinsic.
|
||||
fn submit_finality_proof_weight(precommits_len: u32, votes_ancestries_len: u32) -> Weight {
|
||||
let base_weight = Self::submit_finality_proof(precommits_len, votes_ancestries_len);
|
||||
base_weight.saturating_add(Self::submit_finality_proof_overhead_from_runtime())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: pezframe_system::Config> WeightInfoExt for BridgeWeight<T> {
|
||||
fn submit_finality_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightInfoExt for () {
|
||||
fn submit_finality_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-messages"
|
||||
description = "Module that allows bridged chains to exchange messages using lane concept."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-bridge-messages"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-messages = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
pezsp-trie = { optional = true, workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezbp-runtime = { features = ["test-helpers"], workspace = true }
|
||||
bp-test-utils = { workspace = true }
|
||||
pezpallet-balances = { workspace = true }
|
||||
pezpallet-bridge-grandpa = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-messages/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-test-utils/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-bridge-grandpa/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"pezsp-trie/std",
|
||||
"tracing/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezbp-runtime/test-helpers",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-bridge-grandpa/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-trie?/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-bridge-grandpa/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
test-helpers = ["pezbp-runtime/test-helpers", "pezsp-trie"]
|
||||
@@ -0,0 +1,202 @@
|
||||
# Bridge Messages Pezpallet
|
||||
|
||||
The messages pezpallet is used to deliver messages from source chain to target chain. Message is (almost) opaque to the
|
||||
module and the final goal is to hand message to the message dispatch mechanism.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Message Workflow](#message-workflow)
|
||||
- [Integrating Message Lane Module into Runtime](#integrating-messages-module-into-runtime)
|
||||
- [Non-Essential Functionality](#non-essential-functionality)
|
||||
- [Weights of Module Extrinsics](#weights-of-module-extrinsics)
|
||||
|
||||
## Overview
|
||||
|
||||
Message lane is a unidirectional channel, where messages are sent from source chain to the target chain. At the same
|
||||
time, a single instance of messages module supports both outbound lanes and inbound lanes. So the chain where the module
|
||||
is deployed (this chain), may act as a source chain for outbound messages (heading to a bridged chain) and as a target
|
||||
chain for inbound messages (coming from a bridged chain).
|
||||
|
||||
Messages module supports multiple message lanes. Every message lane is identified with a 4-byte identifier. Messages
|
||||
sent through the lane are assigned unique (for this lane) increasing integer value that is known as nonce ("number that
|
||||
can only be used once"). Messages that are sent over the same lane are guaranteed to be delivered to the target chain in
|
||||
the same order they're sent from the source chain. In other words, message with nonce `N` will be delivered right before
|
||||
delivering a message with nonce `N+1`.
|
||||
|
||||
Single message lane may be seen as a transport channel for single application (onchain, offchain or mixed). At the same
|
||||
time the module itself never dictates any lane or message rules. In the end, it is the runtime developer who defines
|
||||
what message lane and message mean for this runtime.
|
||||
|
||||
In our [Kusama<>PezkuwiChain bridge](../../docs/pezkuwi-kusama-bridge-overview.md) we are using lane
|
||||
as a channel of communication between two teyrchains of different relay chains. For example, lane
|
||||
`[0, 0, 0, 0]` is used for PezkuwiChain <> Kusama Asset Hub communications. Other lanes may be used to
|
||||
bridge other teyrchains.
|
||||
|
||||
## Message Workflow
|
||||
|
||||
The pezpallet is not intended to be used by end users and provides no public calls to send the message. Instead, it
|
||||
provides runtime-internal method that allows other pallets (or other runtime code) to queue outbound messages.
|
||||
|
||||
The message "appears" when some runtime code calls the `send_message()` method of the pezpallet. The submitter specifies
|
||||
the lane that they're willing to use and the message itself. If some fee must be paid for sending the message, it must
|
||||
be paid outside of the pezpallet. If a message passes all checks (that include, for example, message size check, disabled
|
||||
lane check, ...), the nonce is assigned and the message is stored in the module storage. The message is in an
|
||||
"undelivered" state now.
|
||||
|
||||
We assume that there are external, offchain actors, called relayers, that are submitting module related transactions to
|
||||
both target and source chains. The pezpallet itself has no assumptions about relayers incentivization scheme, but it has
|
||||
some callbacks for paying rewards. See [Integrating Messages Module into
|
||||
runtime](#Integrating-Messages-Module-into-runtime) for details.
|
||||
|
||||
Eventually, some relayer would notice this message in the "undelivered" state and it would decide to deliver this
|
||||
message. Relayer then crafts `receive_messages_proof()` transaction (aka delivery transaction) for the messages module
|
||||
instance, deployed at the target chain. Relayer provides its account id at the source chain, the proof of message (or
|
||||
several messages), the number of messages in the transaction and their cumulative dispatch weight. Once a transaction is
|
||||
mined, the message is considered "delivered".
|
||||
|
||||
Once a message is delivered, the relayer may want to confirm delivery back to the source chain. There are two reasons
|
||||
why it would want to do that. The first is that we intentionally limit number of "delivered", but not yet "confirmed"
|
||||
messages at inbound lanes (see [What about other Constants in the Messages Module Configuration
|
||||
Trait](#What-about-other-Constants-in-the-Messages-Module-Configuration-Trait) for explanation). So at some point, the
|
||||
target chain may stop accepting new messages until relayers confirm some of these. The second is that if the relayer
|
||||
wants to be rewarded for delivery, it must prove the fact that it has actually delivered the message. And this proof may
|
||||
only be generated after the delivery transaction is mined. So relayer crafts the `receive_messages_delivery_proof()`
|
||||
transaction (aka confirmation transaction) for the messages module instance, deployed at the source chain. Once this
|
||||
transaction is mined, the message is considered "confirmed".
|
||||
|
||||
The "confirmed" state is the final state of the message. But there's one last thing related to the message - the fact
|
||||
that it is now "confirmed" and reward has been paid to the relayer (or at least callback for this has been called), must
|
||||
be confirmed to the target chain. Otherwise, we may reach the limit of "unconfirmed" messages at the target chain and it
|
||||
will stop accepting new messages. So relayer sometimes includes a nonce of the latest "confirmed" message in the next
|
||||
`receive_messages_proof()` transaction, proving that some messages have been confirmed.
|
||||
|
||||
## Integrating Messages Module into Runtime
|
||||
|
||||
As it has been said above, the messages module supports both outbound and inbound message lanes. So if we will integrate
|
||||
a module in some runtime, it may act as the source chain runtime for outbound messages and as the target chain runtime
|
||||
for inbound messages. In this section, we'll sometimes refer to the chain we're currently integrating with, as "this
|
||||
chain" and the other chain as "bridged chain".
|
||||
|
||||
Messages module doesn't simply accept transactions that are claiming that the bridged chain has some updated data for
|
||||
us. Instead of this, the module assumes that the bridged chain is able to prove that updated data in some way. The proof
|
||||
is abstracted from the module and may be of any kind. In our Bizinikiwi-to-Bizinikiwi bridge we're using runtime storage
|
||||
proofs. Other bridges may use transaction proofs, Bizinikiwi header digests or anything else that may be proved.
|
||||
|
||||
**IMPORTANT NOTE**: everything below in this chapter describes details of the messages module configuration. But if
|
||||
you're interested in well-probed and relatively easy integration of two Bizinikiwi-based chains, you may want to look at
|
||||
the [pezbridge-runtime-common](../../bin/runtime-common/) crate. This crate is providing a lot of helpers for integration,
|
||||
which may be directly used from within your runtime. Then if you'll decide to change something in this scheme, get back
|
||||
here for detailed information.
|
||||
|
||||
### General Information
|
||||
|
||||
The messages module supports instances. Every module instance is supposed to bridge this chain and some bridged chain.
|
||||
To bridge with another chain, using another instance is suggested (this isn't forced anywhere in the code, though). Keep
|
||||
in mind, that the pezpallet may be used to build virtual channels between multiple chains, as we do in our [PezkuwiChain <>
|
||||
Kusama bridge](../../docs/pezkuwi-kusama-bridge-overview.md). There, the pezpallet actually bridges only two teyrchains -
|
||||
Kusama Bridge Hub and PezkuwiChain Bridge Hub. However, other Kusama and PezkuwiChain teyrchains are able to send (XCM) messages
|
||||
to their Bridge Hubs. The messages will be delivered to the other side of the bridge and routed to the proper
|
||||
destination teyrchain within the bridged chain consensus.
|
||||
|
||||
Message submitters may track message progress by inspecting module events. When Message is accepted, the
|
||||
`MessageAccepted` event is emitted. The event contains both message lane identifier and nonce that has been assigned to
|
||||
the message. When a message is delivered to the target chain, the `MessagesDelivered` event is emitted from the
|
||||
`receive_messages_delivery_proof()` transaction. The `MessagesDelivered` contains the message lane identifier and
|
||||
inclusive range of delivered message nonces.
|
||||
|
||||
The pezpallet provides no means to get the result of message dispatch at the target chain. If that is
|
||||
required, it must be done outside of the pezpallet. For example, XCM messages, when dispatched, have
|
||||
special instructions to send some data back to the sender. Other dispatchers may use similar
|
||||
mechanism for that.
|
||||
|
||||
### How to plug-in Messages Module to Send and Receive Messages from the Bridged Chain?
|
||||
|
||||
The `pallet_bridge_messages::Config` trait has 2 main associated types that are used to work with
|
||||
inbound messages. The `pallet_bridge_messages::BridgedChain` defines basic primitives of the bridged
|
||||
chain. The `pallet_bridge_messages::BridgedHeaderChain` defines the way we access the bridged chain
|
||||
headers in our runtime. You may use `pallet_bridge_grandpa` if you're bridging with chain that uses
|
||||
GRANDPA finality or `pallet_bridge_teyrchains::TeyrchainHeaders` if you're bridging with teyrchain.
|
||||
|
||||
The `pallet_bridge_messages::Config::MessageDispatch` defines a way on how to dispatch delivered
|
||||
messages. Apart from actually dispatching the message, the implementation must return the correct
|
||||
dispatch weight of the message before dispatch is called.
|
||||
|
||||
The last type is the `pallet_bridge_messages::Config::DeliveryConfirmationPayments`. When confirmation
|
||||
transaction is received, we call the `pay_reward()` method, passing the range of delivered messages.
|
||||
You may use the [`pezpallet-bridge-relayers`](../relayers/) pezpallet and its
|
||||
[`DeliveryConfirmationPaymentsAdapter`](../relayers/src/payment_adapter.rs) adapter as a possible
|
||||
implementation. It allows you to pay fixed reward for relaying the message and some of its portion
|
||||
for confirming delivery.
|
||||
|
||||
### I have a Messages Module in my Runtime, but I Want to Reject all Outbound Messages. What shall I do?
|
||||
|
||||
You should be looking at the `bp_messages::source_chain::ForbidOutboundMessages` structure
|
||||
[`bp_messages::source_chain`](../../primitives/messages/src/source_chain.rs). It implements all required traits and will
|
||||
simply reject all transactions, related to outbound messages.
|
||||
|
||||
### I have a Messages Module in my Runtime, but I Want to Reject all Inbound Messages. What shall I do?
|
||||
|
||||
You should be looking at the `bp_messages::target_chain::ForbidInboundMessages` structure from the
|
||||
[`bp_messages::target_chain`](../../primitives/messages/src/target_chain.rs) module. It implements all required traits
|
||||
and will simply reject all transactions, related to inbound messages.
|
||||
|
||||
### What about other Constants in the Messages Module Configuration Trait?
|
||||
|
||||
`pallet_bridge_messages::Config::MaximalOutboundPayloadSize` constant defines the maximal size
|
||||
of outbound message that may be sent. If the message size is above this limit, the message is
|
||||
rejected.
|
||||
|
||||
To be able to reward the relayer for delivering messages, we store a map of message nonces range =>
|
||||
identifier of the relayer that has delivered this range at the target chain runtime storage. If a
|
||||
relayer delivers multiple consequent ranges, they're merged into single entry. So there may be more
|
||||
than one entry for the same relayer. Eventually, this whole map must be delivered back to the source
|
||||
chain to confirm delivery and pay rewards. So to make sure we are able to craft this confirmation
|
||||
transaction, we need to: (1) keep the size of this map below a certain limit and (2) make sure that
|
||||
the weight of processing this map is below a certain limit. Both size and processing weight mostly
|
||||
depend on the number of entries. The number of entries is limited with the
|
||||
`pallet_bridge_messages::Config::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` parameter.
|
||||
Processing weight also depends on the total number of messages that are being confirmed, because every
|
||||
confirmed message needs to be read. So there's another
|
||||
`pallet_bridge_messages::Config::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX` parameter
|
||||
for that.
|
||||
|
||||
When choosing values for these parameters, you must also keep in mind that if proof in your scheme
|
||||
is based on finality of headers (and it is the most obvious option for Bizinikiwi-based chains with
|
||||
finality notion), then choosing too small values for these parameters may cause significant delays
|
||||
in message delivery. That's because there are too many actors involved in this scheme: 1) authorities
|
||||
that are finalizing headers of the target chain need to finalize header with non-empty map; 2) the
|
||||
headers relayer then needs to submit this header and its finality proof to the source chain; 3) the
|
||||
messages relayer must then send confirmation transaction (storage proof of this map) to the source
|
||||
chain; 4) when the confirmation transaction will be mined at some header, source chain authorities
|
||||
must finalize this header; 5) the headers relay then needs to submit this header and its finality
|
||||
proof to the target chain; 6) only now the messages relayer may submit new messages from the source
|
||||
to target chain and prune the entry from the map.
|
||||
|
||||
Delivery transaction requires the relayer to provide both number of entries and total number of
|
||||
messages in the map. This means that the module never charges an extra cost for delivering a map -
|
||||
the relayer would need to pay exactly for the number of entries+messages it has delivered. So the
|
||||
best guess for values of these parameters would be the pair that would occupy `N` percent of the
|
||||
maximal transaction size and weight of the source chain. The `N` should be large enough to process
|
||||
large maps, at the same time keeping reserve for future source chain upgrades.
|
||||
|
||||
## Non-Essential Functionality
|
||||
|
||||
There may be a special account in every runtime where the messages module is deployed. This account, named 'module
|
||||
owner', is like a module-level sudo account - he's able to halt and resume all module operations without requiring
|
||||
runtime upgrade. Calls that are related to this account are:
|
||||
- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account;
|
||||
- `fn set_operating_mode()`: the module owner (or sudo account) may call this function to pause/resume
|
||||
pezpallet operations. Owner may halt the pezpallet by calling this method with
|
||||
`MessagesOperatingMode::Basic(BasicOperatingMode::Halted)` argument - all message-related
|
||||
transactions will be rejected. Owner may then resume pezpallet operations by passing the
|
||||
`MessagesOperatingMode::Basic(BasicOperatingMode::Normal)` argument. There's also
|
||||
`MessagesOperatingMode::RejectingOutboundMessages` pezpallet mode, where it still accepts all incoming
|
||||
messages, but all outbound messages are rejected.
|
||||
|
||||
If pezpallet owner is not defined, the governance may be used to make those calls.
|
||||
|
||||
## Messages Relay
|
||||
|
||||
We have an offchain actor, who is watching for new messages and submits them to the bridged chain. It is the messages
|
||||
relay - you may look at the [crate level documentation and the code](../../relays/messages/).
|
||||
@@ -0,0 +1,552 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Messages pezpallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::{
|
||||
active_outbound_lane, weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH, BridgedChainOf, Call,
|
||||
InboundLanes, OutboundLanes,
|
||||
};
|
||||
|
||||
use bp_messages::{
|
||||
source_chain::FromBridgedChainMessagesDeliveryProof,
|
||||
target_chain::FromBridgedChainMessagesProof, ChainWithMessages, DeliveredMessages,
|
||||
InboundLaneData, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer,
|
||||
UnrewardedRelayersState,
|
||||
};
|
||||
use pezbp_runtime::{AccountIdOf, HashOf, UnverifiedStorageProofParams};
|
||||
use codec::Decode;
|
||||
use pezframe_benchmarking::{account, v2::*};
|
||||
use pezframe_support::weights::Weight;
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_runtime::{traits::TrailingZeroInput, BoundedVec};
|
||||
use pezsp_std::{ops::RangeInclusive, prelude::*};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
/// Pezpallet we're benchmarking here.
|
||||
pub struct Pezpallet<T: Config<I>, I: 'static = ()>(crate::Pezpallet<T, I>);
|
||||
|
||||
/// Benchmark-specific message proof parameters.
|
||||
#[derive(Debug)]
|
||||
pub struct MessageProofParams<LaneId> {
|
||||
/// Id of the lane.
|
||||
pub lane: LaneId,
|
||||
/// Range of messages to include in the proof.
|
||||
pub message_nonces: RangeInclusive<MessageNonce>,
|
||||
/// If `Some`, the proof needs to include this outbound lane data.
|
||||
pub outbound_lane_data: Option<OutboundLaneData>,
|
||||
/// If `true`, the caller expects that the proof will contain correct messages that will
|
||||
/// be successfully dispatched. This is only called from the "optional"
|
||||
/// `receive_single_message_proof_with_dispatch` benchmark. If you don't need it, just
|
||||
/// return `true` from the `is_message_successfully_dispatched`.
|
||||
pub is_successful_dispatch_expected: bool,
|
||||
/// Proof size requirements.
|
||||
pub proof_params: UnverifiedStorageProofParams,
|
||||
}
|
||||
|
||||
/// Benchmark-specific message delivery proof parameters.
|
||||
#[derive(Debug)]
|
||||
pub struct MessageDeliveryProofParams<ThisChainAccountId, LaneId> {
|
||||
/// Id of the lane.
|
||||
pub lane: LaneId,
|
||||
/// The proof needs to include this inbound lane data.
|
||||
pub inbound_lane_data: InboundLaneData<ThisChainAccountId>,
|
||||
/// Proof size requirements.
|
||||
pub proof_params: UnverifiedStorageProofParams,
|
||||
}
|
||||
|
||||
/// Trait that must be implemented by runtime.
|
||||
pub trait Config<I: 'static>: crate::Config<I> {
|
||||
/// Lane id to use in benchmarks.
|
||||
fn bench_lane_id() -> Self::LaneId {
|
||||
Self::LaneId::default()
|
||||
}
|
||||
|
||||
/// Return id of relayer account at the bridged chain.
|
||||
///
|
||||
/// By default, zero account is returned.
|
||||
fn bridged_relayer_id() -> AccountIdOf<BridgedChainOf<Self, I>> {
|
||||
Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap()
|
||||
}
|
||||
|
||||
/// Create given account and give it enough balance for test purposes. Used to create
|
||||
/// relayer account at the target chain. Is strictly necessary when your rewards scheme
|
||||
/// assumes that the relayer account must exist.
|
||||
///
|
||||
/// Does nothing by default.
|
||||
fn endow_account(_account: &Self::AccountId) {}
|
||||
|
||||
/// Prepare messages proof to receive by the module.
|
||||
fn prepare_message_proof(
|
||||
params: MessageProofParams<Self::LaneId>,
|
||||
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChainOf<Self, I>>, Self::LaneId>, Weight);
|
||||
/// Prepare messages delivery proof to receive by the module.
|
||||
fn prepare_message_delivery_proof(
|
||||
params: MessageDeliveryProofParams<Self::AccountId, Self::LaneId>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<Self, I>>, Self::LaneId>;
|
||||
|
||||
/// Returns true if message has been successfully dispatched or not.
|
||||
fn is_message_successfully_dispatched(_nonce: MessageNonce) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if given relayer has been rewarded for some of its actions.
|
||||
fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool;
|
||||
}
|
||||
|
||||
fn send_regular_message<T: Config<I>, I: 'static>() {
|
||||
OutboundLanes::<T, I>::insert(
|
||||
T::bench_lane_id(),
|
||||
OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
latest_generated_nonce: 1,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let mut outbound_lane = active_outbound_lane::<T, I>(T::bench_lane_id()).unwrap();
|
||||
outbound_lane.send_message(BoundedVec::try_from(vec![]).expect("We craft valid messages"));
|
||||
}
|
||||
|
||||
fn receive_messages<T: Config<I>, I: 'static>(nonce: MessageNonce) {
|
||||
InboundLanes::<T, I>::insert(
|
||||
T::bench_lane_id(),
|
||||
InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: vec![UnrewardedRelayer {
|
||||
relayer: T::bridged_relayer_id(),
|
||||
messages: DeliveredMessages::new(nonce),
|
||||
}]
|
||||
.into(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
struct ReceiveMessagesProofSetup<T: Config<I>, I: 'static> {
|
||||
relayer_id_on_src: AccountIdOf<BridgedChainOf<T, I>>,
|
||||
relayer_id_on_tgt: T::AccountId,
|
||||
msgs_count: u32,
|
||||
_phantom_data: pezsp_std::marker::PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> ReceiveMessagesProofSetup<T, I> {
|
||||
const LATEST_RECEIVED_NONCE: MessageNonce = 20;
|
||||
|
||||
fn new(msgs_count: u32) -> Self {
|
||||
let setup = Self {
|
||||
relayer_id_on_src: T::bridged_relayer_id(),
|
||||
relayer_id_on_tgt: account("relayer", 0, SEED),
|
||||
msgs_count,
|
||||
_phantom_data: Default::default(),
|
||||
};
|
||||
T::endow_account(&setup.relayer_id_on_tgt);
|
||||
// mark messages 1..=latest_recvd_nonce as delivered
|
||||
receive_messages::<T, I>(Self::LATEST_RECEIVED_NONCE);
|
||||
|
||||
setup
|
||||
}
|
||||
|
||||
fn relayer_id_on_src(&self) -> AccountIdOf<BridgedChainOf<T, I>> {
|
||||
self.relayer_id_on_src.clone()
|
||||
}
|
||||
|
||||
fn relayer_id_on_tgt(&self) -> T::AccountId {
|
||||
self.relayer_id_on_tgt.clone()
|
||||
}
|
||||
|
||||
fn last_nonce(&self) -> MessageNonce {
|
||||
Self::LATEST_RECEIVED_NONCE + self.msgs_count as u64
|
||||
}
|
||||
|
||||
fn nonces(&self) -> RangeInclusive<MessageNonce> {
|
||||
(Self::LATEST_RECEIVED_NONCE + 1)..=self.last_nonce()
|
||||
}
|
||||
|
||||
fn check_last_nonce(&self) {
|
||||
assert_eq!(
|
||||
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).map(|d| d.last_delivered_nonce()),
|
||||
Some(self.last_nonce()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[instance_benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
//
|
||||
// Benchmarks that are used directly by the runtime calls weight formulae.
|
||||
//
|
||||
|
||||
fn max_msgs<T: Config<I>, I: 'static>() -> u32 {
|
||||
T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX as u32 -
|
||||
ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE as u32
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
|
||||
// conditions:
|
||||
// * proof does not include outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is dispatched (reminder: dispatch weight should be minimal);
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
#[benchmark]
|
||||
fn receive_single_message_proof() {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: None,
|
||||
is_successful_dispatch_expected: false,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(
|
||||
EXPECTED_DEFAULT_MESSAGE_LENGTH,
|
||||
),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with `n` minimal-weight messages and following
|
||||
// conditions:
|
||||
// * proof does not include outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is dispatched (reminder: dispatch weight should be minimal);
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
#[benchmark]
|
||||
fn receive_n_messages_proof(n: Linear<1, { max_msgs::<T, I>() }>) {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(n);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: None,
|
||||
is_successful_dispatch_expected: false,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(
|
||||
EXPECTED_DEFAULT_MESSAGE_LENGTH,
|
||||
),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
|
||||
// conditions:
|
||||
// * proof includes outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is successfully dispatched (reminder: dispatch weight should be minimal);
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
//
|
||||
// The weight of outbound lane state delivery would be
|
||||
// `weight(receive_single_message_proof_with_outbound_lane_state) -
|
||||
// weight(receive_single_message_proof)`. This won't be super-accurate if message has non-zero
|
||||
// dispatch weight, but estimation should be close enough to real weight.
|
||||
#[benchmark]
|
||||
fn receive_single_message_proof_with_outbound_lane_state() {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: setup.last_nonce(),
|
||||
latest_received_nonce: ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE,
|
||||
latest_generated_nonce: setup.last_nonce(),
|
||||
}),
|
||||
is_successful_dispatch_expected: false,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(
|
||||
EXPECTED_DEFAULT_MESSAGE_LENGTH,
|
||||
),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
|
||||
// conditions:
|
||||
// * the proof has large leaf with total size ranging between 1KB and 16KB;
|
||||
// * proof does not include outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is dispatched (reminder: dispatch weight should be minimal);
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
#[benchmark]
|
||||
fn receive_single_n_bytes_message_proof(
|
||||
/// Proof size in KB
|
||||
n: Linear<1, { 16 * 1024 }>,
|
||||
) {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: None,
|
||||
is_successful_dispatch_expected: false,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(n),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
|
||||
// * single relayer is rewarded for relaying single message;
|
||||
// * relayer account does not exist (in practice it needs to exist in production environment).
|
||||
//
|
||||
// This is base benchmark for all other confirmations delivery benchmarks.
|
||||
#[benchmark]
|
||||
fn receive_delivery_proof_for_single_message() {
|
||||
let relayer_id: T::AccountId = account("relayer", 0, SEED);
|
||||
|
||||
// send message that we're going to confirm
|
||||
send_regular_message::<T, I>();
|
||||
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 1,
|
||||
last_delivered_nonce: 1,
|
||||
};
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: vec![UnrewardedRelayer {
|
||||
relayer: relayer_id.clone(),
|
||||
messages: DeliveredMessages::new(1),
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
proof_params: UnverifiedStorageProofParams::default(),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_delivery_proof(
|
||||
RawOrigin::Signed(relayer_id.clone()),
|
||||
proof,
|
||||
relayers_state,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
|
||||
Some(1)
|
||||
);
|
||||
assert!(T::is_relayer_rewarded(&relayer_id));
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
|
||||
// * single relayer is rewarded for relaying two messages;
|
||||
// * relayer account does not exist (in practice it needs to exist in production environment).
|
||||
//
|
||||
// Additional weight for paying single-message reward to the same relayer could be computed
|
||||
// as `weight(receive_delivery_proof_for_two_messages_by_single_relayer)
|
||||
// - weight(receive_delivery_proof_for_single_message)`.
|
||||
#[benchmark]
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() {
|
||||
let relayer_id: T::AccountId = account("relayer", 0, SEED);
|
||||
|
||||
// send message that we're going to confirm
|
||||
send_regular_message::<T, I>();
|
||||
send_regular_message::<T, I>();
|
||||
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 2,
|
||||
total_messages: 2,
|
||||
last_delivered_nonce: 2,
|
||||
};
|
||||
let mut delivered_messages = DeliveredMessages::new(1);
|
||||
delivered_messages.note_dispatched_message();
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: vec![UnrewardedRelayer {
|
||||
relayer: relayer_id.clone(),
|
||||
messages: delivered_messages,
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
proof_params: UnverifiedStorageProofParams::default(),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_delivery_proof(
|
||||
RawOrigin::Signed(relayer_id.clone()),
|
||||
proof,
|
||||
relayers_state,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
|
||||
Some(2)
|
||||
);
|
||||
assert!(T::is_relayer_rewarded(&relayer_id));
|
||||
}
|
||||
|
||||
// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
|
||||
// * two relayers are rewarded for relaying single message each;
|
||||
// * relayer account does not exist (in practice it needs to exist in production environment).
|
||||
//
|
||||
// Additional weight for paying reward to the next relayer could be computed
|
||||
// as `weight(receive_delivery_proof_for_two_messages_by_two_relayers)
|
||||
// - weight(receive_delivery_proof_for_two_messages_by_single_relayer)`.
|
||||
#[benchmark]
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() {
|
||||
let relayer1_id: T::AccountId = account("relayer1", 1, SEED);
|
||||
let relayer2_id: T::AccountId = account("relayer2", 2, SEED);
|
||||
|
||||
// send message that we're going to confirm
|
||||
send_regular_message::<T, I>();
|
||||
send_regular_message::<T, I>();
|
||||
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 2,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 2,
|
||||
last_delivered_nonce: 2,
|
||||
};
|
||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
inbound_lane_data: InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: vec![
|
||||
UnrewardedRelayer {
|
||||
relayer: relayer1_id.clone(),
|
||||
messages: DeliveredMessages::new(1),
|
||||
},
|
||||
UnrewardedRelayer {
|
||||
relayer: relayer2_id.clone(),
|
||||
messages: DeliveredMessages::new(2),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
},
|
||||
proof_params: UnverifiedStorageProofParams::default(),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_delivery_proof(
|
||||
RawOrigin::Signed(relayer1_id.clone()),
|
||||
proof,
|
||||
relayers_state,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
|
||||
Some(2)
|
||||
);
|
||||
assert!(T::is_relayer_rewarded(&relayer1_id));
|
||||
assert!(T::is_relayer_rewarded(&relayer2_id));
|
||||
}
|
||||
|
||||
//
|
||||
// Benchmarks that the runtime developers may use for proper pezpallet configuration.
|
||||
//
|
||||
|
||||
// This benchmark is optional and may be used when runtime developer need a way to compute
|
||||
// message dispatch weight. In this case, he needs to provide messages that can go the whole
|
||||
// dispatch
|
||||
//
|
||||
// Benchmark `receive_messages_proof` extrinsic with single message and following conditions:
|
||||
//
|
||||
// * proof does not include outbound lane state proof;
|
||||
// * inbound lane already has state, so it needs to be read and decoded;
|
||||
// * message is **SUCCESSFULLY** dispatched;
|
||||
// * message requires all heavy checks done by dispatcher.
|
||||
#[benchmark]
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(
|
||||
/// Proof size in KB
|
||||
n: Linear<1, { 16 * 1024 }>,
|
||||
) {
|
||||
// setup code
|
||||
let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
|
||||
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
|
||||
lane: T::bench_lane_id(),
|
||||
message_nonces: setup.nonces(),
|
||||
outbound_lane_data: None,
|
||||
is_successful_dispatch_expected: true,
|
||||
proof_params: UnverifiedStorageProofParams::from_db_size(n),
|
||||
});
|
||||
|
||||
#[extrinsic_call]
|
||||
receive_messages_proof(
|
||||
RawOrigin::Signed(setup.relayer_id_on_tgt()),
|
||||
setup.relayer_id_on_src(),
|
||||
Box::new(proof),
|
||||
setup.msgs_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
|
||||
// verification code
|
||||
setup.check_last_nonce();
|
||||
assert!(T::is_message_successfully_dispatched(setup.last_nonce()));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pezpallet,
|
||||
crate::tests::mock::new_test_ext(),
|
||||
crate::tests::mock::TestRuntime
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helpers for easier manipulation of call processing with signed extensions.
|
||||
|
||||
use crate::{BridgedChainOf, Config, InboundLanes, OutboundLanes, Pezpallet, LOG_TARGET};
|
||||
|
||||
use bp_messages::{
|
||||
target_chain::MessageDispatch, BaseMessagesProofInfo, ChainWithMessages, InboundLaneData,
|
||||
MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo,
|
||||
UnrewardedRelayerOccupation,
|
||||
};
|
||||
use pezbp_runtime::{AccountIdOf, OwnedBridgeModule};
|
||||
use pezframe_support::{dispatch::CallableCallFor, traits::IsSubType};
|
||||
use pezsp_runtime::transaction_validity::TransactionValidity;
|
||||
|
||||
/// Helper struct that provides methods for working with a call supported by `MessagesCallInfo`.
|
||||
pub struct CallHelper<T: Config<I>, I: 'static> {
|
||||
_phantom_data: pezsp_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: &MessagesCallInfo<T::LaneId>) -> bool {
|
||||
match info {
|
||||
MessagesCallInfo::ReceiveMessagesProof(info) => {
|
||||
let inbound_lane_data = match InboundLanes::<T, I>::get(info.base.lane_id) {
|
||||
Some(inbound_lane_data) => inbound_lane_data,
|
||||
None => return false,
|
||||
};
|
||||
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()
|
||||
},
|
||||
MessagesCallInfo::ReceiveMessagesDeliveryProof(info) => {
|
||||
let outbound_lane_data = match OutboundLanes::<T, I>::get(info.0.lane_id) {
|
||||
Some(outbound_lane_data) => outbound_lane_data,
|
||||
None => return false,
|
||||
};
|
||||
outbound_lane_data.latest_received_nonce == *info.0.bundled_range.end()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing a call that is a sub type of `pezpallet_bridge_messages::Call`.
|
||||
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||
IsSubType<CallableCallFor<Pezpallet<T, I>, T>>
|
||||
{
|
||||
/// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call.
|
||||
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo<T::LaneId>>;
|
||||
|
||||
/// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from
|
||||
/// a `ReceiveMessagesDeliveryProof` call.
|
||||
fn receive_messages_delivery_proof_info(
|
||||
&self,
|
||||
) -> Option<ReceiveMessagesDeliveryProofInfo<T::LaneId>>;
|
||||
|
||||
/// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof`
|
||||
/// or a `ReceiveMessagesDeliveryProof` call.
|
||||
fn call_info(&self) -> Option<MessagesCallInfo<T::LaneId>>;
|
||||
|
||||
/// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof`
|
||||
/// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane.
|
||||
fn call_info_for(&self, lane_id: T::LaneId) -> Option<MessagesCallInfo<T::LaneId>>;
|
||||
|
||||
/// Ensures that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call:
|
||||
///
|
||||
/// - does not deliver already delivered messages. We require all messages in the
|
||||
/// `ReceiveMessagesProof` call to be undelivered;
|
||||
///
|
||||
/// - does not submit empty `ReceiveMessagesProof` call with zero messages, unless the lane
|
||||
/// needs to be unblocked by providing relayer rewards proof;
|
||||
///
|
||||
/// - brings no new delivery confirmations in a `ReceiveMessagesDeliveryProof` call. We require
|
||||
/// at least one new delivery confirmation in the unrewarded relayers set;
|
||||
///
|
||||
/// - does not violate some basic (easy verifiable) messages pezpallet rules obsolete (like
|
||||
/// submitting a call when a pezpallet is halted or delivering messages when a dispatcher is
|
||||
/// inactive).
|
||||
///
|
||||
/// If one of above rules is violated, the transaction is treated as invalid.
|
||||
fn check_obsolete_call(&self) -> TransactionValidity;
|
||||
}
|
||||
|
||||
impl<
|
||||
Call: IsSubType<CallableCallFor<Pezpallet<T, I>, T>>,
|
||||
T: pezframe_system::Config<RuntimeCall = Call> + Config<I>,
|
||||
I: 'static,
|
||||
> CallSubType<T, I> for T::RuntimeCall
|
||||
{
|
||||
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo<T::LaneId>> {
|
||||
if let Some(crate::Call::<T, I>::receive_messages_proof { ref proof, .. }) =
|
||||
self.is_sub_type()
|
||||
{
|
||||
let inbound_lane_data = 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<T::LaneId>> {
|
||||
if let Some(crate::Call::<T, I>::receive_messages_delivery_proof {
|
||||
ref proof,
|
||||
ref relayers_state,
|
||||
..
|
||||
}) = self.is_sub_type()
|
||||
{
|
||||
let outbound_lane_data = 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<MessagesCallInfo<T::LaneId>> {
|
||||
if let Some(info) = self.receive_messages_proof_info() {
|
||||
return Some(MessagesCallInfo::ReceiveMessagesProof(info));
|
||||
}
|
||||
|
||||
if let Some(info) = self.receive_messages_delivery_proof_info() {
|
||||
return Some(MessagesCallInfo::ReceiveMessagesDeliveryProof(info));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn call_info_for(&self, lane_id: T::LaneId) -> Option<MessagesCallInfo<T::LaneId>> {
|
||||
self.call_info().filter(|info| {
|
||||
let actual_lane_id = match info {
|
||||
MessagesCallInfo::ReceiveMessagesProof(info) => info.base.lane_id,
|
||||
MessagesCallInfo::ReceiveMessagesDeliveryProof(info) => info.0.lane_id,
|
||||
};
|
||||
actual_lane_id == lane_id
|
||||
})
|
||||
}
|
||||
|
||||
fn check_obsolete_call(&self) -> TransactionValidity {
|
||||
let is_pallet_halted = Pezpallet::<T, I>::ensure_not_halted().is_err();
|
||||
match self.call_info() {
|
||||
Some(proof_info) if is_pallet_halted => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?proof_info,
|
||||
"Rejecting messages transaction on halted pezpallet"
|
||||
);
|
||||
|
||||
return pezsp_runtime::transaction_validity::InvalidTransaction::Call.into();
|
||||
},
|
||||
Some(MessagesCallInfo::ReceiveMessagesProof(proof_info))
|
||||
if proof_info
|
||||
.is_obsolete(T::MessageDispatch::is_active(proof_info.base.lane_id)) =>
|
||||
{
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?proof_info,
|
||||
"Rejecting obsolete messages delivery transaction"
|
||||
);
|
||||
|
||||
return pezsp_runtime::transaction_validity::InvalidTransaction::Stale.into();
|
||||
},
|
||||
Some(MessagesCallInfo::ReceiveMessagesDeliveryProof(proof_info))
|
||||
if proof_info.is_obsolete() =>
|
||||
{
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?proof_info,
|
||||
"Rejecting obsolete messages confirmation transaction"
|
||||
);
|
||||
|
||||
return pezsp_runtime::transaction_validity::InvalidTransaction::Stale.into();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Ok(pezsp_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<AccountIdOf<BridgedChainOf<T, I>>>,
|
||||
) -> UnrewardedRelayerOccupation {
|
||||
UnrewardedRelayerOccupation {
|
||||
free_relayer_slots: T::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX
|
||||
.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::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
|
||||
.saturating_sub(unconfirmed_messages)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::mock::*;
|
||||
use bp_messages::{
|
||||
source_chain::FromBridgedChainMessagesDeliveryProof,
|
||||
target_chain::FromBridgedChainMessagesProof, DeliveredMessages, InboundLaneData, LaneState,
|
||||
OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState,
|
||||
};
|
||||
use pezsp_std::ops::RangeInclusive;
|
||||
|
||||
fn fill_unrewarded_relayers() {
|
||||
let mut inbound_lane_state = InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap();
|
||||
for n in 0..BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX {
|
||||
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
|
||||
relayer: Default::default(),
|
||||
messages: DeliveredMessages { begin: n + 1, end: n + 1 },
|
||||
});
|
||||
}
|
||||
InboundLanes::<TestRuntime>::insert(test_lane_id(), inbound_lane_state);
|
||||
}
|
||||
|
||||
fn fill_unrewarded_messages() {
|
||||
let mut inbound_lane_state = InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap();
|
||||
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
|
||||
relayer: Default::default(),
|
||||
messages: DeliveredMessages {
|
||||
begin: 1,
|
||||
end: BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
|
||||
},
|
||||
});
|
||||
InboundLanes::<TestRuntime>::insert(test_lane_id(), inbound_lane_state);
|
||||
}
|
||||
|
||||
fn deliver_message_10() {
|
||||
InboundLanes::<TestRuntime>::insert(
|
||||
test_lane_id(),
|
||||
bp_messages::InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: Default::default(),
|
||||
last_confirmed_nonce: 10,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn validate_message_delivery(
|
||||
nonces_start: bp_messages::MessageNonce,
|
||||
nonces_end: bp_messages::MessageNonce,
|
||||
) -> bool {
|
||||
RuntimeCall::Messages(crate::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: pezframe_support::weights::Weight::zero(),
|
||||
proof: Box::new(FromBridgedChainMessagesProof {
|
||||
bridged_header_hash: Default::default(),
|
||||
storage_proof: Default::default(),
|
||||
lane: test_lane_id(),
|
||||
nonces_start,
|
||||
nonces_end,
|
||||
}),
|
||||
})
|
||||
.check_obsolete_call()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn run_test<T>(test: impl Fn() -> T) -> T {
|
||||
pezsp_io::TestExternalities::new(Default::default()).execute_with(|| {
|
||||
InboundLanes::<TestRuntime>::insert(test_lane_id(), InboundLaneData::opened());
|
||||
OutboundLanes::<TestRuntime>::insert(test_lane_id(), OutboundLaneData::opened());
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_obsolete_messages() {
|
||||
run_test(|| {
|
||||
// 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() {
|
||||
run_test(|| {
|
||||
// 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() {
|
||||
run_test(|| {
|
||||
// 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() {
|
||||
run_test(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver messages
|
||||
// 13..=15 => tx is rejected
|
||||
deliver_message_10();
|
||||
assert!(!validate_message_delivery(13, 15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_reject_call_when_dispatcher_is_inactive() {
|
||||
run_test(|| {
|
||||
// when current best delivered is message#10 and we're trying to deliver message 11..=15
|
||||
// => tx is accepted, but we have inactive dispatcher, so...
|
||||
deliver_message_10();
|
||||
|
||||
TestMessageDispatch::deactivate(test_lane_id());
|
||||
assert!(!validate_message_delivery(11, 15));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_empty_delivery_with_rewards_confirmations_if_there_are_free_relayer_and_message_slots(
|
||||
) {
|
||||
run_test(|| {
|
||||
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(
|
||||
) {
|
||||
run_test(|| {
|
||||
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(
|
||||
) {
|
||||
run_test(|| {
|
||||
fill_unrewarded_messages();
|
||||
assert!(validate_message_delivery(
|
||||
BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
|
||||
BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX - 1
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_messages() {
|
||||
run_test(|| {
|
||||
// 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() {
|
||||
OutboundLanes::<TestRuntime>::insert(
|
||||
test_lane_id(),
|
||||
bp_messages::OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 0,
|
||||
latest_received_nonce: 10,
|
||||
latest_generated_nonce: 10,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn validate_message_confirmation(last_delivered_nonce: bp_messages::MessageNonce) -> bool {
|
||||
RuntimeCall::Messages(crate::Call::<TestRuntime>::receive_messages_delivery_proof {
|
||||
proof: FromBridgedChainMessagesDeliveryProof {
|
||||
bridged_header_hash: Default::default(),
|
||||
storage_proof: Default::default(),
|
||||
lane: test_lane_id(),
|
||||
},
|
||||
relayers_state: UnrewardedRelayersState { last_delivered_nonce, ..Default::default() },
|
||||
})
|
||||
.check_obsolete_call()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_obsolete_confirmations() {
|
||||
run_test(|| {
|
||||
// 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() {
|
||||
run_test(|| {
|
||||
// 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() {
|
||||
run_test(|| {
|
||||
confirm_message_10();
|
||||
fill_unrewarded_relayers();
|
||||
assert!(!validate_message_confirmation(10));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_confirmation() {
|
||||
run_test(|| {
|
||||
// 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(&MessagesCallInfo::ReceiveMessagesProof(
|
||||
ReceiveMessagesProofInfo {
|
||||
base: BaseMessagesProofInfo {
|
||||
lane_id: test_lane_id(),
|
||||
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 {
|
||||
BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
|
||||
},
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
fn was_successful_returns_false_for_failed_reward_confirmation_transaction() {
|
||||
run_test(|| {
|
||||
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() {
|
||||
run_test(|| {
|
||||
assert!(was_message_delivery_successful(10..=9, true));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_failed_delivery() {
|
||||
run_test(|| {
|
||||
deliver_message_10();
|
||||
assert!(!was_message_delivery_successful(10..=12, false));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_partially_successful_delivery() {
|
||||
run_test(|| {
|
||||
deliver_message_10();
|
||||
assert!(!was_message_delivery_successful(9..=12, false));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_true_for_successful_delivery() {
|
||||
run_test(|| {
|
||||
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(
|
||||
&MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
|
||||
BaseMessagesProofInfo {
|
||||
lane_id: test_lane_id(),
|
||||
bundled_range,
|
||||
best_stored_nonce: 0, // doesn't matter for `was_successful`
|
||||
},
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_failed_confirmation() {
|
||||
run_test(|| {
|
||||
confirm_message_10();
|
||||
assert!(!was_message_confirmation_successful(10..=12));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_false_for_partially_successful_confirmation() {
|
||||
run_test(|| {
|
||||
confirm_message_10();
|
||||
assert!(!was_message_confirmation_successful(9..=12));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn was_successful_returns_true_for_successful_confirmation() {
|
||||
run_test(|| {
|
||||
confirm_message_10();
|
||||
assert!(was_message_confirmation_successful(9..=10));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Everything about incoming messages receival.
|
||||
|
||||
use crate::{BridgedChainOf, Config};
|
||||
|
||||
use bp_messages::{
|
||||
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
|
||||
ChainWithMessages, DeliveredMessages, InboundLaneData, LaneState, MessageKey, MessageNonce,
|
||||
OutboundLaneData, ReceptionResult, UnrewardedRelayer,
|
||||
};
|
||||
use pezbp_runtime::AccountIdOf;
|
||||
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
|
||||
use scale_info::{Type, TypeInfo};
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
use pezsp_std::prelude::PartialEq;
|
||||
|
||||
/// Inbound lane storage.
|
||||
pub trait InboundLaneStorage {
|
||||
/// Id of relayer on source chain.
|
||||
type Relayer: Clone + PartialEq;
|
||||
/// Lane identifier type.
|
||||
type LaneId: Encode;
|
||||
|
||||
/// Lane id.
|
||||
fn id(&self) -> Self::LaneId;
|
||||
/// Return maximal number of unrewarded relayer entries in inbound lane.
|
||||
fn max_unrewarded_relayer_entries(&self) -> MessageNonce;
|
||||
/// Return maximal number of unconfirmed messages in inbound lane.
|
||||
fn max_unconfirmed_messages(&self) -> MessageNonce;
|
||||
/// Get lane data from the storage.
|
||||
fn data(&self) -> InboundLaneData<Self::Relayer>;
|
||||
/// Update lane data in the storage.
|
||||
fn set_data(&mut self, data: InboundLaneData<Self::Relayer>);
|
||||
/// Purge lane data from the storage.
|
||||
fn purge(self);
|
||||
}
|
||||
|
||||
/// Inbound lane data wrapper that implements `MaxEncodedLen`.
|
||||
///
|
||||
/// We have already had `MaxEncodedLen`-like functionality before, but its usage has
|
||||
/// been localized and we haven't been passing bounds (maximal count of unrewarded relayer entries,
|
||||
/// maximal count of unconfirmed messages) everywhere. This wrapper allows us to avoid passing
|
||||
/// these generic bounds all over the code.
|
||||
///
|
||||
/// The encoding of this type matches encoding of the corresponding `MessageData`.
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct StoredInboundLaneData<T: Config<I>, I: 'static>(
|
||||
pub InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
|
||||
);
|
||||
|
||||
impl<T: Config<I>, I: 'static> pezsp_std::ops::Deref for StoredInboundLaneData<T, I> {
|
||||
type Target = InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> pezsp_std::ops::DerefMut for StoredInboundLaneData<T, I> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for StoredInboundLaneData<T, I> {
|
||||
fn default() -> Self {
|
||||
StoredInboundLaneData(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> From<StoredInboundLaneData<T, I>>
|
||||
for InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>
|
||||
{
|
||||
fn from(data: StoredInboundLaneData<T, I>) -> Self {
|
||||
data.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> EncodeLike<StoredInboundLaneData<T, I>>
|
||||
for InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> TypeInfo for StoredInboundLaneData<T, I> {
|
||||
type Identity = Self;
|
||||
|
||||
fn type_info() -> Type {
|
||||
InboundLaneData::<AccountIdOf<BridgedChainOf<T, I>>>::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> MaxEncodedLen for StoredInboundLaneData<T, I> {
|
||||
fn max_encoded_len() -> usize {
|
||||
InboundLaneData::<AccountIdOf<BridgedChainOf<T, I>>>::encoded_size_hint(
|
||||
BridgedChainOf::<T, I>::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX as usize,
|
||||
)
|
||||
.unwrap_or(usize::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
/// Inbound messages lane.
|
||||
pub struct InboundLane<S> {
|
||||
storage: S,
|
||||
}
|
||||
|
||||
impl<S: InboundLaneStorage> InboundLane<S> {
|
||||
/// Create new inbound lane backed by given storage.
|
||||
pub fn new(storage: S) -> Self {
|
||||
InboundLane { storage }
|
||||
}
|
||||
|
||||
/// Get lane state.
|
||||
pub fn state(&self) -> LaneState {
|
||||
self.storage.data().state
|
||||
}
|
||||
|
||||
/// Returns storage reference.
|
||||
pub fn storage(&self) -> &S {
|
||||
&self.storage
|
||||
}
|
||||
|
||||
/// Set lane state.
|
||||
pub fn set_state(&mut self, state: LaneState) {
|
||||
let mut data = self.storage.data();
|
||||
data.state = state;
|
||||
self.storage.set_data(data);
|
||||
}
|
||||
|
||||
/// Receive state of the corresponding outbound lane.
|
||||
pub fn receive_state_update(
|
||||
&mut self,
|
||||
outbound_lane_data: OutboundLaneData,
|
||||
) -> Option<MessageNonce> {
|
||||
let mut data = self.storage.data();
|
||||
let last_delivered_nonce = data.last_delivered_nonce();
|
||||
|
||||
if outbound_lane_data.latest_received_nonce > last_delivered_nonce {
|
||||
// this is something that should never happen if proofs are correct
|
||||
return None;
|
||||
}
|
||||
if outbound_lane_data.latest_received_nonce <= data.last_confirmed_nonce {
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_confirmed_nonce = outbound_lane_data.latest_received_nonce;
|
||||
data.last_confirmed_nonce = new_confirmed_nonce;
|
||||
// Firstly, remove all of the records where higher nonce <= new confirmed nonce
|
||||
while data
|
||||
.relayers
|
||||
.front()
|
||||
.map(|entry| entry.messages.end <= new_confirmed_nonce)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
data.relayers.pop_front();
|
||||
}
|
||||
// Secondly, update the next record with lower nonce equal to new confirmed nonce if needed.
|
||||
// Note: There will be max. 1 record to update as we don't allow messages from relayers to
|
||||
// overlap.
|
||||
match data.relayers.front_mut() {
|
||||
Some(entry) if entry.messages.begin <= new_confirmed_nonce => {
|
||||
entry.messages.begin = new_confirmed_nonce + 1;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
self.storage.set_data(data);
|
||||
Some(outbound_lane_data.latest_received_nonce)
|
||||
}
|
||||
|
||||
/// Receive new message.
|
||||
pub fn receive_message<Dispatch: MessageDispatch<LaneId = S::LaneId>>(
|
||||
&mut self,
|
||||
relayer_at_bridged_chain: &S::Relayer,
|
||||
nonce: MessageNonce,
|
||||
message_data: DispatchMessageData<Dispatch::DispatchPayload>,
|
||||
) -> ReceptionResult<Dispatch::DispatchLevelResult> {
|
||||
let mut data = self.storage.data();
|
||||
if Some(nonce) != data.last_delivered_nonce().checked_add(1) {
|
||||
return ReceptionResult::InvalidNonce;
|
||||
}
|
||||
|
||||
// if there are more unrewarded relayer entries than we may accept, reject this message
|
||||
if data.relayers.len() as MessageNonce >= self.storage.max_unrewarded_relayer_entries() {
|
||||
return ReceptionResult::TooManyUnrewardedRelayers;
|
||||
}
|
||||
|
||||
// if there are more unconfirmed messages than we may accept, reject this message
|
||||
let unconfirmed_messages_count = nonce.saturating_sub(data.last_confirmed_nonce);
|
||||
if unconfirmed_messages_count > self.storage.max_unconfirmed_messages() {
|
||||
return ReceptionResult::TooManyUnconfirmedMessages;
|
||||
}
|
||||
|
||||
// then, dispatch message
|
||||
let dispatch_result = Dispatch::dispatch(DispatchMessage {
|
||||
key: MessageKey { lane_id: self.storage.id(), nonce },
|
||||
data: message_data,
|
||||
});
|
||||
|
||||
// now let's update inbound lane storage
|
||||
match data.relayers.back_mut() {
|
||||
Some(entry) if entry.relayer == *relayer_at_bridged_chain => {
|
||||
entry.messages.note_dispatched_message();
|
||||
},
|
||||
_ => {
|
||||
data.relayers.push_back(UnrewardedRelayer {
|
||||
relayer: relayer_at_bridged_chain.clone(),
|
||||
messages: DeliveredMessages::new(nonce),
|
||||
});
|
||||
},
|
||||
};
|
||||
self.storage.set_data(data);
|
||||
|
||||
ReceptionResult::Dispatched(dispatch_result)
|
||||
}
|
||||
|
||||
/// Purge lane state from the storage.
|
||||
pub fn purge(self) {
|
||||
self.storage.purge()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{active_inbound_lane, lanes_manager::RuntimeInboundLaneStorage, tests::mock::*};
|
||||
use bp_messages::UnrewardedRelayersState;
|
||||
|
||||
fn receive_regular_message(
|
||||
lane: &mut InboundLane<RuntimeInboundLaneStorage<TestRuntime, ()>>,
|
||||
nonce: MessageNonce,
|
||||
) {
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
nonce,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_status_update_ignores_status_from_the_future() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 10,
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_status_update_ignores_obsolete_status() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
receive_regular_message(&mut lane, 2);
|
||||
receive_regular_message(&mut lane, 3);
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(3),
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
|
||||
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_status_update_works() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
receive_regular_message(&mut lane, 2);
|
||||
receive_regular_message(&mut lane, 3);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 0);
|
||||
assert_eq!(
|
||||
lane.storage.data().relayers,
|
||||
vec![unrewarded_relayer(1, 3, TEST_RELAYER_A)]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 2,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(2),
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 2);
|
||||
assert_eq!(
|
||||
lane.storage.data().relayers,
|
||||
vec![unrewarded_relayer(3, 3, TEST_RELAYER_A)]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(3),
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
|
||||
assert_eq!(lane.storage.data().relayers, vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_status_update_works_with_batches_from_relayers() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
let mut seed_storage_data = lane.storage.data();
|
||||
// Prepare data
|
||||
seed_storage_data.last_confirmed_nonce = 0;
|
||||
seed_storage_data.relayers.push_back(unrewarded_relayer(1, 1, TEST_RELAYER_A));
|
||||
// Simulate messages batch (2, 3, 4) from relayer #2
|
||||
seed_storage_data.relayers.push_back(unrewarded_relayer(2, 4, TEST_RELAYER_B));
|
||||
seed_storage_data.relayers.push_back(unrewarded_relayer(5, 5, TEST_RELAYER_C));
|
||||
lane.storage.set_data(seed_storage_data);
|
||||
// Check
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(3),
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_confirmed_nonce, 3);
|
||||
assert_eq!(
|
||||
lane.storage.data().relayers,
|
||||
vec![
|
||||
unrewarded_relayer(4, 4, TEST_RELAYER_B),
|
||||
unrewarded_relayer(5, 5, TEST_RELAYER_C)
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_receive_message_with_incorrect_nonce() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
10,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::InvalidNonce
|
||||
);
|
||||
assert_eq!(lane.storage.data().last_delivered_nonce(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_receive_messages_above_unrewarded_relayer_entries_limit_per_lane() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
let max_nonce = BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
|
||||
for current_nonce in 1..max_nonce + 1 {
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&(TEST_RELAYER_A + current_nonce),
|
||||
current_nonce,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
}
|
||||
// Fails to dispatch new message from different than latest relayer.
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&(TEST_RELAYER_A + max_nonce + 1),
|
||||
max_nonce + 1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::TooManyUnrewardedRelayers,
|
||||
);
|
||||
// Fails to dispatch new messages from latest relayer. Prevents griefing attacks.
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&(TEST_RELAYER_A + max_nonce),
|
||||
max_nonce + 1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::TooManyUnrewardedRelayers,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_receive_messages_above_unconfirmed_messages_limit_per_lane() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
let max_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
|
||||
for current_nonce in 1..=max_nonce {
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
current_nonce,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
}
|
||||
// Fails to dispatch new message from different than latest relayer.
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_B,
|
||||
max_nonce + 1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::TooManyUnconfirmedMessages,
|
||||
);
|
||||
// Fails to dispatch new messages from latest relayer.
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
max_nonce + 1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::TooManyUnconfirmedMessages,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_receives_following_messages_from_two_relayers_alternately() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_B,
|
||||
2,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
3,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
assert_eq!(
|
||||
lane.storage.data().relayers,
|
||||
vec![
|
||||
unrewarded_relayer(1, 1, TEST_RELAYER_A),
|
||||
unrewarded_relayer(2, 2, TEST_RELAYER_B),
|
||||
unrewarded_relayer(3, 3, TEST_RELAYER_A)
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_same_message_from_two_different_relayers() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(0))
|
||||
);
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_B,
|
||||
1,
|
||||
inbound_message_data(REGULAR_PAYLOAD)
|
||||
),
|
||||
ReceptionResult::InvalidNonce,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correct_message_is_processed_instantly() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
assert_eq!(lane.storage.data().last_delivered_nonce(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unspent_weight_is_returned_by_receive_message() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
let mut payload = REGULAR_PAYLOAD;
|
||||
*payload.dispatch_result.unspent_weight.ref_time_mut() = 1;
|
||||
assert_eq!(
|
||||
lane.receive_message::<TestMessageDispatch>(
|
||||
&TEST_RELAYER_A,
|
||||
1,
|
||||
inbound_message_data(payload)
|
||||
),
|
||||
ReceptionResult::Dispatched(dispatch_result(1))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_message_is_confirmed_correctly() {
|
||||
run_test(|| {
|
||||
let mut lane = active_inbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
receive_regular_message(&mut lane, 1);
|
||||
receive_regular_message(&mut lane, 2);
|
||||
assert_eq!(
|
||||
lane.receive_state_update(OutboundLaneData {
|
||||
latest_received_nonce: 1,
|
||||
..Default::default()
|
||||
}),
|
||||
Some(1),
|
||||
);
|
||||
assert_eq!(
|
||||
inbound_unrewarded_relayers_state(test_lane_id()),
|
||||
UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 1,
|
||||
last_delivered_nonce: 2,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
// 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/>.
|
||||
|
||||
use crate::{
|
||||
BridgedChainOf, Config, InboundLane, InboundLaneStorage, InboundLanes, OutboundLane,
|
||||
OutboundLaneStorage, OutboundLanes, OutboundMessages, StoredInboundLaneData,
|
||||
StoredMessagePayload,
|
||||
};
|
||||
|
||||
use bp_messages::{
|
||||
target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneState, MessageKey,
|
||||
MessageNonce, OutboundLaneData,
|
||||
};
|
||||
use pezbp_runtime::AccountIdOf;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{ensure, pezsp_runtime::RuntimeDebug, PalletError};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Lanes manager errors.
|
||||
#[derive(
|
||||
Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
|
||||
)]
|
||||
pub enum LanesManagerError {
|
||||
/// Inbound lane already exists.
|
||||
InboundLaneAlreadyExists,
|
||||
/// Outbound lane already exists.
|
||||
OutboundLaneAlreadyExists,
|
||||
/// No inbound lane with given id.
|
||||
UnknownInboundLane,
|
||||
/// No outbound lane with given id.
|
||||
UnknownOutboundLane,
|
||||
/// Inbound lane with given id is closed.
|
||||
ClosedInboundLane,
|
||||
/// Outbound lane with given id is closed.
|
||||
ClosedOutboundLane,
|
||||
/// Message dispatcher is inactive at given inbound lane. This is logical equivalent
|
||||
/// of the [`Self::ClosedInboundLane`] variant.
|
||||
LaneDispatcherInactive,
|
||||
}
|
||||
|
||||
/// Message lanes manager.
|
||||
pub struct LanesManager<T, I>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for LanesManager<T, I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> LanesManager<T, I> {
|
||||
/// Create new lanes manager.
|
||||
pub fn new() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
|
||||
/// Create new inbound lane in `Opened` state.
|
||||
pub fn create_inbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
InboundLanes::<T, I>::try_mutate(lane_id, |lane| match lane {
|
||||
Some(_) => Err(LanesManagerError::InboundLaneAlreadyExists),
|
||||
None => {
|
||||
*lane = Some(StoredInboundLaneData(InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
..Default::default()
|
||||
}));
|
||||
Ok(())
|
||||
},
|
||||
})?;
|
||||
|
||||
self.active_inbound_lane(lane_id)
|
||||
}
|
||||
|
||||
/// Create new outbound lane in `Opened` state.
|
||||
pub fn create_outbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
OutboundLanes::<T, I>::try_mutate(lane_id, |lane| match lane {
|
||||
Some(_) => Err(LanesManagerError::OutboundLaneAlreadyExists),
|
||||
None => {
|
||||
*lane = Some(OutboundLaneData { state: LaneState::Opened, ..Default::default() });
|
||||
Ok(())
|
||||
},
|
||||
})?;
|
||||
|
||||
self.active_outbound_lane(lane_id)
|
||||
}
|
||||
|
||||
/// Get existing inbound lane, checking that it is in usable state.
|
||||
pub fn active_inbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, true)?))
|
||||
}
|
||||
|
||||
/// Get existing outbound lane, checking that it is in usable state.
|
||||
pub fn active_outbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, true)?))
|
||||
}
|
||||
|
||||
/// Get existing inbound lane without any additional state checks.
|
||||
pub fn any_state_inbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, false)?))
|
||||
}
|
||||
|
||||
/// Get existing outbound lane without any additional state checks.
|
||||
pub fn any_state_outbound_lane(
|
||||
&self,
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, LanesManagerError> {
|
||||
Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, false)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime inbound lane storage.
|
||||
pub struct RuntimeInboundLaneStorage<T: Config<I>, I: 'static = ()> {
|
||||
pub(crate) lane_id: T::LaneId,
|
||||
pub(crate) cached_data: InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> RuntimeInboundLaneStorage<T, I> {
|
||||
/// Creates new runtime inbound lane storage for given **existing** lane.
|
||||
fn from_lane_id(
|
||||
lane_id: T::LaneId,
|
||||
check_active: bool,
|
||||
) -> Result<RuntimeInboundLaneStorage<T, I>, LanesManagerError> {
|
||||
let cached_data =
|
||||
InboundLanes::<T, I>::get(lane_id).ok_or(LanesManagerError::UnknownInboundLane)?;
|
||||
|
||||
if check_active {
|
||||
// check that the lane is not explicitly closed
|
||||
ensure!(cached_data.state.is_active(), LanesManagerError::ClosedInboundLane);
|
||||
// apart from the explicit closure, the lane may be unable to receive any messages.
|
||||
// Right now we do an additional check here, but it may be done later (e.g. by
|
||||
// explicitly closing the lane and reopening it from
|
||||
// `pezpallet-xcm-bridge-hub::on-initialize`)
|
||||
//
|
||||
// The fact that we only check it here, means that the `MessageDispatch` may switch
|
||||
// to inactive state during some message dispatch in the middle of message delivery
|
||||
// transaction. But we treat result of `MessageDispatch::is_active()` as a hint, so
|
||||
// we know that it won't drop messages - just it experiences problems with processing.
|
||||
// This would allow us to check that in our signed extensions, and invalidate
|
||||
// transaction early, thus avoiding losing honest relayers funds. This problem should
|
||||
// gone with relayers coordination protocol.
|
||||
//
|
||||
// There's a limit on number of messages in the message delivery transaction, so even
|
||||
// if we dispatch (enqueue) some additional messages, we'll know the maximal queue
|
||||
// length;
|
||||
ensure!(
|
||||
T::MessageDispatch::is_active(lane_id),
|
||||
LanesManagerError::LaneDispatcherInactive
|
||||
);
|
||||
}
|
||||
|
||||
Ok(RuntimeInboundLaneStorage { lane_id, cached_data: cached_data.into() })
|
||||
}
|
||||
|
||||
/// Returns number of bytes that may be subtracted from the PoV component of
|
||||
/// `receive_messages_proof` call, because the actual inbound lane state is smaller than the
|
||||
/// maximal configured.
|
||||
///
|
||||
/// Maximal inbound lane state set size is configured by the
|
||||
/// `MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` constant from the pezpallet configuration. The PoV
|
||||
/// of the call includes the maximal size of inbound lane state. If the actual size is smaller,
|
||||
/// we may subtract extra bytes from this component.
|
||||
pub fn extra_proof_size_bytes(&self) -> u64 {
|
||||
let max_encoded_len = StoredInboundLaneData::<T, I>::max_encoded_len();
|
||||
let relayers_count = self.data().relayers.len();
|
||||
let actual_encoded_len =
|
||||
InboundLaneData::<AccountIdOf<BridgedChainOf<T, I>>>::encoded_size_hint(relayers_count)
|
||||
.unwrap_or(usize::MAX);
|
||||
max_encoded_len.saturating_sub(actual_encoded_len) as _
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage<T, I> {
|
||||
type Relayer = AccountIdOf<BridgedChainOf<T, I>>;
|
||||
type LaneId = T::LaneId;
|
||||
|
||||
fn id(&self) -> Self::LaneId {
|
||||
self.lane_id
|
||||
}
|
||||
|
||||
fn max_unrewarded_relayer_entries(&self) -> MessageNonce {
|
||||
BridgedChainOf::<T, I>::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX
|
||||
}
|
||||
|
||||
fn max_unconfirmed_messages(&self) -> MessageNonce {
|
||||
BridgedChainOf::<T, I>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
|
||||
}
|
||||
|
||||
fn data(&self) -> InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>> {
|
||||
self.cached_data.clone()
|
||||
}
|
||||
|
||||
fn set_data(&mut self, data: InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>) {
|
||||
self.cached_data = data.clone();
|
||||
InboundLanes::<T, I>::insert(self.lane_id, StoredInboundLaneData::<T, I>(data))
|
||||
}
|
||||
|
||||
fn purge(self) {
|
||||
InboundLanes::<T, I>::remove(self.lane_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime outbound lane storage.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RuntimeOutboundLaneStorage<T: Config<I>, I: 'static> {
|
||||
pub(crate) lane_id: T::LaneId,
|
||||
pub(crate) cached_data: OutboundLaneData,
|
||||
pub(crate) _phantom: PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> RuntimeOutboundLaneStorage<T, I> {
|
||||
/// Creates new runtime outbound lane storage for given **existing** lane.
|
||||
fn from_lane_id(lane_id: T::LaneId, check_active: bool) -> Result<Self, LanesManagerError> {
|
||||
let cached_data =
|
||||
OutboundLanes::<T, I>::get(lane_id).ok_or(LanesManagerError::UnknownOutboundLane)?;
|
||||
ensure!(
|
||||
!check_active || cached_data.state.is_active(),
|
||||
LanesManagerError::ClosedOutboundLane
|
||||
);
|
||||
Ok(Self { lane_id, cached_data, _phantom: PhantomData })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage<T, I> {
|
||||
type StoredMessagePayload = StoredMessagePayload<T, I>;
|
||||
type LaneId = T::LaneId;
|
||||
|
||||
fn id(&self) -> Self::LaneId {
|
||||
self.lane_id
|
||||
}
|
||||
|
||||
fn data(&self) -> OutboundLaneData {
|
||||
self.cached_data.clone()
|
||||
}
|
||||
|
||||
fn set_data(&mut self, data: OutboundLaneData) {
|
||||
self.cached_data = data.clone();
|
||||
OutboundLanes::<T, I>::insert(self.lane_id, data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn message(&self, nonce: &MessageNonce) -> Option<Self::StoredMessagePayload> {
|
||||
OutboundMessages::<T, I>::get(MessageKey { lane_id: self.lane_id, nonce: *nonce })
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn save_message(&mut self, nonce: MessageNonce, message_payload: Self::StoredMessagePayload) {
|
||||
OutboundMessages::<T, I>::insert(
|
||||
MessageKey { lane_id: self.lane_id, nonce },
|
||||
message_payload,
|
||||
);
|
||||
}
|
||||
|
||||
fn remove_message(&mut self, nonce: &MessageNonce) {
|
||||
OutboundMessages::<T, I>::remove(MessageKey { lane_id: self.lane_id, nonce: *nonce });
|
||||
}
|
||||
|
||||
fn purge(self) {
|
||||
OutboundLanes::<T, I>::remove(self.lane_id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,791 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Runtime module that allows sending and receiving messages using lane concept:
|
||||
//!
|
||||
//! 1) the message is sent using `send_message()` call;
|
||||
//! 2) every outbound message is assigned nonce;
|
||||
//! 3) the messages are stored in the storage;
|
||||
//! 4) external component (relay) delivers messages to bridged chain;
|
||||
//! 5) messages are processed in order (ordered by assigned nonce);
|
||||
//! 6) relay may send proof-of-delivery back to this chain.
|
||||
//!
|
||||
//! Once message is sent, its progress can be tracked by looking at module events.
|
||||
//! The assigned nonce is reported using `MessageAccepted` event. When message is
|
||||
//! delivered to the the bridged chain, it is reported using `MessagesDelivered` event.
|
||||
//!
|
||||
//! **IMPORTANT NOTE**: after generating weights (custom `WeighInfo` implementation) for
|
||||
//! your runtime (where this module is plugged to), please add test for these weights.
|
||||
//! The test should call the `ensure_weights_are_correct` function from this module.
|
||||
//! If this test fails with your weights, then either weights are computed incorrectly,
|
||||
//! or some benchmarks assumptions are broken for your runtime.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use inbound_lane::{InboundLane, InboundLaneStorage, StoredInboundLaneData};
|
||||
pub use lanes_manager::{
|
||||
LanesManager, LanesManagerError, RuntimeInboundLaneStorage, RuntimeOutboundLaneStorage,
|
||||
};
|
||||
pub use outbound_lane::{
|
||||
OutboundLane, OutboundLaneStorage, ReceptionConfirmationError, StoredMessagePayload,
|
||||
};
|
||||
pub use weights::WeightInfo;
|
||||
pub use weights_ext::{
|
||||
ensure_able_to_receive_confirmation, ensure_able_to_receive_message,
|
||||
ensure_maximal_message_dispatch, ensure_weights_are_correct, WeightInfoExt,
|
||||
EXPECTED_DEFAULT_MESSAGE_LENGTH, EXTRA_STORAGE_PROOF_SIZE,
|
||||
};
|
||||
|
||||
use bp_header_pez_chain::HeaderChain;
|
||||
use bp_messages::{
|
||||
source_chain::{
|
||||
DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered,
|
||||
SendMessageArtifacts,
|
||||
},
|
||||
target_chain::{
|
||||
DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch,
|
||||
ProvedLaneMessages, ProvedMessages,
|
||||
},
|
||||
ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, MessageKey,
|
||||
MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails,
|
||||
UnrewardedRelayersState, VerificationError,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt,
|
||||
Size,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound};
|
||||
use pezsp_std::{marker::PhantomData, prelude::*};
|
||||
|
||||
mod call_ext;
|
||||
mod inbound_lane;
|
||||
mod lanes_manager;
|
||||
mod outbound_lane;
|
||||
mod proofs;
|
||||
mod tests;
|
||||
mod weights_ext;
|
||||
|
||||
pub mod weights;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmarking;
|
||||
pub mod migration;
|
||||
|
||||
pub use call_ext::*;
|
||||
pub use pezpallet::*;
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub use tests::*;
|
||||
|
||||
/// The target that will be used when publishing logs related to this pezpallet.
|
||||
pub const LOG_TARGET: &str = "runtime::bridge-messages";
|
||||
|
||||
#[pezframe_support::pezpallet]
|
||||
pub mod pezpallet {
|
||||
use super::*;
|
||||
use bp_messages::{LaneIdType, ReceivedMessages, ReceptionResult};
|
||||
use pezbp_runtime::RangeInclusiveExt;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pezpallet::config]
|
||||
pub trait Config<I: 'static = ()>: pezframe_system::Config {
|
||||
// General types
|
||||
|
||||
/// The overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
/// Benchmarks results from runtime we're plugged into.
|
||||
type WeightInfo: WeightInfoExt;
|
||||
|
||||
/// This chain type.
|
||||
type ThisChain: ChainWithMessages;
|
||||
/// Bridged chain type.
|
||||
type BridgedChain: ChainWithMessages;
|
||||
/// Bridged chain headers provider.
|
||||
type BridgedHeaderChain: HeaderChain<Self::BridgedChain>;
|
||||
|
||||
/// Payload type of outbound messages. This payload is dispatched on the bridged chain.
|
||||
type OutboundPayload: Parameter + Size;
|
||||
/// Payload type of inbound messages. This payload is dispatched on this chain.
|
||||
type InboundPayload: Decode;
|
||||
/// Lane identifier type.
|
||||
type LaneId: LaneIdType;
|
||||
|
||||
/// Handler for relayer payments that happen during message delivery transaction.
|
||||
type DeliveryPayments: DeliveryPayments<Self::AccountId>;
|
||||
/// Handler for relayer payments that happen during message delivery confirmation
|
||||
/// transaction.
|
||||
type DeliveryConfirmationPayments: DeliveryConfirmationPayments<
|
||||
Self::AccountId,
|
||||
Self::LaneId,
|
||||
>;
|
||||
/// Delivery confirmation callback.
|
||||
type OnMessagesDelivered: OnMessagesDelivered<Self::LaneId>;
|
||||
|
||||
/// Message dispatch handler.
|
||||
type MessageDispatch: MessageDispatch<
|
||||
DispatchPayload = Self::InboundPayload,
|
||||
LaneId = Self::LaneId,
|
||||
>;
|
||||
}
|
||||
|
||||
/// Shortcut to this chain type for Config.
|
||||
pub type ThisChainOf<T, I> = <T as Config<I>>::ThisChain;
|
||||
/// Shortcut to bridged chain type for Config.
|
||||
pub type BridgedChainOf<T, I> = <T as Config<I>>::BridgedChain;
|
||||
/// Shortcut to bridged header chain type for Config.
|
||||
pub type BridgedHeaderChainOf<T, I> = <T as Config<I>>::BridgedHeaderChain;
|
||||
/// Shortcut to lane identifier type for Config.
|
||||
pub type LaneIdOf<T, I> = <T as Config<I>>::LaneId;
|
||||
|
||||
#[pezpallet::pezpallet]
|
||||
#[pezpallet::storage_version(migration::STORAGE_VERSION)]
|
||||
pub struct Pezpallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pezpallet<T, I> {
|
||||
const LOG_TARGET: &'static str = LOG_TARGET;
|
||||
type OwnerStorage = PalletOwner<T, I>;
|
||||
type OperatingMode = MessagesOperatingMode;
|
||||
type OperatingModeStorage = PalletOperatingMode<T, I>;
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Change `PalletOwner`.
|
||||
///
|
||||
/// May only be called either by root, or by `PalletOwner`.
|
||||
#[pezpallet::call_index(0)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
|
||||
<Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
|
||||
}
|
||||
|
||||
/// Halt or resume all/some pezpallet operations.
|
||||
///
|
||||
/// May only be called either by root, or by `PalletOwner`.
|
||||
#[pezpallet::call_index(1)]
|
||||
#[pezpallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_operating_mode(
|
||||
origin: OriginFor<T>,
|
||||
operating_mode: MessagesOperatingMode,
|
||||
) -> DispatchResult {
|
||||
<Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
|
||||
}
|
||||
|
||||
/// Receive messages proof from bridged chain.
|
||||
///
|
||||
/// The weight of the call assumes that the transaction always brings outbound lane
|
||||
/// state update. Because of that, the submitter (relayer) has no benefit of not including
|
||||
/// this data in the transaction, so reward confirmations lags should be minimal.
|
||||
///
|
||||
/// The call fails if:
|
||||
///
|
||||
/// - the pezpallet is halted;
|
||||
///
|
||||
/// - the call origin is not `Signed(_)`;
|
||||
///
|
||||
/// - there are too many messages in the proof;
|
||||
///
|
||||
/// - the proof verification procedure returns an error - e.g. because header used to craft
|
||||
/// proof is not imported by the associated finality pezpallet;
|
||||
///
|
||||
/// - the `dispatch_weight` argument is not sufficient to dispatch all bundled messages.
|
||||
///
|
||||
/// The call may succeed, but some messages may not be delivered e.g. if they are not fit
|
||||
/// into the unrewarded relayers vector.
|
||||
#[pezpallet::call_index(2)]
|
||||
#[pezpallet::weight(T::WeightInfo::receive_messages_proof_weight(&**proof, *messages_count, *dispatch_weight))]
|
||||
pub fn receive_messages_proof(
|
||||
origin: OriginFor<T>,
|
||||
relayer_id_at_bridged_chain: AccountIdOf<BridgedChainOf<T, I>>,
|
||||
proof: Box<FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>>,
|
||||
messages_count: u32,
|
||||
dispatch_weight: Weight,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
|
||||
let relayer_id_at_this_chain = ensure_signed(origin)?;
|
||||
|
||||
// reject transactions that are declaring too many messages
|
||||
ensure!(
|
||||
MessageNonce::from(messages_count) <=
|
||||
BridgedChainOf::<T, I>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
|
||||
Error::<T, I>::TooManyMessagesInTheProof
|
||||
);
|
||||
|
||||
// why do we need to know the weight of this (`receive_messages_proof`) call? Because
|
||||
// we may want to return some funds for not-dispatching (or partially dispatching) some
|
||||
// messages to the call origin (relayer). And this is done by returning actual weight
|
||||
// from the call. But we only know dispatch weight of every message. So to refund
|
||||
// relayer because we have not dispatched message, we need to:
|
||||
//
|
||||
// ActualWeight = DeclaredWeight - Message.DispatchWeight
|
||||
//
|
||||
// The DeclaredWeight is exactly what's computed here. Unfortunately it is impossible
|
||||
// to get pre-computed value (and it has been already computed by the executive).
|
||||
let declared_weight = T::WeightInfo::receive_messages_proof_weight(
|
||||
&*proof,
|
||||
messages_count,
|
||||
dispatch_weight,
|
||||
);
|
||||
let mut actual_weight = declared_weight;
|
||||
|
||||
// verify messages proof && convert proof into messages
|
||||
let (lane_id, lane_data) =
|
||||
verify_and_decode_messages_proof::<T, I>(*proof, messages_count).map_err(
|
||||
|err| {
|
||||
tracing::trace!(target: LOG_TARGET, error=?err, "Rejecting invalid messages proof");
|
||||
|
||||
Error::<T, I>::InvalidMessagesProof
|
||||
},
|
||||
)?;
|
||||
|
||||
// dispatch messages and (optionally) update lane(s) state(s)
|
||||
let mut total_messages = 0;
|
||||
let mut valid_messages = 0;
|
||||
let mut dispatch_weight_left = dispatch_weight;
|
||||
let mut lane = active_inbound_lane::<T, I>(lane_id)?;
|
||||
|
||||
// subtract extra storage proof bytes from the actual PoV size - there may be
|
||||
// less unrewarded relayers than the maximal configured value
|
||||
let lane_extra_proof_size_bytes = lane.storage().extra_proof_size_bytes();
|
||||
actual_weight = actual_weight.set_proof_size(
|
||||
actual_weight.proof_size().saturating_sub(lane_extra_proof_size_bytes),
|
||||
);
|
||||
|
||||
if let Some(lane_state) = lane_data.lane_state {
|
||||
let updated_latest_confirmed_nonce = lane.receive_state_update(lane_state);
|
||||
if let Some(updated_latest_confirmed_nonce) = updated_latest_confirmed_nonce {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
latest_confirmed_nonce=%updated_latest_confirmed_nonce,
|
||||
unrewarded_relayers=?UnrewardedRelayersState::from(&lane.storage().data()),
|
||||
"Received state update"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut messages_received_status =
|
||||
ReceivedMessages::new(lane_id, Vec::with_capacity(lane_data.messages.len()));
|
||||
for mut message in lane_data.messages {
|
||||
debug_assert_eq!(message.key.lane_id, lane_id);
|
||||
total_messages += 1;
|
||||
|
||||
// ensure that relayer has declared enough weight for dispatching next message
|
||||
// on this lane. We can't dispatch lane messages out-of-order, so if declared
|
||||
// weight is not enough, let's move to next lane
|
||||
let message_dispatch_weight = T::MessageDispatch::dispatch_weight(&mut message);
|
||||
if message_dispatch_weight.any_gt(dispatch_weight_left) {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
declared=%message_dispatch_weight,
|
||||
left=%dispatch_weight_left,
|
||||
"Cannot dispatch any more messages"
|
||||
);
|
||||
|
||||
fail!(Error::<T, I>::InsufficientDispatchWeight);
|
||||
}
|
||||
|
||||
let receival_result = lane.receive_message::<T::MessageDispatch>(
|
||||
&relayer_id_at_bridged_chain,
|
||||
message.key.nonce,
|
||||
message.data,
|
||||
);
|
||||
|
||||
// note that we're returning unspent weight to relayer even if message has been
|
||||
// rejected by the lane. This allows relayers to submit spam transactions with
|
||||
// e.g. the same set of already delivered messages over and over again, without
|
||||
// losing funds for messages dispatch. But keep in mind that relayer pays base
|
||||
// delivery transaction cost anyway. And base cost covers everything except
|
||||
// dispatch, so we have a balance here.
|
||||
let unspent_weight = match &receival_result {
|
||||
ReceptionResult::Dispatched(dispatch_result) => {
|
||||
valid_messages += 1;
|
||||
dispatch_result.unspent_weight
|
||||
},
|
||||
ReceptionResult::InvalidNonce |
|
||||
ReceptionResult::TooManyUnrewardedRelayers |
|
||||
ReceptionResult::TooManyUnconfirmedMessages => message_dispatch_weight,
|
||||
};
|
||||
messages_received_status.push(message.key.nonce, receival_result);
|
||||
|
||||
let unspent_weight = unspent_weight.min(message_dispatch_weight);
|
||||
dispatch_weight_left -= message_dispatch_weight - unspent_weight;
|
||||
actual_weight = actual_weight.saturating_sub(unspent_weight);
|
||||
}
|
||||
|
||||
// let's now deal with relayer payments
|
||||
T::DeliveryPayments::pay_reward(
|
||||
relayer_id_at_this_chain,
|
||||
total_messages,
|
||||
valid_messages,
|
||||
actual_weight,
|
||||
);
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
total=%total_messages,
|
||||
valid=%valid_messages,
|
||||
%actual_weight,
|
||||
%declared_weight,
|
||||
"Received messages."
|
||||
);
|
||||
|
||||
Self::deposit_event(Event::MessagesReceived(messages_received_status));
|
||||
|
||||
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
|
||||
}
|
||||
|
||||
/// Receive messages delivery proof from bridged chain.
|
||||
#[pezpallet::call_index(3)]
|
||||
#[pezpallet::weight(T::WeightInfo::receive_messages_delivery_proof_weight(
|
||||
proof,
|
||||
relayers_state,
|
||||
))]
|
||||
pub fn receive_messages_delivery_proof(
|
||||
origin: OriginFor<T>,
|
||||
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
|
||||
mut relayers_state: UnrewardedRelayersState,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
|
||||
|
||||
let proof_size = proof.size();
|
||||
let confirmation_relayer = ensure_signed(origin)?;
|
||||
let (lane_id, lane_data) = proofs::verify_messages_delivery_proof::<T, I>(proof)
|
||||
.map_err(|err| {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
error=?err,
|
||||
"Rejecting invalid messages delivery proof"
|
||||
);
|
||||
|
||||
Error::<T, I>::InvalidMessagesDeliveryProof
|
||||
})?;
|
||||
ensure!(
|
||||
relayers_state.is_valid(&lane_data),
|
||||
Error::<T, I>::InvalidUnrewardedRelayersState
|
||||
);
|
||||
|
||||
// mark messages as delivered
|
||||
let mut lane = any_state_outbound_lane::<T, I>(lane_id)?;
|
||||
let last_delivered_nonce = lane_data.last_delivered_nonce();
|
||||
let confirmed_messages = lane
|
||||
.confirm_delivery(
|
||||
relayers_state.total_messages,
|
||||
last_delivered_nonce,
|
||||
&lane_data.relayers,
|
||||
)
|
||||
.map_err(Error::<T, I>::ReceptionConfirmation)?;
|
||||
|
||||
if let Some(confirmed_messages) = confirmed_messages {
|
||||
// emit 'delivered' event
|
||||
let received_range = confirmed_messages.begin..=confirmed_messages.end;
|
||||
Self::deposit_event(Event::MessagesDelivered {
|
||||
lane_id: lane_id.into(),
|
||||
messages: confirmed_messages,
|
||||
});
|
||||
|
||||
// if some new messages have been confirmed, reward relayers
|
||||
let actually_rewarded_relayers = T::DeliveryConfirmationPayments::pay_reward(
|
||||
lane_id,
|
||||
lane_data.relayers,
|
||||
&confirmation_relayer,
|
||||
&received_range,
|
||||
);
|
||||
|
||||
// update relayers state with actual numbers to compute actual weight below
|
||||
relayers_state.unrewarded_relayer_entries = pezsp_std::cmp::min(
|
||||
relayers_state.unrewarded_relayer_entries,
|
||||
actually_rewarded_relayers,
|
||||
);
|
||||
relayers_state.total_messages = pezsp_std::cmp::min(
|
||||
relayers_state.total_messages,
|
||||
received_range.checked_len().unwrap_or(MessageNonce::MAX),
|
||||
);
|
||||
};
|
||||
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
%last_delivered_nonce,
|
||||
"Received messages delivery proof up to (and including)"
|
||||
);
|
||||
|
||||
// notify others about messages delivery
|
||||
T::OnMessagesDelivered::on_messages_delivered(
|
||||
lane_id,
|
||||
lane.data().queued_messages().saturating_len(),
|
||||
);
|
||||
|
||||
// because of lags, the inbound lane state (`lane_data`) may have entries for
|
||||
// already rewarded relayers and messages (if all entries are duplicated, then
|
||||
// this transaction must be filtered out by our signed extension)
|
||||
let actual_weight = T::WeightInfo::receive_messages_delivery_proof_weight(
|
||||
&PreComputedSize(proof_size as usize),
|
||||
&relayers_state,
|
||||
);
|
||||
|
||||
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::event]
|
||||
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// Message has been accepted and is waiting to be delivered.
|
||||
MessageAccepted {
|
||||
/// Lane, which has accepted the message.
|
||||
lane_id: T::LaneId,
|
||||
/// Nonce of accepted message.
|
||||
nonce: MessageNonce,
|
||||
},
|
||||
/// Messages have been received from the bridged chain.
|
||||
MessagesReceived(
|
||||
/// Result of received messages dispatch.
|
||||
ReceivedMessages<
|
||||
<T::MessageDispatch as MessageDispatch>::DispatchLevelResult,
|
||||
T::LaneId,
|
||||
>,
|
||||
),
|
||||
/// Messages in the inclusive range have been delivered to the bridged chain.
|
||||
MessagesDelivered {
|
||||
/// Lane for which the delivery has been confirmed.
|
||||
lane_id: T::LaneId,
|
||||
/// Delivered messages.
|
||||
messages: DeliveredMessages,
|
||||
},
|
||||
}
|
||||
|
||||
#[pezpallet::error]
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// Pezpallet is not in Normal operating mode.
|
||||
NotOperatingNormally,
|
||||
/// Error that is reported by the lanes manager.
|
||||
LanesManager(LanesManagerError),
|
||||
/// Message has been treated as invalid by the pezpallet logic.
|
||||
MessageRejectedByPallet(VerificationError),
|
||||
/// The transaction brings too many messages.
|
||||
TooManyMessagesInTheProof,
|
||||
/// Invalid messages has been submitted.
|
||||
InvalidMessagesProof,
|
||||
/// Invalid messages delivery proof has been submitted.
|
||||
InvalidMessagesDeliveryProof,
|
||||
/// The relayer has declared invalid unrewarded relayers state in the
|
||||
/// `receive_messages_delivery_proof` call.
|
||||
InvalidUnrewardedRelayersState,
|
||||
/// The cumulative dispatch weight, passed by relayer is not enough to cover dispatch
|
||||
/// of all bundled messages.
|
||||
InsufficientDispatchWeight,
|
||||
/// Error confirming messages receival.
|
||||
ReceptionConfirmation(ReceptionConfirmationError),
|
||||
/// Error generated by the `OwnedBridgeModule` trait.
|
||||
BridgeModule(pezbp_runtime::OwnedBridgeModuleError),
|
||||
}
|
||||
|
||||
/// Optional pezpallet owner.
|
||||
///
|
||||
/// Pezpallet owner has a right to halt all pezpallet operations and then resume it. If it is
|
||||
/// `None`, then there are no direct ways to halt/resume pezpallet operations, but other
|
||||
/// runtime methods may still be used to do that (i.e. democracy::referendum to update halt
|
||||
/// flag directly or call the `set_operating_mode`).
|
||||
#[pezpallet::storage]
|
||||
pub type PalletOwner<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
|
||||
|
||||
/// The current operating mode of the pezpallet.
|
||||
///
|
||||
/// Depending on the mode either all, some, or no transactions will be allowed.
|
||||
#[pezpallet::storage]
|
||||
pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, MessagesOperatingMode, ValueQuery>;
|
||||
|
||||
// TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/89: let's limit number of
|
||||
// possible opened lanes && use it to constraint maps below
|
||||
|
||||
/// Map of lane id => inbound lane data.
|
||||
#[pezpallet::storage]
|
||||
pub type InboundLanes<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Blake2_128Concat, T::LaneId, StoredInboundLaneData<T, I>, OptionQuery>;
|
||||
|
||||
/// Map of lane id => outbound lane data.
|
||||
#[pezpallet::storage]
|
||||
pub type OutboundLanes<T: Config<I>, I: 'static = ()> = StorageMap<
|
||||
Hasher = Blake2_128Concat,
|
||||
Key = T::LaneId,
|
||||
Value = OutboundLaneData,
|
||||
QueryKind = OptionQuery,
|
||||
>;
|
||||
|
||||
/// All queued outbound messages.
|
||||
#[pezpallet::storage]
|
||||
pub type OutboundMessages<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Blake2_128Concat, MessageKey<T::LaneId>, StoredMessagePayload<T, I>>;
|
||||
|
||||
#[pezpallet::genesis_config]
|
||||
#[derive(DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
|
||||
/// Initial pezpallet operating mode.
|
||||
pub operating_mode: MessagesOperatingMode,
|
||||
/// Initial pezpallet owner.
|
||||
pub owner: Option<T::AccountId>,
|
||||
/// Opened lanes.
|
||||
pub opened_lanes: Vec<T::LaneId>,
|
||||
/// Dummy marker.
|
||||
#[serde(skip)]
|
||||
pub _phantom: pezsp_std::marker::PhantomData<I>,
|
||||
}
|
||||
|
||||
#[pezpallet::genesis_build]
|
||||
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
|
||||
fn build(&self) {
|
||||
PalletOperatingMode::<T, I>::put(self.operating_mode);
|
||||
if let Some(ref owner) = self.owner {
|
||||
PalletOwner::<T, I>::put(owner);
|
||||
}
|
||||
|
||||
for lane_id in &self.opened_lanes {
|
||||
InboundLanes::<T, I>::insert(lane_id, InboundLaneData::opened());
|
||||
OutboundLanes::<T, I>::insert(lane_id, OutboundLaneData::opened());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pezpallet<T, I> {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_n: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Self::do_try_state()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Get stored data of the outbound message with given nonce.
|
||||
pub fn outbound_message_data(
|
||||
lane: T::LaneId,
|
||||
nonce: MessageNonce,
|
||||
) -> Option<MessagePayload> {
|
||||
OutboundMessages::<T, I>::get(MessageKey { lane_id: lane, nonce }).map(Into::into)
|
||||
}
|
||||
|
||||
/// Prepare data, related to given inbound message.
|
||||
pub fn inbound_message_data(
|
||||
lane: T::LaneId,
|
||||
payload: MessagePayload,
|
||||
outbound_details: OutboundMessageDetails,
|
||||
) -> InboundMessageDetails {
|
||||
let mut dispatch_message = DispatchMessage {
|
||||
key: MessageKey { lane_id: lane, nonce: outbound_details.nonce },
|
||||
data: payload.into(),
|
||||
};
|
||||
InboundMessageDetails {
|
||||
dispatch_weight: T::MessageDispatch::dispatch_weight(&mut dispatch_message),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return outbound lane data.
|
||||
pub fn outbound_lane_data(lane: T::LaneId) -> Option<OutboundLaneData> {
|
||||
OutboundLanes::<T, I>::get(lane)
|
||||
}
|
||||
|
||||
/// Return inbound lane data.
|
||||
pub fn inbound_lane_data(
|
||||
lane: T::LaneId,
|
||||
) -> Option<InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>> {
|
||||
InboundLanes::<T, I>::get(lane).map(|lane| lane.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "try-runtime", test))]
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Ensure the correctness of the state of this pezpallet.
|
||||
pub fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Self::do_try_state_for_outbound_lanes()
|
||||
}
|
||||
|
||||
/// Ensure the correctness of the state of outbound lanes.
|
||||
pub fn do_try_state_for_outbound_lanes() -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
use pezsp_runtime::traits::One;
|
||||
use pezsp_std::vec::Vec;
|
||||
|
||||
// collect unpruned lanes
|
||||
let mut unpruned_lanes = Vec::new();
|
||||
for (lane_id, lane_data) in OutboundLanes::<T, I>::iter() {
|
||||
let Some(expected_last_prunned_nonce) =
|
||||
lane_data.oldest_unpruned_nonce.checked_sub(One::one())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// collect message_nonces that were supposed to be pruned
|
||||
let mut unpruned_message_nonces = Vec::new();
|
||||
const MAX_MESSAGES_ITERATION: u64 = 16;
|
||||
let start_nonce =
|
||||
expected_last_prunned_nonce.checked_sub(MAX_MESSAGES_ITERATION).unwrap_or(0);
|
||||
for current_nonce in start_nonce..=expected_last_prunned_nonce {
|
||||
// check a message for current_nonce
|
||||
if OutboundMessages::<T, I>::contains_key(MessageKey {
|
||||
lane_id,
|
||||
nonce: current_nonce,
|
||||
}) {
|
||||
unpruned_message_nonces.push(current_nonce);
|
||||
}
|
||||
}
|
||||
|
||||
if !unpruned_message_nonces.is_empty() {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
?lane_data,
|
||||
?unpruned_message_nonces,
|
||||
"do_try_state_for_outbound_lanes found",
|
||||
);
|
||||
unpruned_lanes.push((lane_id, lane_data, unpruned_message_nonces));
|
||||
}
|
||||
}
|
||||
|
||||
// ensure messages before `oldest_unpruned_nonce` are really pruned.
|
||||
ensure!(unpruned_lanes.is_empty(), "Found unpruned lanes!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure, containing a validated message payload and all the info required
|
||||
/// to send it on the bridge.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SendMessageArgs<T: Config<I>, I: 'static> {
|
||||
lane_id: T::LaneId,
|
||||
lane: OutboundLane<RuntimeOutboundLaneStorage<T, I>>,
|
||||
payload: StoredMessagePayload<T, I>,
|
||||
}
|
||||
|
||||
impl<T, I> bp_messages::source_chain::MessagesBridge<T::OutboundPayload, T::LaneId> for Pezpallet<T, I>
|
||||
where
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
{
|
||||
type Error = Error<T, I>;
|
||||
type SendMessageArgs = SendMessageArgs<T, I>;
|
||||
|
||||
fn validate_message(
|
||||
lane_id: T::LaneId,
|
||||
message: &T::OutboundPayload,
|
||||
) -> Result<SendMessageArgs<T, I>, Self::Error> {
|
||||
// we can't accept any messages if the pezpallet is halted
|
||||
ensure_normal_operating_mode::<T, I>()?;
|
||||
|
||||
// check lane
|
||||
let lane = active_outbound_lane::<T, I>(lane_id)?;
|
||||
|
||||
Ok(SendMessageArgs {
|
||||
lane_id,
|
||||
lane,
|
||||
payload: StoredMessagePayload::<T, I>::try_from(message.encode()).map_err(|_| {
|
||||
Error::<T, I>::MessageRejectedByPallet(VerificationError::MessageTooLarge)
|
||||
})?,
|
||||
})
|
||||
}
|
||||
|
||||
fn send_message(args: SendMessageArgs<T, I>) -> SendMessageArtifacts {
|
||||
// save message in outbound storage and emit event
|
||||
let mut lane = args.lane;
|
||||
let message_len = args.payload.len();
|
||||
let nonce = lane.send_message(args.payload);
|
||||
|
||||
// return number of messages in the queue to let sender know about its state
|
||||
let enqueued_messages = lane.data().queued_messages().saturating_len();
|
||||
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
lane_id=?args.lane_id,
|
||||
%nonce,
|
||||
message_size=?message_len,
|
||||
"Accepted message"
|
||||
);
|
||||
|
||||
Pezpallet::<T, I>::deposit_event(Event::MessageAccepted {
|
||||
lane_id: args.lane_id.into(),
|
||||
nonce,
|
||||
});
|
||||
|
||||
SendMessageArtifacts { nonce, enqueued_messages }
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that the pezpallet is in normal operational mode.
|
||||
fn ensure_normal_operating_mode<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
|
||||
if PalletOperatingMode::<T, I>::get() ==
|
||||
MessagesOperatingMode::Basic(BasicOperatingMode::Normal)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(Error::<T, I>::NotOperatingNormally)
|
||||
}
|
||||
|
||||
/// Creates new inbound lane object, backed by runtime storage. Lane must be active.
|
||||
fn active_inbound_lane<T: Config<I>, I: 'static>(
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, Error<T, I>> {
|
||||
LanesManager::<T, I>::new()
|
||||
.active_inbound_lane(lane_id)
|
||||
.map_err(Error::LanesManager)
|
||||
}
|
||||
|
||||
/// Creates new outbound lane object, backed by runtime storage. Lane must be active.
|
||||
fn active_outbound_lane<T: Config<I>, I: 'static>(
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, Error<T, I>> {
|
||||
LanesManager::<T, I>::new()
|
||||
.active_outbound_lane(lane_id)
|
||||
.map_err(Error::LanesManager)
|
||||
}
|
||||
|
||||
/// Creates new outbound lane object, backed by runtime storage.
|
||||
fn any_state_outbound_lane<T: Config<I>, I: 'static>(
|
||||
lane_id: T::LaneId,
|
||||
) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, Error<T, I>> {
|
||||
LanesManager::<T, I>::new()
|
||||
.any_state_outbound_lane(lane_id)
|
||||
.map_err(Error::LanesManager)
|
||||
}
|
||||
|
||||
/// Verify messages proof and return proved messages with decoded payload.
|
||||
fn verify_and_decode_messages_proof<T: Config<I>, I: 'static>(
|
||||
proof: FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
|
||||
messages_count: u32,
|
||||
) -> Result<
|
||||
ProvedMessages<T::LaneId, DispatchMessage<T::InboundPayload, T::LaneId>>,
|
||||
VerificationError,
|
||||
> {
|
||||
// `receive_messages_proof` weight formula and `MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX`
|
||||
// check guarantees that the `message_count` is sane and Vec<Message> may be allocated.
|
||||
// (tx with too many messages will either be rejected from the pool, or will fail earlier)
|
||||
proofs::verify_messages_proof::<T, I>(proof, messages_count).map(|(lane, lane_data)| {
|
||||
(
|
||||
lane,
|
||||
ProvedLaneMessages {
|
||||
lane_state: lane_data.lane_state,
|
||||
messages: lane_data.messages.into_iter().map(Into::into).collect(),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A module that is responsible for migration of storage.
|
||||
|
||||
use crate::{Config, Pezpallet};
|
||||
use pezframe_support::{
|
||||
traits::{Get, StorageVersion},
|
||||
weights::Weight,
|
||||
};
|
||||
|
||||
/// The in-code storage version.
|
||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
|
||||
/// This module contains data structures that are valid for the initial state of `0`.
|
||||
/// (used with v1 migration).
|
||||
pub mod v0 {
|
||||
use super::Config;
|
||||
use crate::BridgedChainOf;
|
||||
use bp_messages::{MessageNonce, UnrewardedRelayer};
|
||||
use pezbp_runtime::AccountIdOf;
|
||||
use codec::{Decode, Encode};
|
||||
use pezsp_std::collections::vec_deque::VecDeque;
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct StoredInboundLaneData<T: Config<I>, I: 'static>(
|
||||
pub(crate) InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
|
||||
);
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct InboundLaneData<RelayerId> {
|
||||
pub(crate) relayers: VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||
pub(crate) last_confirmed_nonce: MessageNonce,
|
||||
}
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct OutboundLaneData {
|
||||
pub(crate) oldest_unpruned_nonce: MessageNonce,
|
||||
pub(crate) latest_received_nonce: MessageNonce,
|
||||
pub(crate) latest_generated_nonce: MessageNonce,
|
||||
}
|
||||
}
|
||||
|
||||
/// This migration to `1` updates the metadata of `InboundLanes` and `OutboundLanes` to the new
|
||||
/// structures.
|
||||
pub mod v1 {
|
||||
use super::*;
|
||||
use crate::{
|
||||
InboundLaneData, InboundLanes, OutboundLaneData, OutboundLanes, StoredInboundLaneData,
|
||||
};
|
||||
use bp_messages::LaneState;
|
||||
use pezframe_support::traits::UncheckedOnRuntimeUpgrade;
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Migrates the pezpallet storage to v1.
|
||||
pub struct UncheckedMigrationV0ToV1<T, I>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> UncheckedOnRuntimeUpgrade for UncheckedMigrationV0ToV1<T, I> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
|
||||
// `InboundLanes` - add state to the old structs
|
||||
let translate_inbound =
|
||||
|pre: v0::StoredInboundLaneData<T, I>| -> Option<v1::StoredInboundLaneData<T, I>> {
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
Some(v1::StoredInboundLaneData(v1::InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: pre.0.relayers,
|
||||
last_confirmed_nonce: pre.0.last_confirmed_nonce,
|
||||
}))
|
||||
};
|
||||
InboundLanes::<T, I>::translate_values(translate_inbound);
|
||||
|
||||
// `OutboundLanes` - add state to the old structs
|
||||
let translate_outbound = |pre: v0::OutboundLaneData| -> Option<v1::OutboundLaneData> {
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
Some(v1::OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: pre.oldest_unpruned_nonce,
|
||||
latest_received_nonce: pre.latest_received_nonce,
|
||||
latest_generated_nonce: pre.latest_generated_nonce,
|
||||
})
|
||||
};
|
||||
OutboundLanes::<T, I>::translate_values(translate_outbound);
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<pezsp_std::vec::Vec<u8>, pezsp_runtime::DispatchError> {
|
||||
use codec::Encode;
|
||||
|
||||
let number_of_inbound_to_migrate = InboundLanes::<T, I>::iter_keys().count();
|
||||
let number_of_outbound_to_migrate = OutboundLanes::<T, I>::iter_keys().count();
|
||||
Ok((number_of_inbound_to_migrate as u32, number_of_outbound_to_migrate as u32).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: pezsp_std::vec::Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
|
||||
use codec::Decode;
|
||||
const LOG_TARGET: &str = "runtime::bridge-messages-migration";
|
||||
|
||||
let (number_of_inbound_to_migrate, number_of_outbound_to_migrate): (u32, u32) =
|
||||
Decode::decode(&mut &state[..]).unwrap();
|
||||
let number_of_inbound = InboundLanes::<T, I>::iter_keys().count();
|
||||
let number_of_outbound = OutboundLanes::<T, I>::iter_keys().count();
|
||||
|
||||
tracing::info!(target: LOG_TARGET, %number_of_inbound_to_migrate, "post-upgrade expects inbound lanes to have been migrated.");
|
||||
tracing::info!(target: LOG_TARGET, %number_of_outbound_to_migrate, "post-upgrade expects outbound lanes to have been migrated.");
|
||||
|
||||
pezframe_support::ensure!(
|
||||
number_of_inbound_to_migrate as usize == number_of_inbound,
|
||||
"must migrate all `InboundLanes`."
|
||||
);
|
||||
pezframe_support::ensure!(
|
||||
number_of_outbound_to_migrate as usize == number_of_outbound,
|
||||
"must migrate all `OutboundLanes`."
|
||||
);
|
||||
|
||||
tracing::info!(target: LOG_TARGET, "migrated all.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`UncheckedMigrationV0ToV1`] wrapped in a
|
||||
/// [`VersionedMigration`](pezframe_support::migrations::VersionedMigration), ensuring the
|
||||
/// migration is only performed when on-chain version is 0.
|
||||
pub type MigrationToV1<T, I> = pezframe_support::migrations::VersionedMigration<
|
||||
0,
|
||||
1,
|
||||
UncheckedMigrationV0ToV1<T, I>,
|
||||
Pezpallet<T, I>,
|
||||
<T as pezframe_system::Config>::DbWeight,
|
||||
>;
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Everything about outgoing messages sending.
|
||||
|
||||
use crate::{Config, LOG_TARGET};
|
||||
|
||||
use bp_messages::{
|
||||
ChainWithMessages, DeliveredMessages, LaneState, MessageNonce, OutboundLaneData,
|
||||
UnrewardedRelayer,
|
||||
};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_support::{traits::Get, BoundedVec, PalletError};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
use pezsp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive};
|
||||
|
||||
/// Outbound lane storage.
|
||||
pub trait OutboundLaneStorage {
|
||||
/// Stored message payload type.
|
||||
type StoredMessagePayload;
|
||||
/// Lane identifier type.
|
||||
type LaneId: Encode;
|
||||
|
||||
/// Lane id.
|
||||
fn id(&self) -> Self::LaneId;
|
||||
/// Get lane data from the storage.
|
||||
fn data(&self) -> OutboundLaneData;
|
||||
/// Update lane data in the storage.
|
||||
fn set_data(&mut self, data: OutboundLaneData);
|
||||
/// Returns saved outbound message payload.
|
||||
#[cfg(test)]
|
||||
fn message(&self, nonce: &MessageNonce) -> Option<Self::StoredMessagePayload>;
|
||||
/// Save outbound message in the storage.
|
||||
fn save_message(&mut self, nonce: MessageNonce, message_payload: Self::StoredMessagePayload);
|
||||
/// Remove outbound message from the storage.
|
||||
fn remove_message(&mut self, nonce: &MessageNonce);
|
||||
/// Purge lane data from the storage.
|
||||
fn purge(self);
|
||||
}
|
||||
|
||||
/// Limit for the `StoredMessagePayload` vector.
|
||||
pub struct StoredMessagePayloadLimit<T, I>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> Get<u32> for StoredMessagePayloadLimit<T, I> {
|
||||
fn get() -> u32 {
|
||||
T::BridgedChain::maximal_incoming_message_size()
|
||||
}
|
||||
}
|
||||
|
||||
/// Outbound message data wrapper that implements `MaxEncodedLen`.
|
||||
pub type StoredMessagePayload<T, I> = BoundedVec<u8, StoredMessagePayloadLimit<T, I>>;
|
||||
|
||||
/// Result of messages receival confirmation.
|
||||
#[derive(
|
||||
Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
|
||||
)]
|
||||
pub enum ReceptionConfirmationError {
|
||||
/// Bridged chain is trying to confirm more messages than we have generated. May be a result
|
||||
/// of invalid bridged chain storage.
|
||||
FailedToConfirmFutureMessages,
|
||||
/// The unrewarded relayers vec contains an empty entry. May be a result of invalid bridged
|
||||
/// chain storage.
|
||||
EmptyUnrewardedRelayerEntry,
|
||||
/// The unrewarded relayers vec contains non-consecutive entries. May be a result of invalid
|
||||
/// bridged chain storage.
|
||||
NonConsecutiveUnrewardedRelayerEntries,
|
||||
/// The chain has more messages that need to be confirmed than there is in the proof.
|
||||
TryingToConfirmMoreMessagesThanExpected,
|
||||
}
|
||||
|
||||
/// Outbound messages lane.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct OutboundLane<S> {
|
||||
storage: S,
|
||||
}
|
||||
|
||||
impl<S: OutboundLaneStorage> OutboundLane<S> {
|
||||
/// Create new outbound lane backed by given storage.
|
||||
pub fn new(storage: S) -> Self {
|
||||
OutboundLane { storage }
|
||||
}
|
||||
|
||||
/// Get this lane data.
|
||||
pub fn data(&self) -> OutboundLaneData {
|
||||
self.storage.data()
|
||||
}
|
||||
|
||||
/// Get lane state.
|
||||
pub fn state(&self) -> LaneState {
|
||||
self.storage.data().state
|
||||
}
|
||||
|
||||
/// Set lane state.
|
||||
pub fn set_state(&mut self, state: LaneState) {
|
||||
let mut data = self.storage.data();
|
||||
data.state = state;
|
||||
self.storage.set_data(data);
|
||||
}
|
||||
|
||||
/// Return nonces of all currently queued messages.
|
||||
pub fn queued_messages(&self) -> RangeInclusive<MessageNonce> {
|
||||
let data = self.storage.data();
|
||||
data.oldest_unpruned_nonce..=data.latest_generated_nonce
|
||||
}
|
||||
|
||||
/// Send message over lane.
|
||||
///
|
||||
/// Returns new message nonce.
|
||||
pub fn send_message(&mut self, message_payload: S::StoredMessagePayload) -> MessageNonce {
|
||||
let mut data = self.storage.data();
|
||||
let nonce = data.latest_generated_nonce + 1;
|
||||
data.latest_generated_nonce = nonce;
|
||||
|
||||
self.storage.save_message(nonce, message_payload);
|
||||
self.storage.set_data(data);
|
||||
|
||||
nonce
|
||||
}
|
||||
|
||||
/// Confirm messages delivery.
|
||||
pub fn confirm_delivery<RelayerId>(
|
||||
&mut self,
|
||||
max_allowed_messages: MessageNonce,
|
||||
latest_delivered_nonce: MessageNonce,
|
||||
relayers: &VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||
) -> Result<Option<DeliveredMessages>, ReceptionConfirmationError> {
|
||||
let mut data = self.storage.data();
|
||||
let confirmed_messages = DeliveredMessages {
|
||||
begin: data.latest_received_nonce.saturating_add(1),
|
||||
end: latest_delivered_nonce,
|
||||
};
|
||||
if confirmed_messages.total_messages() == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
if confirmed_messages.end > data.latest_generated_nonce {
|
||||
return Err(ReceptionConfirmationError::FailedToConfirmFutureMessages);
|
||||
}
|
||||
if confirmed_messages.total_messages() > max_allowed_messages {
|
||||
// that the relayer has declared correct number of messages that the proof contains (it
|
||||
// is checked outside of the function). But it may happen (but only if this/bridged
|
||||
// chain storage is corrupted, though) that the actual number of confirmed messages if
|
||||
// larger than declared. This would mean that 'reward loop' will take more time than the
|
||||
// weight formula accounts, so we can't allow that.
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
confirmed=%confirmed_messages.total_messages(),
|
||||
max_allowed=%max_allowed_messages,
|
||||
"Messages delivery proof contains too many messages to confirm"
|
||||
);
|
||||
return Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected);
|
||||
}
|
||||
|
||||
ensure_unrewarded_relayers_are_correct(confirmed_messages.end, relayers)?;
|
||||
|
||||
// prune all confirmed messages
|
||||
for nonce in confirmed_messages.begin..=confirmed_messages.end {
|
||||
self.storage.remove_message(&nonce);
|
||||
}
|
||||
|
||||
data.latest_received_nonce = confirmed_messages.end;
|
||||
data.oldest_unpruned_nonce = data.latest_received_nonce.saturating_add(1);
|
||||
self.storage.set_data(data);
|
||||
|
||||
Ok(Some(confirmed_messages))
|
||||
}
|
||||
|
||||
/// Remove message from the storage. Doesn't perform any checks.
|
||||
pub fn remove_oldest_unpruned_message(&mut self) {
|
||||
let mut data = self.storage.data();
|
||||
self.storage.remove_message(&data.oldest_unpruned_nonce);
|
||||
data.oldest_unpruned_nonce += 1;
|
||||
self.storage.set_data(data);
|
||||
}
|
||||
|
||||
/// Purge lane state from the storage.
|
||||
pub fn purge(self) {
|
||||
self.storage.purge()
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies unrewarded relayers vec.
|
||||
///
|
||||
/// Returns `Err(_)` if unrewarded relayers vec contains invalid data, meaning that the bridged
|
||||
/// chain has invalid runtime storage.
|
||||
fn ensure_unrewarded_relayers_are_correct<RelayerId>(
|
||||
latest_received_nonce: MessageNonce,
|
||||
relayers: &VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||
) -> Result<(), ReceptionConfirmationError> {
|
||||
let mut expected_entry_begin = relayers.front().map(|entry| entry.messages.begin);
|
||||
for entry in relayers {
|
||||
// unrewarded relayer entry must have at least 1 unconfirmed message
|
||||
// (guaranteed by the `InboundLane::receive_message()`)
|
||||
if entry.messages.end < entry.messages.begin {
|
||||
return Err(ReceptionConfirmationError::EmptyUnrewardedRelayerEntry);
|
||||
}
|
||||
// every entry must confirm range of messages that follows previous entry range
|
||||
// (guaranteed by the `InboundLane::receive_message()`)
|
||||
if expected_entry_begin != Some(entry.messages.begin) {
|
||||
return Err(ReceptionConfirmationError::NonConsecutiveUnrewardedRelayerEntries);
|
||||
}
|
||||
expected_entry_begin = entry.messages.end.checked_add(1);
|
||||
// entry can't confirm messages larger than `inbound_lane_data.latest_received_nonce()`
|
||||
// (guaranteed by the `InboundLane::receive_message()`)
|
||||
if entry.messages.end > latest_received_nonce {
|
||||
return Err(ReceptionConfirmationError::FailedToConfirmFutureMessages);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
active_outbound_lane,
|
||||
tests::mock::{
|
||||
outbound_message_data, run_test, test_lane_id, unrewarded_relayer, TestRelayer,
|
||||
TestRuntime, REGULAR_PAYLOAD,
|
||||
},
|
||||
};
|
||||
use pezsp_std::ops::RangeInclusive;
|
||||
|
||||
fn unrewarded_relayers(
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> VecDeque<UnrewardedRelayer<TestRelayer>> {
|
||||
vec![unrewarded_relayer(*nonces.start(), *nonces.end(), 0)]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn delivered_messages(nonces: RangeInclusive<MessageNonce>) -> DeliveredMessages {
|
||||
DeliveredMessages { begin: *nonces.start(), end: *nonces.end() }
|
||||
}
|
||||
|
||||
fn assert_3_messages_confirmation_fails(
|
||||
latest_received_nonce: MessageNonce,
|
||||
relayers: &VecDeque<UnrewardedRelayer<TestRelayer>>,
|
||||
) -> Result<Option<DeliveredMessages>, ReceptionConfirmationError> {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
let result = lane.confirm_delivery(3, latest_received_nonce, relayers);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_message_works() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 0);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1);
|
||||
assert!(lane.storage.message(&1).is_some());
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_works() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 2);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 3);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
|
||||
Ok(Some(delivered_messages(1..=3))),
|
||||
);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_partial_delivery_works() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 2);
|
||||
assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 3);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
|
||||
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 2, &unrewarded_relayers(1..=2)),
|
||||
Ok(Some(delivered_messages(1..=2))),
|
||||
);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 2);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3);
|
||||
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 3, &unrewarded_relayers(3..=3)),
|
||||
Ok(Some(delivered_messages(3..=3))),
|
||||
);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_rejects_nonce_lesser_than_latest_received() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 0);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
|
||||
Ok(Some(delivered_messages(1..=3))),
|
||||
);
|
||||
assert_eq!(lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)), Ok(None),);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
|
||||
|
||||
assert_eq!(lane.confirm_delivery(1, 2, &unrewarded_relayers(1..=1)), Ok(None),);
|
||||
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
|
||||
assert_eq!(lane.storage.data().latest_received_nonce, 3);
|
||||
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_rejects_nonce_larger_than_last_generated() {
|
||||
assert_eq!(
|
||||
assert_3_messages_confirmation_fails(10, &unrewarded_relayers(1..=10),),
|
||||
Err(ReceptionConfirmationError::FailedToConfirmFutureMessages),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_fails_if_entry_confirms_future_messages() {
|
||||
assert_eq!(
|
||||
assert_3_messages_confirmation_fails(
|
||||
3,
|
||||
&unrewarded_relayers(1..=1)
|
||||
.into_iter()
|
||||
.chain(unrewarded_relayers(2..=30))
|
||||
.chain(unrewarded_relayers(3..=3))
|
||||
.collect(),
|
||||
),
|
||||
Err(ReceptionConfirmationError::FailedToConfirmFutureMessages),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
fn confirm_delivery_fails_if_entry_is_empty() {
|
||||
assert_eq!(
|
||||
assert_3_messages_confirmation_fails(
|
||||
3,
|
||||
&unrewarded_relayers(1..=1)
|
||||
.into_iter()
|
||||
.chain(unrewarded_relayers(2..=1))
|
||||
.chain(unrewarded_relayers(2..=3))
|
||||
.collect(),
|
||||
),
|
||||
Err(ReceptionConfirmationError::EmptyUnrewardedRelayerEntry),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_fails_if_entries_are_non_consecutive() {
|
||||
assert_eq!(
|
||||
assert_3_messages_confirmation_fails(
|
||||
3,
|
||||
&unrewarded_relayers(1..=1)
|
||||
.into_iter()
|
||||
.chain(unrewarded_relayers(3..=3))
|
||||
.chain(unrewarded_relayers(2..=2))
|
||||
.collect(),
|
||||
),
|
||||
Err(ReceptionConfirmationError::NonConsecutiveUnrewardedRelayerEntries),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_delivery_detects_when_more_than_expected_messages_are_confirmed() {
|
||||
run_test(|| {
|
||||
let mut lane = active_outbound_lane::<TestRuntime, _>(test_lane_id()).unwrap();
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
lane.send_message(outbound_message_data(REGULAR_PAYLOAD));
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(0, 3, &unrewarded_relayers(1..=3)),
|
||||
Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected),
|
||||
);
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(2, 3, &unrewarded_relayers(1..=3)),
|
||||
Err(ReceptionConfirmationError::TryingToConfirmMoreMessagesThanExpected),
|
||||
);
|
||||
assert_eq!(
|
||||
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
|
||||
Ok(Some(delivered_messages(1..=3))),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
// 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/>.
|
||||
|
||||
//! Tools for messages and delivery proof verification.
|
||||
|
||||
use crate::{BridgedChainOf, BridgedHeaderChainOf, Config};
|
||||
|
||||
use bp_header_pez_chain::{HeaderChain, HeaderChainError};
|
||||
use bp_messages::{
|
||||
source_chain::FromBridgedChainMessagesDeliveryProof,
|
||||
target_chain::{FromBridgedChainMessagesProof, ProvedLaneMessages, ProvedMessages},
|
||||
ChainWithMessages, InboundLaneData, Message, MessageKey, MessageNonce, MessagePayload,
|
||||
OutboundLaneData, VerificationError,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
HashOf, HasherOf, RangeInclusiveExt, RawStorageProof, StorageProofChecker, StorageProofError,
|
||||
};
|
||||
use codec::Decode;
|
||||
use pezsp_std::vec::Vec;
|
||||
|
||||
/// 'Parsed' message delivery proof - inbound lane id and its state.
|
||||
pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain<T, I> =
|
||||
(<T as Config<I>>::LaneId, InboundLaneData<<T as pezframe_system::Config>::AccountId>);
|
||||
|
||||
/// Verify proof of Bridged -> This chain messages.
|
||||
///
|
||||
/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
|
||||
/// teyrchains, please use the `verify_messages_proof_from_teyrchain`.
|
||||
///
|
||||
/// 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<T: Config<I>, I: 'static>(
|
||||
proof: FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
|
||||
messages_count: u32,
|
||||
) -> Result<ProvedMessages<T::LaneId, Message<T::LaneId>>, VerificationError> {
|
||||
let FromBridgedChainMessagesProof {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane,
|
||||
nonces_start,
|
||||
nonces_end,
|
||||
} = proof;
|
||||
let mut parser: MessagesStorageProofAdapter<T, I> =
|
||||
MessagesStorageProofAdapter::try_new_with_verified_storage_proof(
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
)
|
||||
.map_err(VerificationError::HeaderChain)?;
|
||||
let nonces_range = nonces_start..=nonces_end;
|
||||
|
||||
// receiving proofs where end < begin is ok (if proof includes outbound lane state)
|
||||
let messages_in_the_proof = nonces_range.saturating_len();
|
||||
if messages_in_the_proof != MessageNonce::from(messages_count) {
|
||||
return Err(VerificationError::MessagesCountMismatch);
|
||||
}
|
||||
|
||||
// Read messages first. All messages that are claimed to be in the proof must
|
||||
// be in the proof. So any error in `read_value`, or even missing value is fatal.
|
||||
//
|
||||
// Mind that we allow proofs with no messages if outbound lane state is proved.
|
||||
let mut messages = Vec::with_capacity(messages_in_the_proof as _);
|
||||
for nonce in nonces_range {
|
||||
let message_key = MessageKey { lane_id: lane, nonce };
|
||||
let message_payload = parser
|
||||
.read_and_decode_message_payload(&message_key)
|
||||
.map_err(VerificationError::MessageStorage)?;
|
||||
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)
|
||||
.map_err(VerificationError::OutboundLaneStorage)?,
|
||||
messages,
|
||||
};
|
||||
|
||||
// Now we may actually check if the proof is empty or not.
|
||||
if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() {
|
||||
return Err(VerificationError::EmptyMessageProof);
|
||||
}
|
||||
|
||||
// Check that the storage proof doesn't have any untouched keys.
|
||||
parser.ensure_no_unused_keys().map_err(VerificationError::StorageProof)?;
|
||||
|
||||
Ok((lane, proved_lane_messages))
|
||||
}
|
||||
|
||||
/// Verify proof of This -> Bridged chain messages delivery.
|
||||
pub fn verify_messages_delivery_proof<T: Config<I>, I: 'static>(
|
||||
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
|
||||
) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<T, I>, VerificationError> {
|
||||
let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = proof;
|
||||
let mut parser: MessagesStorageProofAdapter<T, I> =
|
||||
MessagesStorageProofAdapter::try_new_with_verified_storage_proof(
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
)
|
||||
.map_err(VerificationError::HeaderChain)?;
|
||||
// Messages delivery proof is just proof of single storage key read => any error
|
||||
// is fatal.
|
||||
let storage_inbound_lane_data_key = bp_messages::storage_keys::inbound_lane_data_key(
|
||||
T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&lane,
|
||||
);
|
||||
let inbound_lane_data = parser
|
||||
.read_and_decode_mandatory_value(&storage_inbound_lane_data_key)
|
||||
.map_err(VerificationError::InboundLaneStorage)?;
|
||||
|
||||
// check that the storage proof doesn't have any untouched trie nodes
|
||||
parser.ensure_no_unused_keys().map_err(VerificationError::StorageProof)?;
|
||||
|
||||
Ok((lane, inbound_lane_data))
|
||||
}
|
||||
|
||||
/// Abstraction over storage proof manipulation, hiding implementation details of actual storage
|
||||
/// proofs.
|
||||
trait StorageProofAdapter<T: Config<I>, I: 'static> {
|
||||
fn read_and_decode_mandatory_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<D, StorageProofError>;
|
||||
fn read_and_decode_optional_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<Option<D>, StorageProofError>;
|
||||
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError>;
|
||||
|
||||
fn read_and_decode_outbound_lane_data(
|
||||
&mut self,
|
||||
lane_id: &T::LaneId,
|
||||
) -> Result<Option<OutboundLaneData>, StorageProofError> {
|
||||
let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key(
|
||||
T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
lane_id,
|
||||
);
|
||||
self.read_and_decode_optional_value(&storage_outbound_lane_data_key)
|
||||
}
|
||||
|
||||
fn read_and_decode_message_payload(
|
||||
&mut self,
|
||||
message_key: &MessageKey<T::LaneId>,
|
||||
) -> Result<MessagePayload, StorageProofError> {
|
||||
let storage_message_key = bp_messages::storage_keys::message_key(
|
||||
T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&message_key.lane_id,
|
||||
message_key.nonce,
|
||||
);
|
||||
self.read_and_decode_mandatory_value(&storage_message_key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Actual storage proof adapter for messages proofs.
|
||||
type MessagesStorageProofAdapter<T, I> = StorageProofCheckerAdapter<T, I>;
|
||||
|
||||
/// A `StorageProofAdapter` implementation for raw storage proofs.
|
||||
struct StorageProofCheckerAdapter<T: Config<I>, I: 'static> {
|
||||
storage: StorageProofChecker<HasherOf<BridgedChainOf<T, I>>>,
|
||||
_dummy: pezsp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> StorageProofCheckerAdapter<T, I> {
|
||||
fn try_new_with_verified_storage_proof(
|
||||
bridged_header_hash: HashOf<BridgedChainOf<T, I>>,
|
||||
storage_proof: RawStorageProof,
|
||||
) -> Result<Self, HeaderChainError> {
|
||||
BridgedHeaderChainOf::<T, I>::verify_storage_proof(bridged_header_hash, storage_proof).map(
|
||||
|storage| StorageProofCheckerAdapter::<T, I> { storage, _dummy: Default::default() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> StorageProofAdapter<T, I> for StorageProofCheckerAdapter<T, I> {
|
||||
fn read_and_decode_optional_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<Option<D>, StorageProofError> {
|
||||
self.storage.read_and_decode_opt_value(key.as_ref())
|
||||
}
|
||||
|
||||
fn read_and_decode_mandatory_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<D, StorageProofError> {
|
||||
self.storage.read_and_decode_mandatory_value(key.as_ref())
|
||||
}
|
||||
|
||||
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError> {
|
||||
self.storage.ensure_no_unused_nodes()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{
|
||||
messages_generation::{
|
||||
encode_all_messages, encode_lane_data, generate_dummy_message,
|
||||
prepare_messages_storage_proof,
|
||||
},
|
||||
mock::*,
|
||||
};
|
||||
|
||||
use bp_header_pez_chain::{HeaderChainError, StoredHeaderDataBuilder};
|
||||
use bp_messages::LaneState;
|
||||
use pezbp_runtime::{HeaderId, StorageProofError};
|
||||
use codec::Encode;
|
||||
use pezsp_runtime::traits::Header;
|
||||
|
||||
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>,
|
||||
add_duplicate_key: bool,
|
||||
add_unused_key: bool,
|
||||
test: impl Fn(FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>) -> R,
|
||||
) -> R {
|
||||
let (state_root, storage_proof) =
|
||||
prepare_messages_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
|
||||
test_lane_id(),
|
||||
1..=nonces_end,
|
||||
outbound_lane_data,
|
||||
pezbp_runtime::UnverifiedStorageProofParams::default(),
|
||||
generate_dummy_message,
|
||||
encode_message,
|
||||
encode_outbound_lane_data,
|
||||
add_duplicate_key,
|
||||
add_unused_key,
|
||||
);
|
||||
|
||||
pezsp_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();
|
||||
|
||||
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(HeaderId(
|
||||
0,
|
||||
bridged_header_hash,
|
||||
));
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
bridged_header.build(),
|
||||
);
|
||||
test(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,
|
||||
false,
|
||||
false,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 5) }
|
||||
),
|
||||
Err(VerificationError::MessagesCountMismatch),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 15) }
|
||||
),
|
||||
Err(VerificationError::MessagesCountMismatch),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_header_is_missing_from_the_chain() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| {
|
||||
let bridged_header_hash =
|
||||
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::remove(
|
||||
bridged_header_hash,
|
||||
);
|
||||
verify_messages_proof::<TestRuntime, ()>(proof, 10)
|
||||
}
|
||||
),
|
||||
Err(VerificationError::HeaderChain(HeaderChainError::UnknownHeader)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_header_state_root_mismatches() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| {
|
||||
let bridged_header_hash =
|
||||
pezpallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
BridgedChainHeader::new(
|
||||
0,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
.build(),
|
||||
);
|
||||
verify_messages_proof::<TestRuntime, ()>(proof, 10)
|
||||
}
|
||||
),
|
||||
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
|
||||
StorageProofError::StorageRootMismatch
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_it_has_duplicate_trie_nodes() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
true,
|
||||
false,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 10) },
|
||||
),
|
||||
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
|
||||
StorageProofError::DuplicateNodes
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
#[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,
|
||||
false,
|
||||
true,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 10) },
|
||||
),
|
||||
Err(VerificationError::StorageProof(StorageProofError::UnusedKey)),
|
||||
);
|
||||
}
|
||||
|
||||
#[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,
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 10)
|
||||
),
|
||||
Err(VerificationError::MessageStorage(StorageProofError::EmptyVal)),
|
||||
);
|
||||
}
|
||||
|
||||
#[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,
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 10),
|
||||
),
|
||||
Err(VerificationError::MessageStorage(StorageProofError::DecodeError)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() {
|
||||
matches!(
|
||||
using_messages_proof(
|
||||
10,
|
||||
Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
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
|
||||
},
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 10),
|
||||
),
|
||||
Err(VerificationError::OutboundLaneStorage(StorageProofError::DecodeError)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_proof_is_rejected_if_it_is_empty() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
0,
|
||||
None,
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| { verify_messages_proof::<TestRuntime, ()>(proof, 0) },
|
||||
),
|
||||
Err(VerificationError::EmptyMessageProof),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_empty_message_proof_without_messages_is_accepted() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
0,
|
||||
Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 0),
|
||||
),
|
||||
Ok((
|
||||
test_lane_id(),
|
||||
ProvedLaneMessages {
|
||||
lane_state: Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
messages: Vec::new(),
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_empty_message_proof_is_accepted() {
|
||||
assert_eq!(
|
||||
using_messages_proof(
|
||||
1,
|
||||
Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 1,
|
||||
latest_generated_nonce: 1,
|
||||
}),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
|proof| verify_messages_proof::<TestRuntime, ()>(proof, 1),
|
||||
),
|
||||
Ok((
|
||||
test_lane_id(),
|
||||
ProvedLaneMessages {
|
||||
lane_state: Some(OutboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
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],
|
||||
}],
|
||||
},
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[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,
|
||||
false,
|
||||
false,
|
||||
|mut proof| {
|
||||
proof.nonces_end = u64::MAX;
|
||||
verify_messages_proof::<TestRuntime, ()>(proof, u32::MAX)
|
||||
},
|
||||
),
|
||||
Err(VerificationError::MessagesCountMismatch),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helpers for generating message storage proofs, that are used by tests and by benchmarks.
|
||||
|
||||
use bp_messages::{
|
||||
storage_keys, ChainWithMessages, InboundLaneData, MessageKey, MessageNonce, MessagePayload,
|
||||
OutboundLaneData,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
grow_storage_value, record_all_trie_keys, AccountIdOf, Chain, HashOf, HasherOf,
|
||||
RawStorageProof, UnverifiedStorageProofParams,
|
||||
};
|
||||
use codec::Encode;
|
||||
use pezsp_std::{ops::RangeInclusive, prelude::*};
|
||||
use pezsp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
|
||||
|
||||
/// Dummy message generation function.
|
||||
pub fn generate_dummy_message(_: MessageNonce) -> MessagePayload {
|
||||
vec![42]
|
||||
}
|
||||
|
||||
/// Simple and correct message data encode function.
|
||||
pub fn encode_all_messages(_: MessageNonce, m: &MessagePayload) -> Option<Vec<u8>> {
|
||||
Some(m.encode())
|
||||
}
|
||||
|
||||
/// Simple and correct outbound lane data encode function.
|
||||
pub fn encode_lane_data(d: &OutboundLaneData) -> Vec<u8> {
|
||||
d.encode()
|
||||
}
|
||||
|
||||
/// Prepare storage proof of given messages.
|
||||
///
|
||||
/// Returns state trie root and nodes with prepared messages.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn prepare_messages_storage_proof<
|
||||
BridgedChain: Chain,
|
||||
ThisChain: ChainWithMessages,
|
||||
LaneId: Encode + Copy,
|
||||
>(
|
||||
lane: LaneId,
|
||||
message_nonces: RangeInclusive<MessageNonce>,
|
||||
outbound_lane_data: Option<OutboundLaneData>,
|
||||
proof_params: UnverifiedStorageProofParams,
|
||||
generate_message: impl Fn(MessageNonce) -> MessagePayload,
|
||||
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
|
||||
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
|
||||
add_duplicate_key: bool,
|
||||
add_unused_key: bool,
|
||||
) -> (HashOf<BridgedChain>, RawStorageProof)
|
||||
where
|
||||
HashOf<BridgedChain>: 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>>::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, &generate_message(nonce)) {
|
||||
Some(message_payload) =>
|
||||
if i == 0 {
|
||||
grow_storage_value(message_payload, &proof_params)
|
||||
} else {
|
||||
message_payload
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
let storage_key = storage_keys::message_key(
|
||||
ThisChain::WITH_CHAIN_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(
|
||||
ThisChain::WITH_CHAIN_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 mut storage_proof =
|
||||
record_all_trie_keys::<LayoutV1<HasherOf<BridgedChain>>, _>(&mdb, &root)
|
||||
.map_err(|_| "record_all_trie_keys has failed")
|
||||
.expect("record_all_trie_keys should not fail in benchmarks");
|
||||
|
||||
if add_duplicate_key {
|
||||
assert!(!storage_proof.is_empty());
|
||||
let node = storage_proof.pop().unwrap();
|
||||
storage_proof.push(node.clone());
|
||||
storage_proof.push(node);
|
||||
}
|
||||
|
||||
if add_unused_key {
|
||||
storage_proof.push(b"unused_value".to_vec());
|
||||
}
|
||||
|
||||
(root, storage_proof)
|
||||
}
|
||||
|
||||
/// Prepare storage proof of given messages delivery.
|
||||
///
|
||||
/// Returns state trie root and nodes with prepared messages.
|
||||
pub fn prepare_message_delivery_storage_proof<
|
||||
BridgedChain: Chain,
|
||||
ThisChain: ChainWithMessages,
|
||||
LaneId: Encode,
|
||||
>(
|
||||
lane: LaneId,
|
||||
inbound_lane_data: InboundLaneData<AccountIdOf<ThisChain>>,
|
||||
proof_params: UnverifiedStorageProofParams,
|
||||
) -> (HashOf<BridgedChain>, RawStorageProof)
|
||||
where
|
||||
HashOf<BridgedChain>: Copy + Default,
|
||||
{
|
||||
// prepare Bridged chain storage with inbound lane state
|
||||
let storage_key =
|
||||
storage_keys::inbound_lane_data_key(ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, &lane).0;
|
||||
let mut root = Default::default();
|
||||
let mut mdb = MemoryDB::default();
|
||||
{
|
||||
let mut trie =
|
||||
TrieDBMutBuilderV1::<HasherOf<BridgedChain>>::new(&mut mdb, &mut root).build();
|
||||
let inbound_lane_data = grow_storage_value(inbound_lane_data.encode(), &proof_params);
|
||||
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>>, _>(&mdb, &root)
|
||||
.map_err(|_| "record_all_trie_keys has failed")
|
||||
.expect("record_all_trie_keys should not fail in benchmarks");
|
||||
|
||||
(root, storage_proof)
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// From construct_runtime macro
|
||||
#![allow(clippy::from_over_into)]
|
||||
|
||||
use crate::{
|
||||
tests::messages_generation::{
|
||||
encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof,
|
||||
prepare_messages_storage_proof,
|
||||
},
|
||||
Config, StoredMessagePayload,
|
||||
};
|
||||
|
||||
use bp_header_pez_chain::{ChainWithGrandpa, StoredHeaderData};
|
||||
use bp_messages::{
|
||||
calc_relayers_rewards,
|
||||
source_chain::{
|
||||
DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered,
|
||||
},
|
||||
target_chain::{
|
||||
DeliveryPayments, DispatchMessage, DispatchMessageData, FromBridgedChainMessagesProof,
|
||||
MessageDispatch,
|
||||
},
|
||||
ChainWithMessages, DeliveredMessages, HashedLaneId, InboundLaneData, LaneIdType, LaneState,
|
||||
Message, MessageKey, MessageNonce, OutboundLaneData, UnrewardedRelayer,
|
||||
UnrewardedRelayersState,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
messages::MessageDispatchResult, Chain, ChainId, Size, UnverifiedStorageProofParams,
|
||||
};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_support::{
|
||||
derive_impl,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{
|
||||
testing::Header as BizinikiwiHeader,
|
||||
traits::{BlakeTwo256, ConstU32},
|
||||
BuildStorage, StateVersion,
|
||||
};
|
||||
use std::{collections::VecDeque, ops::RangeInclusive};
|
||||
|
||||
pub type AccountId = u64;
|
||||
pub type Balance = u64;
|
||||
#[derive(Decode, DecodeWithMemTracking, Encode, Clone, Debug, PartialEq, Eq, TypeInfo)]
|
||||
pub struct TestPayload {
|
||||
/// Field that may be used to identify messages.
|
||||
pub id: u64,
|
||||
/// Dispatch weight that is declared by the message sender.
|
||||
pub declared_weight: Weight,
|
||||
/// Message dispatch result.
|
||||
///
|
||||
/// Note: in correct code `dispatch_result.unspent_weight` will always be <= `declared_weight`,
|
||||
/// but for test purposes we'll be making it larger than `declared_weight` sometimes.
|
||||
pub dispatch_result: MessageDispatchResult<TestDispatchLevelResult>,
|
||||
/// Extra bytes that affect payload size.
|
||||
pub extra: Vec<u8>,
|
||||
}
|
||||
pub type TestMessageFee = u64;
|
||||
pub type TestRelayer = u64;
|
||||
pub type TestDispatchLevelResult = ();
|
||||
|
||||
pub struct ThisChain;
|
||||
|
||||
impl Chain for ThisChain {
|
||||
const ID: ChainId = *b"ttch";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BizinikiwiHeader;
|
||||
type AccountId = AccountId;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
u32::MAX
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for ThisChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithThisChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
pub struct BridgedChain;
|
||||
|
||||
pub type BridgedHeaderHash = H256;
|
||||
pub type BridgedChainHeader = BizinikiwiHeader;
|
||||
|
||||
impl Chain for BridgedChain {
|
||||
const ID: ChainId = *b"tbch";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = BridgedHeaderHash;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = TestRelayer;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
4096
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for BridgedChain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "WithBridgedChainBridgeGrandpa";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = 16;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 4;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 4096;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 4096;
|
||||
}
|
||||
|
||||
impl ChainWithMessages for BridgedChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithBridgedChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
use crate as pezpallet_bridge_messages;
|
||||
|
||||
pezframe_support::construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Balances: pezpallet_balances::{Pezpallet, Call, Event<T>},
|
||||
BridgedChainGrandpa: pezpallet_bridge_grandpa::{Pezpallet, Call, Event<T>},
|
||||
Messages: pezpallet_bridge_messages::{Pezpallet, Call, Event<T>},
|
||||
}
|
||||
}
|
||||
|
||||
pub type DbWeight = RocksDbWeight;
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
type DbWeight = DbWeight;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for TestRuntime {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_grandpa::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = BridgedChain;
|
||||
type MaxFreeHeadersPerBlock = ConstU32<4>;
|
||||
type FreeHeadersInterval = ConstU32<1_024>;
|
||||
type HeadersToKeep = ConstU32<8>;
|
||||
type WeightInfo = pezpallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
|
||||
}
|
||||
|
||||
/// weights of messages pezpallet calls we use in tests.
|
||||
pub type TestWeightInfo = ();
|
||||
|
||||
impl Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = TestWeightInfo;
|
||||
|
||||
type ThisChain = ThisChain;
|
||||
type BridgedChain = BridgedChain;
|
||||
type BridgedHeaderChain = BridgedChainGrandpa;
|
||||
|
||||
type OutboundPayload = TestPayload;
|
||||
type InboundPayload = TestPayload;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
type DeliveryPayments = TestDeliveryPayments;
|
||||
type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments;
|
||||
type OnMessagesDelivered = TestOnMessagesDelivered;
|
||||
|
||||
type MessageDispatch = TestMessageDispatch;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl crate::benchmarking::Config<()> for TestRuntime {
|
||||
fn bench_lane_id() -> Self::LaneId {
|
||||
test_lane_id()
|
||||
}
|
||||
|
||||
fn prepare_message_proof(
|
||||
params: crate::benchmarking::MessageProofParams<Self::LaneId>,
|
||||
) -> (FromBridgedChainMessagesProof<BridgedHeaderHash, Self::LaneId>, Weight) {
|
||||
use pezbp_runtime::RangeInclusiveExt;
|
||||
|
||||
let dispatch_weight =
|
||||
REGULAR_PAYLOAD.declared_weight * params.message_nonces.saturating_len();
|
||||
(
|
||||
*prepare_messages_proof(
|
||||
params.message_nonces.into_iter().map(|n| message(n, REGULAR_PAYLOAD)).collect(),
|
||||
params.outbound_lane_data,
|
||||
),
|
||||
dispatch_weight,
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare_message_delivery_proof(
|
||||
params: crate::benchmarking::MessageDeliveryProofParams<AccountId, Self::LaneId>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, Self::LaneId> {
|
||||
// in mock run we only care about benchmarks correctness, not the benchmark results
|
||||
// => ignore size related arguments
|
||||
prepare_messages_delivery_proof(params.lane, params.inbound_lane_data)
|
||||
}
|
||||
|
||||
fn is_relayer_rewarded(_relayer: &AccountId) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Size for TestPayload {
|
||||
fn size(&self) -> u32 {
|
||||
16 + self.extra.len() as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// Account that has balance to use in tests.
|
||||
pub const ENDOWED_ACCOUNT: AccountId = 0xDEAD;
|
||||
|
||||
/// Account id of test relayer.
|
||||
pub const TEST_RELAYER_A: AccountId = 100;
|
||||
|
||||
/// Account id of additional test relayer - B.
|
||||
pub const TEST_RELAYER_B: AccountId = 101;
|
||||
|
||||
/// Account id of additional test relayer - C.
|
||||
pub const TEST_RELAYER_C: AccountId = 102;
|
||||
|
||||
/// Lane identifier type used for tests.
|
||||
pub type TestLaneIdType = HashedLaneId;
|
||||
/// Lane that we're using in tests.
|
||||
pub fn test_lane_id() -> TestLaneIdType {
|
||||
TestLaneIdType::try_new(1, 2).unwrap()
|
||||
}
|
||||
|
||||
/// Lane that is completely unknown to our runtime.
|
||||
pub fn unknown_lane_id() -> TestLaneIdType {
|
||||
TestLaneIdType::try_new(1, 3).unwrap()
|
||||
}
|
||||
|
||||
/// Lane that is registered, but it is closed.
|
||||
pub fn closed_lane_id() -> TestLaneIdType {
|
||||
TestLaneIdType::try_new(1, 4).unwrap()
|
||||
}
|
||||
|
||||
/// Regular message payload.
|
||||
pub const REGULAR_PAYLOAD: TestPayload = message_payload(0, 50);
|
||||
|
||||
/// Reward payments at the target chain during delivery transaction.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TestDeliveryPayments;
|
||||
|
||||
impl TestDeliveryPayments {
|
||||
/// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is
|
||||
/// cleared after the call.
|
||||
pub fn is_reward_paid(relayer: AccountId) -> bool {
|
||||
let key = (b":delivery-relayer-reward:", relayer).encode();
|
||||
pezframe_support::storage::unhashed::take::<bool>(&key).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeliveryPayments<AccountId> for TestDeliveryPayments {
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_reward(
|
||||
relayer: AccountId,
|
||||
_total_messages: MessageNonce,
|
||||
_valid_messages: MessageNonce,
|
||||
_actual_weight: Weight,
|
||||
) {
|
||||
let key = (b":delivery-relayer-reward:", relayer).encode();
|
||||
pezframe_support::storage::unhashed::put(&key, &true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reward payments at the source chain during delivery confirmation transaction.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TestDeliveryConfirmationPayments;
|
||||
|
||||
impl TestDeliveryConfirmationPayments {
|
||||
/// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is
|
||||
/// cleared after the call.
|
||||
pub fn is_reward_paid(relayer: AccountId, fee: TestMessageFee) -> bool {
|
||||
let key = (b":relayer-reward:", relayer, fee).encode();
|
||||
pezframe_support::storage::unhashed::take::<bool>(&key).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeliveryConfirmationPayments<AccountId, TestLaneIdType> for TestDeliveryConfirmationPayments {
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_reward(
|
||||
_lane_id: TestLaneIdType,
|
||||
pez_messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
|
||||
_confirmation_relayer: &AccountId,
|
||||
received_range: &RangeInclusive<MessageNonce>,
|
||||
) -> MessageNonce {
|
||||
let relayers_rewards = calc_relayers_rewards(pez_messages_relayers, received_range);
|
||||
let rewarded_relayers = relayers_rewards.len();
|
||||
for (relayer, reward) in &relayers_rewards {
|
||||
let key = (b":relayer-reward:", relayer, reward).encode();
|
||||
pezframe_support::storage::unhashed::put(&key, &true);
|
||||
}
|
||||
|
||||
rewarded_relayers as _
|
||||
}
|
||||
}
|
||||
|
||||
/// Test message dispatcher.
|
||||
#[derive(Debug)]
|
||||
pub struct TestMessageDispatch;
|
||||
|
||||
impl TestMessageDispatch {
|
||||
pub fn deactivate(lane: TestLaneIdType) {
|
||||
// "enqueue" enough (to deactivate dispatcher) messages at dispatcher
|
||||
let latest_received_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1;
|
||||
for _ in 1..=latest_received_nonce {
|
||||
Self::emulate_enqueued_message(lane);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emulate_enqueued_message(lane: TestLaneIdType) {
|
||||
let key = (b"dispatched", lane).encode();
|
||||
let dispatched = pezframe_support::storage::unhashed::get_or_default::<MessageNonce>(&key[..]);
|
||||
pezframe_support::storage::unhashed::put(&key[..], &(dispatched + 1));
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDispatch for TestMessageDispatch {
|
||||
type DispatchPayload = TestPayload;
|
||||
type DispatchLevelResult = TestDispatchLevelResult;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default::<MessageNonce>(
|
||||
&(b"dispatched", lane).encode()[..],
|
||||
) <= BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
|
||||
}
|
||||
|
||||
fn dispatch_weight(message: &mut DispatchMessage<TestPayload, Self::LaneId>) -> Weight {
|
||||
match message.data.payload.as_ref() {
|
||||
Ok(payload) => payload.declared_weight,
|
||||
Err(_) => Weight::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
message: DispatchMessage<TestPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<TestDispatchLevelResult> {
|
||||
match message.data.payload.as_ref() {
|
||||
Ok(payload) => {
|
||||
Self::emulate_enqueued_message(message.key.lane_id);
|
||||
payload.dispatch_result.clone()
|
||||
},
|
||||
Err(_) => dispatch_result(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test callback, called during message delivery confirmation transaction.
|
||||
pub struct TestOnMessagesDelivered;
|
||||
|
||||
impl TestOnMessagesDelivered {
|
||||
pub fn call_arguments() -> Option<(TestLaneIdType, MessageNonce)> {
|
||||
pezframe_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered")
|
||||
}
|
||||
}
|
||||
|
||||
impl OnMessagesDelivered<TestLaneIdType> for TestOnMessagesDelivered {
|
||||
fn on_messages_delivered(lane: TestLaneIdType, enqueued_messages: MessageNonce) {
|
||||
pezframe_support::storage::unhashed::put(
|
||||
b"TestOnMessagesDelivered.OnMessagesDelivered",
|
||||
&(lane, enqueued_messages),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return test lane message with given nonce and payload.
|
||||
pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message<TestLaneIdType> {
|
||||
Message { key: MessageKey { lane_id: test_lane_id(), nonce }, payload: payload.encode() }
|
||||
}
|
||||
|
||||
/// Return valid outbound message data, constructed from given payload.
|
||||
pub fn outbound_message_data(payload: TestPayload) -> StoredMessagePayload<TestRuntime, ()> {
|
||||
StoredMessagePayload::<TestRuntime, ()>::try_from(payload.encode()).expect("payload too large")
|
||||
}
|
||||
|
||||
/// Return valid inbound (dispatch) message data, constructed from given payload.
|
||||
pub fn inbound_message_data(payload: TestPayload) -> DispatchMessageData<TestPayload> {
|
||||
DispatchMessageData { payload: Ok(payload) }
|
||||
}
|
||||
|
||||
/// Constructs message payload using given arguments and zero unspent weight.
|
||||
pub const fn message_payload(id: u64, declared_weight: u64) -> TestPayload {
|
||||
TestPayload {
|
||||
id,
|
||||
declared_weight: Weight::from_parts(declared_weight, 0),
|
||||
dispatch_result: dispatch_result(0),
|
||||
extra: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns message dispatch result with given unspent weight.
|
||||
pub const fn dispatch_result(
|
||||
unspent_weight: u64,
|
||||
) -> MessageDispatchResult<TestDispatchLevelResult> {
|
||||
MessageDispatchResult {
|
||||
unspent_weight: Weight::from_parts(unspent_weight, 0),
|
||||
dispatch_level_result: (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs unrewarded relayer entry from nonces range and relayer id.
|
||||
pub fn unrewarded_relayer(
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
relayer: TestRelayer,
|
||||
) -> UnrewardedRelayer<TestRelayer> {
|
||||
UnrewardedRelayer { relayer, messages: DeliveredMessages { begin, end } }
|
||||
}
|
||||
|
||||
/// Returns unrewarded relayers state at given lane.
|
||||
pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRelayersState {
|
||||
let inbound_lane_data = crate::InboundLanes::<TestRuntime, ()>::get(lane).unwrap().0;
|
||||
UnrewardedRelayersState::from(&inbound_lane_data)
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
|
||||
pezpallet_balances::GenesisConfig::<TestRuntime> {
|
||||
balances: vec![(ENDOWED_ACCOUNT, 1_000_000)],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
pezsp_io::TestExternalities::new(t)
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
crate::InboundLanes::<TestRuntime, ()>::insert(test_lane_id(), InboundLaneData::opened());
|
||||
crate::OutboundLanes::<TestRuntime, ()>::insert(test_lane_id(), OutboundLaneData::opened());
|
||||
crate::InboundLanes::<TestRuntime, ()>::insert(
|
||||
closed_lane_id(),
|
||||
InboundLaneData { state: LaneState::Closed, ..Default::default() },
|
||||
);
|
||||
crate::OutboundLanes::<TestRuntime, ()>::insert(
|
||||
closed_lane_id(),
|
||||
OutboundLaneData { state: LaneState::Closed, ..Default::default() },
|
||||
);
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepare valid storage proof for given messages and insert appropriate header to the
|
||||
/// bridged header chain.
|
||||
///
|
||||
/// Since this function changes the runtime storage, you can't "inline" it in the
|
||||
/// `asset_noop` macro calls.
|
||||
pub fn prepare_messages_proof(
|
||||
messages: Vec<Message<TestLaneIdType>>,
|
||||
outbound_lane_data: Option<OutboundLaneData>,
|
||||
) -> Box<FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>> {
|
||||
// first - let's generate storage proof
|
||||
let lane = messages.first().unwrap().key.lane_id;
|
||||
let nonces_start = messages.first().unwrap().key.nonce;
|
||||
let nonces_end = messages.last().unwrap().key.nonce;
|
||||
let (storage_root, storage_proof) =
|
||||
prepare_messages_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
|
||||
lane,
|
||||
nonces_start..=nonces_end,
|
||||
outbound_lane_data,
|
||||
UnverifiedStorageProofParams::default(),
|
||||
|nonce| messages[(nonce - nonces_start) as usize].payload.clone(),
|
||||
encode_all_messages,
|
||||
encode_lane_data,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
// let's now insert bridged chain header into the storage
|
||||
let bridged_header_hash = Default::default();
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
StoredHeaderData { number: 0, state_root: storage_root },
|
||||
);
|
||||
|
||||
Box::new(FromBridgedChainMessagesProof::<BridgedHeaderHash, TestLaneIdType> {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane,
|
||||
nonces_start,
|
||||
nonces_end,
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepare valid storage proof for given messages and insert appropriate header to the
|
||||
/// bridged header chain.
|
||||
///
|
||||
/// Since this function changes the runtime storage, you can't "inline" it in the
|
||||
/// `asset_noop` macro calls.
|
||||
pub fn prepare_messages_delivery_proof(
|
||||
lane: TestLaneIdType,
|
||||
inbound_lane_data: InboundLaneData<AccountId>,
|
||||
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, TestLaneIdType> {
|
||||
// first - let's generate storage proof
|
||||
let (storage_root, storage_proof) =
|
||||
prepare_message_delivery_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
|
||||
lane,
|
||||
inbound_lane_data,
|
||||
UnverifiedStorageProofParams::default(),
|
||||
);
|
||||
|
||||
// let's now insert bridged chain header into the storage
|
||||
let bridged_header_hash = Default::default();
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
|
||||
bridged_header_hash,
|
||||
StoredHeaderData { number: 0, state_root: storage_root },
|
||||
);
|
||||
|
||||
FromBridgedChainMessagesDeliveryProof::<BridgedHeaderHash, TestLaneIdType> {
|
||||
bridged_header_hash,
|
||||
storage_proof,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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/>.
|
||||
|
||||
//! Tests and test helpers for messages pezpallet.
|
||||
|
||||
#![cfg(any(feature = "test-helpers", test))]
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod mock;
|
||||
#[cfg(test)]
|
||||
mod pezpallet_tests;
|
||||
|
||||
pub mod messages_generation;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,530 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_bridge_messages
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-06-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `serban-ROG-Zephyrus`, CPU: `12th Gen Intel(R) Core(TM) i7-12700H`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/unknown-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_bridge_messages
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/messages/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pezpallet_bridge_messages.
|
||||
pub trait WeightInfo {
|
||||
fn receive_single_message_proof() -> Weight;
|
||||
fn receive_n_messages_proof(n: u32) -> Weight;
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight;
|
||||
fn receive_single_n_bytes_message_proof(n: u32) -> Weight;
|
||||
fn receive_delivery_proof_for_single_message() -> Weight;
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight;
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight;
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(n: u32) -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_bridge_messages` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
fn receive_single_message_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_724 nanoseconds.
|
||||
Weight::from_parts(40_650_000, 52673)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49208), added:
|
||||
/// 51683, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 1004]`.
|
||||
///
|
||||
/// The range of component `n` is `[1, 1004]`.
|
||||
fn receive_n_messages_proof(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 39_354 nanoseconds.
|
||||
Weight::from_parts(29_708_543, 52673)
|
||||
// Standard Error: 1_185
|
||||
.saturating_add(Weight::from_parts(7_648_787, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 45_578 nanoseconds.
|
||||
Weight::from_parts(47_161_000, 52673)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
fn receive_single_n_bytes_message_proof(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_702 nanoseconds.
|
||||
Weight::from_parts(41_040_143, 52673)
|
||||
// Standard Error: 5
|
||||
.saturating_add(Weight::from_parts(1_174, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_single_message() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `3558`
|
||||
// Minimum execution time: 37_197 nanoseconds.
|
||||
Weight::from_parts(38_371_000, 3558)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `3558`
|
||||
// Minimum execution time: 38_684 nanoseconds.
|
||||
Weight::from_parts(39_929_000, 3558)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:2 w:2)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `6126`
|
||||
// Minimum execution time: 41_363 nanoseconds.
|
||||
Weight::from_parts(42_621_000, 6126)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_925 nanoseconds.
|
||||
Weight::from_parts(39_617_000, 52673)
|
||||
// Standard Error: 612
|
||||
.saturating_add(Weight::from_parts(372_813, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
fn receive_single_message_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_724 nanoseconds.
|
||||
Weight::from_parts(40_650_000, 52673)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49208), added:
|
||||
/// 51683, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 1004]`.
|
||||
///
|
||||
/// The range of component `n` is `[1, 1004]`.
|
||||
fn receive_n_messages_proof(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 39_354 nanoseconds.
|
||||
Weight::from_parts(29_708_543, 52673)
|
||||
// Standard Error: 1_185
|
||||
.saturating_add(Weight::from_parts(7_648_787, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 45_578 nanoseconds.
|
||||
Weight::from_parts(47_161_000, 52673)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49208), added:
|
||||
/// 51683, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
fn receive_single_n_bytes_message_proof(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_702 nanoseconds.
|
||||
Weight::from_parts(41_040_143, 52673)
|
||||
// Standard Error: 5
|
||||
.saturating_add(Weight::from_parts(1_174, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_single_message() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `3558`
|
||||
// Minimum execution time: 37_197 nanoseconds.
|
||||
Weight::from_parts(38_371_000, 3558)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `3558`
|
||||
// Minimum execution time: 38_684 nanoseconds.
|
||||
Weight::from_parts(39_929_000, 3558)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added:
|
||||
/// 539, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:2 w:2)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(93), added: 2568,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeRialtoMessages OutboundMessages (r:0 w:2)
|
||||
///
|
||||
/// Proof: BridgeRialtoMessages OutboundMessages (max_values: None, max_size: Some(65596),
|
||||
/// added: 68071, mode: MaxEncodedLen)
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `6126`
|
||||
// Minimum execution time: 41_363 nanoseconds.
|
||||
Weight::from_parts(42_621_000, 6126)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2),
|
||||
/// added: 497, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added:
|
||||
/// 51655, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `n` is `[1, 16384]`.
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(n: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `653`
|
||||
// Estimated: `52673`
|
||||
// Minimum execution time: 38_925 nanoseconds.
|
||||
Weight::from_parts(39_617_000, 52673)
|
||||
// Standard Error: 612
|
||||
.saturating_add(Weight::from_parts(372_813, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Weight-related utilities.
|
||||
|
||||
use crate::weights::WeightInfo;
|
||||
|
||||
use bp_messages::{MessageNonce, UnrewardedRelayersState};
|
||||
use pezbp_runtime::{PreComputedSize, Size};
|
||||
use pezframe_support::weights::Weight;
|
||||
|
||||
/// Size of the message being delivered in benchmarks.
|
||||
pub const EXPECTED_DEFAULT_MESSAGE_LENGTH: u32 = 128;
|
||||
|
||||
/// We assume that size of signed extensions on all our chains and size of all 'small' arguments of
|
||||
/// calls we're checking here would fit 1KB.
|
||||
const SIGNED_EXTENSIONS_SIZE: u32 = 1024;
|
||||
|
||||
/// Number of extra bytes (excluding size of storage value itself) of storage proof.
|
||||
/// This mostly depends on number of entries (and their density) in the storage trie.
|
||||
/// Some reserve is reserved to account future chain growth.
|
||||
pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
|
||||
|
||||
/// Ensure that weights from `WeightInfoExt` implementation are looking correct.
|
||||
pub fn ensure_weights_are_correct<W: WeightInfoExt>() {
|
||||
// all components of weight formulae must have zero `proof_size`, because the `proof_size` is
|
||||
// benchmarked using `MaxEncodedLen` approach and there are no components that cause additional
|
||||
// db reads
|
||||
|
||||
// W::receive_messages_proof_outbound_lane_state_overhead().ref_time() may be zero because:
|
||||
// the outbound lane state processing code (`InboundLane::receive_state_update`) is minimal and
|
||||
// may not be accounted by our benchmarks
|
||||
assert_eq!(W::receive_messages_proof_outbound_lane_state_overhead().proof_size(), 0);
|
||||
assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
|
||||
assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);
|
||||
|
||||
// verify `receive_messages_delivery_proof` weight components
|
||||
assert_ne!(W::receive_messages_delivery_proof_overhead().ref_time(), 0);
|
||||
assert_ne!(W::receive_messages_delivery_proof_overhead().proof_size(), 0);
|
||||
// W::receive_messages_delivery_proof_messages_overhead(1).ref_time() may be zero because:
|
||||
// there's no code that iterates over confirmed messages in confirmation transaction
|
||||
assert_eq!(W::receive_messages_delivery_proof_messages_overhead(1).proof_size(), 0);
|
||||
// W::receive_messages_delivery_proof_relayers_overhead(1).ref_time() may be zero because:
|
||||
// runtime **can** choose not to pay any rewards to relayers
|
||||
// W::receive_messages_delivery_proof_relayers_overhead(1).proof_size() is an exception
|
||||
// it may or may not cause additional db reads, so proof size may vary
|
||||
assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
|
||||
assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);
|
||||
|
||||
// verify `receive_message_proof` weight
|
||||
let receive_messages_proof_weight =
|
||||
W::receive_messages_proof_weight(&PreComputedSize(1), 10, Weight::zero());
|
||||
assert_ne!(receive_messages_proof_weight.ref_time(), 0);
|
||||
assert_ne!(receive_messages_proof_weight.proof_size(), 0);
|
||||
messages_proof_size_does_not_affect_proof_size::<W>();
|
||||
messages_count_does_not_affect_proof_size::<W>();
|
||||
|
||||
// verify `receive_message_proof` weight
|
||||
let receive_messages_delivery_proof_weight = W::receive_messages_delivery_proof_weight(
|
||||
&PreComputedSize(1),
|
||||
&UnrewardedRelayersState::default(),
|
||||
);
|
||||
assert_ne!(receive_messages_delivery_proof_weight.ref_time(), 0);
|
||||
assert_ne!(receive_messages_delivery_proof_weight.proof_size(), 0);
|
||||
messages_delivery_proof_size_does_not_affect_proof_size::<W>();
|
||||
total_messages_in_delivery_proof_does_not_affect_proof_size::<W>();
|
||||
}
|
||||
|
||||
/// Ensure that we are able to dispatch maximal size messages.
|
||||
pub fn ensure_maximal_message_dispatch<W: WeightInfoExt>(
|
||||
max_incoming_message_size: u32,
|
||||
max_incoming_message_dispatch_weight: Weight,
|
||||
) {
|
||||
let message_dispatch_weight = W::message_dispatch_weight(max_incoming_message_size);
|
||||
assert!(
|
||||
message_dispatch_weight.all_lte(max_incoming_message_dispatch_weight),
|
||||
"Dispatch weight of maximal message {message_dispatch_weight:?} must be lower \
|
||||
than the hardcoded {max_incoming_message_dispatch_weight:?}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure that we're able to receive maximal (by-size and by-weight) message from other chain.
|
||||
pub fn ensure_able_to_receive_message<W: WeightInfoExt>(
|
||||
max_extrinsic_size: u32,
|
||||
max_extrinsic_weight: Weight,
|
||||
max_incoming_message_proof_size: u32,
|
||||
max_incoming_message_dispatch_weight: Weight,
|
||||
) {
|
||||
// verify that we're able to receive proof of maximal-size message
|
||||
let max_delivery_transaction_size =
|
||||
max_incoming_message_proof_size.saturating_add(SIGNED_EXTENSIONS_SIZE);
|
||||
assert!(
|
||||
max_delivery_transaction_size <= max_extrinsic_size,
|
||||
"Size of maximal message delivery transaction {max_incoming_message_proof_size} + \
|
||||
{SIGNED_EXTENSIONS_SIZE} is larger than maximal possible transaction size {max_extrinsic_size}",
|
||||
);
|
||||
|
||||
// verify that we're able to receive proof of maximal-size message with maximal dispatch weight
|
||||
let max_delivery_transaction_dispatch_weight = W::receive_messages_proof_weight(
|
||||
&PreComputedSize(
|
||||
(max_incoming_message_proof_size + W::expected_extra_storage_proof_size()) as usize,
|
||||
),
|
||||
1,
|
||||
max_incoming_message_dispatch_weight,
|
||||
);
|
||||
assert!(
|
||||
max_delivery_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
|
||||
"Weight of maximal message delivery transaction + {max_delivery_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure that we're able to receive maximal confirmation from other chain.
|
||||
pub fn ensure_able_to_receive_confirmation<W: WeightInfoExt>(
|
||||
max_extrinsic_size: u32,
|
||||
max_extrinsic_weight: Weight,
|
||||
max_inbound_lane_data_proof_size_from_peer_chain: u32,
|
||||
max_unrewarded_relayer_entries_at_peer_inbound_lane: MessageNonce,
|
||||
max_unconfirmed_messages_at_inbound_lane: MessageNonce,
|
||||
) {
|
||||
// verify that we're able to receive confirmation of maximal-size
|
||||
let max_confirmation_transaction_size =
|
||||
max_inbound_lane_data_proof_size_from_peer_chain.saturating_add(SIGNED_EXTENSIONS_SIZE);
|
||||
assert!(
|
||||
max_confirmation_transaction_size <= max_extrinsic_size,
|
||||
"Size of maximal message delivery confirmation transaction {max_inbound_lane_data_proof_size_from_peer_chain} + {SIGNED_EXTENSIONS_SIZE} is larger than maximal possible transaction size {max_extrinsic_size}",
|
||||
);
|
||||
|
||||
// verify that we're able to reward maximal number of relayers that have delivered maximal
|
||||
// number of messages
|
||||
let max_confirmation_transaction_dispatch_weight = W::receive_messages_delivery_proof_weight(
|
||||
&PreComputedSize(max_inbound_lane_data_proof_size_from_peer_chain as usize),
|
||||
&UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: max_unrewarded_relayer_entries_at_peer_inbound_lane,
|
||||
total_messages: max_unconfirmed_messages_at_inbound_lane,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
max_confirmation_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
|
||||
"Weight of maximal confirmation transaction {max_confirmation_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of message delivery call depends on the message proof size.
|
||||
fn messages_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
|
||||
let dispatch_weight = Weight::zero();
|
||||
let weight_when_proof_size_is_8k =
|
||||
W::receive_messages_proof_weight(&PreComputedSize(8 * 1024), 1, dispatch_weight);
|
||||
let weight_when_proof_size_is_16k =
|
||||
W::receive_messages_proof_weight(&PreComputedSize(16 * 1024), 1, dispatch_weight);
|
||||
|
||||
ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
|
||||
ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
|
||||
ensure_proof_size_is_the_same(
|
||||
weight_when_proof_size_is_8k,
|
||||
weight_when_proof_size_is_16k,
|
||||
"Messages proof size does not affect values that we read from our storage",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of message delivery call depends on the messages count.
|
||||
///
|
||||
/// In practice, it will depend on the messages count, because most probably every
|
||||
/// message will read something from db during dispatch. But this must be accounted
|
||||
/// by the `dispatch_weight`.
|
||||
fn messages_count_does_not_affect_proof_size<W: WeightInfoExt>() {
|
||||
let messages_proof_size = PreComputedSize(8 * 1024);
|
||||
let dispatch_weight = Weight::zero();
|
||||
let weight_of_one_incoming_message =
|
||||
W::receive_messages_proof_weight(&messages_proof_size, 1, dispatch_weight);
|
||||
let weight_of_two_incoming_messages =
|
||||
W::receive_messages_proof_weight(&messages_proof_size, 2, dispatch_weight);
|
||||
|
||||
ensure_weight_components_are_not_zero(weight_of_one_incoming_message);
|
||||
ensure_weight_components_are_not_zero(weight_of_two_incoming_messages);
|
||||
ensure_proof_size_is_the_same(
|
||||
weight_of_one_incoming_message,
|
||||
weight_of_two_incoming_messages,
|
||||
"Number of same-lane incoming messages does not affect values that we read from our storage",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of delivery confirmation call depends on the delivery proof size.
|
||||
fn messages_delivery_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
|
||||
let relayers_state = UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 1,
|
||||
last_delivered_nonce: 1,
|
||||
};
|
||||
let weight_when_proof_size_is_8k =
|
||||
W::receive_messages_delivery_proof_weight(&PreComputedSize(8 * 1024), &relayers_state);
|
||||
let weight_when_proof_size_is_16k =
|
||||
W::receive_messages_delivery_proof_weight(&PreComputedSize(16 * 1024), &relayers_state);
|
||||
|
||||
ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
|
||||
ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
|
||||
ensure_proof_size_is_the_same(
|
||||
weight_when_proof_size_is_8k,
|
||||
weight_when_proof_size_is_16k,
|
||||
"Messages delivery proof size does not affect values that we read from our storage",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of delivery confirmation call depends on the number of confirmed
|
||||
/// messages.
|
||||
fn total_messages_in_delivery_proof_does_not_affect_proof_size<W: WeightInfoExt>() {
|
||||
let proof_size = PreComputedSize(8 * 1024);
|
||||
let weight_when_1k_messages_confirmed = W::receive_messages_delivery_proof_weight(
|
||||
&proof_size,
|
||||
&UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 1024,
|
||||
last_delivered_nonce: 1,
|
||||
},
|
||||
);
|
||||
let weight_when_2k_messages_confirmed = W::receive_messages_delivery_proof_weight(
|
||||
&proof_size,
|
||||
&UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: 1,
|
||||
messages_in_oldest_entry: 1,
|
||||
total_messages: 2048,
|
||||
last_delivered_nonce: 1,
|
||||
},
|
||||
);
|
||||
|
||||
ensure_weight_components_are_not_zero(weight_when_1k_messages_confirmed);
|
||||
ensure_weight_components_are_not_zero(weight_when_2k_messages_confirmed);
|
||||
ensure_proof_size_is_the_same(
|
||||
weight_when_1k_messages_confirmed,
|
||||
weight_when_2k_messages_confirmed,
|
||||
"More messages in delivery proof does not affect values that we read from our storage",
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics if either Weight' `proof_size` or `ref_time` are zero.
|
||||
fn ensure_weight_components_are_not_zero(weight: Weight) {
|
||||
assert_ne!(weight.ref_time(), 0);
|
||||
assert_ne!(weight.proof_size(), 0);
|
||||
}
|
||||
|
||||
/// Panics if `proof_size` of `weight1` is not equal to `proof_size` of `weight2`.
|
||||
fn ensure_proof_size_is_the_same(weight1: Weight, weight2: Weight, msg: &str) {
|
||||
assert_eq!(
|
||||
weight1.proof_size(),
|
||||
weight2.proof_size(),
|
||||
"{msg}: {} must be equal to {}",
|
||||
weight1.proof_size(),
|
||||
weight2.proof_size(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Extended weight info.
|
||||
pub trait WeightInfoExt: WeightInfo {
|
||||
/// Size of proof that is already included in the single message delivery weight.
|
||||
///
|
||||
/// The message submitter (at source chain) has already covered this cost. But there are two
|
||||
/// factors that may increase proof size: (1) the message size may be larger than predefined
|
||||
/// and (2) relayer may add extra trie nodes to the proof. So if proof size is larger than
|
||||
/// this value, we're going to charge relayer for that.
|
||||
fn expected_extra_storage_proof_size() -> u32;
|
||||
|
||||
// Our configuration assumes that the runtime has special signed extensions used to:
|
||||
//
|
||||
// 1) reject obsolete delivery and confirmation transactions;
|
||||
//
|
||||
// 2) refund transaction cost to relayer and register his rewards.
|
||||
//
|
||||
// The checks in (1) are trivial, so its computation weight may be ignored. And we only touch
|
||||
// storage values that are read during the call. So we may ignore the weight of this check.
|
||||
//
|
||||
// However, during (2) we read and update storage values of other pallets
|
||||
// (`pezpallet-bridge-relayers` and balances/assets pezpallet). So we need to add this weight to the
|
||||
// weight of our call. Hence two following methods.
|
||||
|
||||
/// Extra weight that is added to the `receive_messages_proof` call weight by signed extensions
|
||||
/// that are declared at runtime level.
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight;
|
||||
|
||||
/// Extra weight that is added to the `receive_messages_delivery_proof` call weight by signed
|
||||
/// extensions that are declared at runtime level.
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight;
|
||||
|
||||
// Functions that are directly mapped to extrinsics weights.
|
||||
|
||||
/// Weight of message delivery extrinsic.
|
||||
fn receive_messages_proof_weight(
|
||||
proof: &impl Size,
|
||||
messages_count: u32,
|
||||
dispatch_weight: Weight,
|
||||
) -> Weight {
|
||||
// basic components of extrinsic weight
|
||||
let base_weight = Self::receive_n_messages_proof(messages_count);
|
||||
let transaction_overhead_from_runtime =
|
||||
Self::receive_messages_proof_overhead_from_runtime();
|
||||
let outbound_state_delivery_weight =
|
||||
Self::receive_messages_proof_outbound_lane_state_overhead();
|
||||
let messages_dispatch_weight = dispatch_weight;
|
||||
|
||||
// proof size overhead weight
|
||||
let expected_proof_size = EXPECTED_DEFAULT_MESSAGE_LENGTH
|
||||
.saturating_mul(messages_count.saturating_sub(1))
|
||||
.saturating_add(Self::expected_extra_storage_proof_size());
|
||||
let actual_proof_size = proof.size();
|
||||
let proof_size_overhead = Self::storage_proof_size_overhead(
|
||||
actual_proof_size.saturating_sub(expected_proof_size),
|
||||
);
|
||||
|
||||
base_weight
|
||||
.saturating_add(transaction_overhead_from_runtime)
|
||||
.saturating_add(outbound_state_delivery_weight)
|
||||
.saturating_add(messages_dispatch_weight)
|
||||
.saturating_add(proof_size_overhead)
|
||||
}
|
||||
|
||||
/// Weight of confirmation delivery extrinsic.
|
||||
fn receive_messages_delivery_proof_weight(
|
||||
proof: &impl Size,
|
||||
relayers_state: &UnrewardedRelayersState,
|
||||
) -> Weight {
|
||||
// basic components of extrinsic weight
|
||||
let transaction_overhead = Self::receive_messages_delivery_proof_overhead();
|
||||
let transaction_overhead_from_runtime =
|
||||
Self::receive_messages_delivery_proof_overhead_from_runtime();
|
||||
let messages_overhead =
|
||||
Self::receive_messages_delivery_proof_messages_overhead(relayers_state.total_messages);
|
||||
let relayers_overhead = Self::receive_messages_delivery_proof_relayers_overhead(
|
||||
relayers_state.unrewarded_relayer_entries,
|
||||
);
|
||||
|
||||
// proof size overhead weight
|
||||
let expected_proof_size = Self::expected_extra_storage_proof_size();
|
||||
let actual_proof_size = proof.size();
|
||||
let proof_size_overhead = Self::storage_proof_size_overhead(
|
||||
actual_proof_size.saturating_sub(expected_proof_size),
|
||||
);
|
||||
|
||||
transaction_overhead
|
||||
.saturating_add(transaction_overhead_from_runtime)
|
||||
.saturating_add(messages_overhead)
|
||||
.saturating_add(relayers_overhead)
|
||||
.saturating_add(proof_size_overhead)
|
||||
}
|
||||
|
||||
// Functions that are used by extrinsics weights formulas.
|
||||
|
||||
/// Returns weight that needs to be accounted when message delivery transaction
|
||||
/// (`receive_messages_proof`) is carrying outbound lane state proof.
|
||||
fn receive_messages_proof_outbound_lane_state_overhead() -> Weight {
|
||||
let weight_of_single_message_and_lane_state =
|
||||
Self::receive_single_message_proof_with_outbound_lane_state();
|
||||
let weight_of_single_message = Self::receive_single_message_proof();
|
||||
weight_of_single_message_and_lane_state.saturating_sub(weight_of_single_message)
|
||||
}
|
||||
|
||||
/// Returns weight overhead of delivery confirmation transaction
|
||||
/// (`receive_messages_delivery_proof`).
|
||||
fn receive_messages_delivery_proof_overhead() -> Weight {
|
||||
let weight_of_two_messages_and_two_tx_overheads =
|
||||
Self::receive_delivery_proof_for_single_message().saturating_mul(2);
|
||||
let weight_of_two_messages_and_single_tx_overhead =
|
||||
Self::receive_delivery_proof_for_two_messages_by_single_relayer();
|
||||
weight_of_two_messages_and_two_tx_overheads
|
||||
.saturating_sub(weight_of_two_messages_and_single_tx_overhead)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when receiving confirmations for given a number of
|
||||
/// messages with delivery confirmation transaction (`receive_messages_delivery_proof`).
|
||||
fn receive_messages_delivery_proof_messages_overhead(messages: MessageNonce) -> Weight {
|
||||
let weight_of_two_messages =
|
||||
Self::receive_delivery_proof_for_two_messages_by_single_relayer();
|
||||
let weight_of_single_message = Self::receive_delivery_proof_for_single_message();
|
||||
weight_of_two_messages
|
||||
.saturating_sub(weight_of_single_message)
|
||||
.saturating_mul(messages as _)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when receiving confirmations for given a number of
|
||||
/// relayers entries with delivery confirmation transaction (`receive_messages_delivery_proof`).
|
||||
fn receive_messages_delivery_proof_relayers_overhead(relayers: MessageNonce) -> Weight {
|
||||
let weight_of_two_messages_by_two_relayers =
|
||||
Self::receive_delivery_proof_for_two_messages_by_two_relayers();
|
||||
let weight_of_two_messages_by_single_relayer =
|
||||
Self::receive_delivery_proof_for_two_messages_by_single_relayer();
|
||||
weight_of_two_messages_by_two_relayers
|
||||
.saturating_sub(weight_of_two_messages_by_single_relayer)
|
||||
.saturating_mul(relayers as _)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when storage proof of given size is received
|
||||
/// (either in `receive_messages_proof` or `receive_messages_delivery_proof`).
|
||||
///
|
||||
/// **IMPORTANT**: this overhead is already included in the 'base' transaction cost - e.g. proof
|
||||
/// size depends on messages count or number of entries in the unrewarded relayers set. So this
|
||||
/// shouldn't be added to cost of transaction, but instead should act as a minimal cost that the
|
||||
/// relayer must pay when it relays proof of given size (even if cost based on other parameters
|
||||
/// is less than that cost).
|
||||
fn storage_proof_size_overhead(proof_size: u32) -> Weight {
|
||||
let proof_size_in_bytes = proof_size;
|
||||
let byte_weight = Self::receive_single_n_bytes_message_proof(2) -
|
||||
Self::receive_single_n_bytes_message_proof(1);
|
||||
proof_size_in_bytes * byte_weight
|
||||
}
|
||||
|
||||
// Functions that may be used by runtime developers.
|
||||
|
||||
/// Returns dispatch weight of message of given size.
|
||||
///
|
||||
/// This function would return correct value only if your runtime is configured to run
|
||||
/// `receive_single_message_proof_with_dispatch` benchmark. See its requirements for
|
||||
/// details.
|
||||
fn message_dispatch_weight(message_size: u32) -> Weight {
|
||||
let message_size_in_bytes = message_size;
|
||||
Self::receive_single_n_bytes_message_proof_with_dispatch(message_size_in_bytes)
|
||||
.saturating_sub(Self::receive_single_n_bytes_message_proof(message_size_in_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightInfoExt for () {
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: pezframe_system::Config> WeightInfoExt for crate::weights::BridgeWeight<T> {
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{tests::mock::TestRuntime, weights::BridgeWeight};
|
||||
|
||||
#[test]
|
||||
fn ensure_default_weights_are_correct() {
|
||||
ensure_weights_are_correct::<BridgeWeight<TestRuntime>>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-relayers"
|
||||
description = "Module used to store relayer rewards and coordinate relayers set."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-bridge-relayers"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-messages = { workspace = true }
|
||||
bp-relayers = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
pezpallet-bridge-grandpa = { workspace = true }
|
||||
pezpallet-bridge-messages = { workspace = true }
|
||||
pezpallet-bridge-teyrchains = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-transaction-payment = { workspace = true }
|
||||
pezsp-arithmetic = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-pezkuwi-core = { workspace = true }
|
||||
bp-test-utils = { workspace = true }
|
||||
bp-teyrchains = { workspace = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-utility = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-messages/std",
|
||||
"bp-pezkuwi-core/std",
|
||||
"bp-relayers/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-test-utils/std",
|
||||
"bp-teyrchains/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-bridge-grandpa/std",
|
||||
"pezpallet-bridge-messages/std",
|
||||
"pezpallet-bridge-teyrchains/std",
|
||||
"pezpallet-transaction-payment/std",
|
||||
"pezpallet-utility/std",
|
||||
"scale-info/std",
|
||||
"pezsp-arithmetic/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"tracing/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"bp-relayers/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"bp-teyrchains/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-bridge-grandpa/runtime-benchmarks",
|
||||
"pezpallet-bridge-messages/runtime-benchmarks",
|
||||
"pezpallet-bridge-teyrchains/runtime-benchmarks",
|
||||
"pezpallet-transaction-payment/runtime-benchmarks",
|
||||
"pezpallet-utility/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-bridge-grandpa/try-runtime",
|
||||
"pezpallet-bridge-messages/try-runtime",
|
||||
"pezpallet-bridge-teyrchains/try-runtime",
|
||||
"pezpallet-transaction-payment/try-runtime",
|
||||
"pezpallet-utility/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
integrity-test = []
|
||||
@@ -0,0 +1,14 @@
|
||||
# Bridge Relayers Pezpallet
|
||||
|
||||
The pezpallet serves as a storage for pending bridge relayer rewards. Any runtime component may register reward
|
||||
to some relayer for doing some useful job at some messages lane. Later, the relayer may claim its rewards
|
||||
using the `claim_rewards` call.
|
||||
|
||||
The reward payment procedure is abstracted from the pezpallet code. One of possible implementations, is the
|
||||
[`PayLaneRewardFromAccount`](../../primitives/relayers/src/lib.rs), which just does a `Currency::transfer`
|
||||
call to relayer account from the relayer-rewards account, determined by the message lane id.
|
||||
|
||||
We have two examples of how this pezpallet is used in production. Rewards are registered at the target chain to
|
||||
compensate fees of message delivery transactions (and linked finality delivery calls). At the source chain, rewards
|
||||
are registered during delivery confirmation transactions. You may find more information about that in the
|
||||
[Kusama <> PezkuwiChain bridge](../../docs/pezkuwi-kusama-bridge-overview.md) documentation.
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Benchmarks for the relayers Pezpallet.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::*;
|
||||
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_support::{assert_ok, weights::Weight};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_runtime::traits::One;
|
||||
|
||||
/// Reward amount that is (hopefully) is larger than existential deposit across all chains.
|
||||
const REWARD_AMOUNT: u32 = u32::MAX;
|
||||
|
||||
/// Pezpallet we're benchmarking here.
|
||||
pub struct Pezpallet<T: Config<I>, I: 'static = ()>(crate::Pezpallet<T, I>);
|
||||
|
||||
/// Trait that must be implemented by runtime.
|
||||
pub trait Config<I: 'static = ()>: crate::Config<I> {
|
||||
/// `T::Reward` to use in benchmarks.
|
||||
fn bench_reward() -> Self::Reward;
|
||||
/// Prepare environment for paying given reward for serving given lane.
|
||||
fn prepare_rewards_account(
|
||||
reward_kind: Self::Reward,
|
||||
reward: Self::RewardBalance,
|
||||
) -> Option<BeneficiaryOf<Self, I>>;
|
||||
/// Give enough balance to given account.
|
||||
fn deposit_account(account: Self::AccountId, balance: Self::Balance);
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config<I>, I: 'static>(
|
||||
generic_event: <T as pezpallet::Config<I>>::RuntimeEvent,
|
||||
) {
|
||||
pezframe_system::Pezpallet::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
#[instance_benchmarks(
|
||||
where
|
||||
BeneficiaryOf<T, I>: From<<T as pezframe_system::Config>::AccountId>,
|
||||
)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn claim_rewards() {
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let reward_kind = T::bench_reward();
|
||||
let reward_balance = T::RewardBalance::from(REWARD_AMOUNT);
|
||||
let _ = T::prepare_rewards_account(reward_kind, reward_balance);
|
||||
RelayerRewards::<T, I>::insert(&relayer, reward_kind, reward_balance);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(relayer.clone()), reward_kind);
|
||||
|
||||
// we can't check anything here, because `PaymentProcedure` is responsible for
|
||||
// payment logic, so we assume that if call has succeeded, the procedure has
|
||||
// also completed successfully
|
||||
assert_last_event::<T, I>(
|
||||
Event::RewardPaid {
|
||||
relayer: relayer.clone(),
|
||||
reward_kind,
|
||||
reward_balance,
|
||||
beneficiary: relayer.into(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn claim_rewards_to() -> Result<(), BenchmarkError> {
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let reward_kind = T::bench_reward();
|
||||
let reward_balance = T::RewardBalance::from(REWARD_AMOUNT);
|
||||
|
||||
let Some(alternative_beneficiary) = T::prepare_rewards_account(reward_kind, reward_balance)
|
||||
else {
|
||||
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
|
||||
};
|
||||
RelayerRewards::<T, I>::insert(&relayer, reward_kind, reward_balance);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(relayer.clone()), reward_kind, alternative_beneficiary.clone());
|
||||
|
||||
// we can't check anything here, because `PaymentProcedure` is responsible for
|
||||
// payment logic, so we assume that if call has succeeded, the procedure has
|
||||
// also completed successfully
|
||||
assert_last_event::<T, I>(
|
||||
Event::RewardPaid {
|
||||
relayer: relayer.clone(),
|
||||
reward_kind,
|
||||
reward_balance,
|
||||
beneficiary: alternative_beneficiary,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register() {
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let valid_till = pezframe_system::Pezpallet::<T>::block_number()
|
||||
.saturating_add(crate::Pezpallet::<T, I>::required_registration_lease())
|
||||
.saturating_add(One::one())
|
||||
.saturating_add(One::one());
|
||||
T::deposit_account(relayer.clone(), crate::Pezpallet::<T, I>::required_stake());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(relayer.clone()), valid_till);
|
||||
|
||||
assert!(crate::Pezpallet::<T, I>::is_registration_active(&relayer));
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn deregister() {
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let valid_till = pezframe_system::Pezpallet::<T>::block_number()
|
||||
.saturating_add(crate::Pezpallet::<T, I>::required_registration_lease())
|
||||
.saturating_add(One::one())
|
||||
.saturating_add(One::one());
|
||||
T::deposit_account(relayer.clone(), crate::Pezpallet::<T, I>::required_stake());
|
||||
crate::Pezpallet::<T, I>::register(RawOrigin::Signed(relayer.clone()).into(), valid_till)
|
||||
.unwrap();
|
||||
pezframe_system::Pezpallet::<T>::set_block_number(valid_till.saturating_add(One::one()));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(relayer.clone()));
|
||||
|
||||
assert!(!crate::Pezpallet::<T, I>::is_registration_active(&relayer));
|
||||
}
|
||||
|
||||
// Benchmark `slash_and_deregister` method of the pezpallet. We are adding this weight to
|
||||
// the weight of message delivery call if `BridgeRelayersTransactionExtension` signed extension
|
||||
// is deployed at runtime level.
|
||||
#[benchmark]
|
||||
fn slash_and_deregister() {
|
||||
// prepare and register relayer account
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
let valid_till = pezframe_system::Pezpallet::<T>::block_number()
|
||||
.saturating_add(crate::Pezpallet::<T, I>::required_registration_lease())
|
||||
.saturating_add(One::one())
|
||||
.saturating_add(One::one());
|
||||
T::deposit_account(relayer.clone(), crate::Pezpallet::<T, I>::required_stake());
|
||||
assert_ok!(crate::Pezpallet::<T, I>::register(
|
||||
RawOrigin::Signed(relayer.clone()).into(),
|
||||
valid_till
|
||||
));
|
||||
|
||||
// create slash destination account
|
||||
let slash_destination: T::AccountId = whitelisted_caller();
|
||||
T::deposit_account(slash_destination.clone(), Zero::zero());
|
||||
|
||||
#[block]
|
||||
{
|
||||
crate::Pezpallet::<T, I>::slash_and_deregister(
|
||||
&relayer,
|
||||
bp_relayers::ExplicitOrAccountParams::Explicit::<_, ()>(slash_destination),
|
||||
);
|
||||
}
|
||||
|
||||
assert!(!crate::Pezpallet::<T, I>::is_registration_active(&relayer));
|
||||
}
|
||||
|
||||
// Benchmark `register_relayer_reward` method of the pezpallet. We are adding this weight to
|
||||
// the weight of message delivery call if `BridgeRelayersTransactionExtension` signed extension
|
||||
// is deployed at runtime level.
|
||||
#[benchmark]
|
||||
fn register_relayer_reward() {
|
||||
let reward_kind = T::bench_reward();
|
||||
let relayer: T::AccountId = whitelisted_caller();
|
||||
|
||||
#[block]
|
||||
{
|
||||
crate::Pezpallet::<T, I>::register_relayer_reward(reward_kind, &relayer, One::one());
|
||||
}
|
||||
|
||||
assert_eq!(RelayerRewards::<T, I>::get(relayer, &reward_kind), Some(One::one()));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pezpallet, crate::mock::new_test_ext(), crate::mock::TestRuntime);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Adapter that allows using `pezpallet-bridge-relayers` as a signed extension in the
|
||||
//! bridge with remote GRANDPA chain.
|
||||
|
||||
use crate::{
|
||||
extension::verify_messages_call_succeeded, Config as BridgeRelayersConfig, LOG_TARGET,
|
||||
};
|
||||
|
||||
use bp_relayers::{BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
|
||||
use pezbp_runtime::{Chain, StaticStrProvider};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::dispatch::{DispatchInfo, PostDispatchInfo};
|
||||
use pezframe_system::Config as SystemConfig;
|
||||
use pezpallet_bridge_grandpa::{
|
||||
CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig,
|
||||
SubmitFinalityProofHelper,
|
||||
};
|
||||
use pezpallet_bridge_messages::{
|
||||
CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{Dispatchable, Get},
|
||||
transaction_validity::{TransactionPriority, TransactionValidityError},
|
||||
Saturating,
|
||||
};
|
||||
|
||||
/// Adapter to be used in signed extension configuration, when bridging with remote
|
||||
/// chains that are using GRANDPA finality.
|
||||
pub struct WithGrandpaChainExtensionConfig<
|
||||
// signed extension identifier
|
||||
IdProvider,
|
||||
// runtime that implements `BridgeMessagesConfig<BridgeMessagesPalletInstance>`, which
|
||||
// uses `BridgeGrandpaConfig<BridgeGrandpaPalletInstance>` to receive messages and
|
||||
// confirmations from the remote chain.
|
||||
Runtime,
|
||||
// batch call unpacker
|
||||
BatchCallUnpacker,
|
||||
// instance of the `pezpallet-bridge-grandpa`, tracked by this extension
|
||||
BridgeGrandpaPalletInstance,
|
||||
// instance of BridgedChain `pezpallet-bridge-messages`, tracked by this extension
|
||||
BridgeMessagesPalletInstance,
|
||||
// instance of `pezpallet-bridge-relayers`, tracked by this extension
|
||||
BridgeRelayersPalletInstance,
|
||||
// message delivery transaction priority boost for every additional message
|
||||
PriorityBoostPerMessage,
|
||||
>(
|
||||
PhantomData<(
|
||||
IdProvider,
|
||||
Runtime,
|
||||
BatchCallUnpacker,
|
||||
BridgeGrandpaPalletInstance,
|
||||
BridgeMessagesPalletInstance,
|
||||
BridgeRelayersPalletInstance,
|
||||
PriorityBoostPerMessage,
|
||||
)>,
|
||||
);
|
||||
|
||||
impl<ID, R, BCU, GI, MI, RI, P> ExtensionConfig
|
||||
for WithGrandpaChainExtensionConfig<ID, R, BCU, GI, MI, RI, P>
|
||||
where
|
||||
ID: StaticStrProvider,
|
||||
R: BridgeRelayersConfig<RI>
|
||||
+ BridgeMessagesConfig<MI, BridgedChain = pezpallet_bridge_grandpa::BridgedChain<R, GI>>
|
||||
+ BridgeGrandpaConfig<GI>,
|
||||
BCU: BatchCallUnpacker<R>,
|
||||
GI: 'static,
|
||||
MI: 'static,
|
||||
RI: 'static,
|
||||
P: Get<TransactionPriority>,
|
||||
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
|
||||
+ BridgeGrandpaCallSubtype<R, GI>
|
||||
+ BridgeMessagesCallSubType<R, MI>,
|
||||
{
|
||||
type IdProvider = ID;
|
||||
type Runtime = R;
|
||||
type BridgeMessagesPalletInstance = MI;
|
||||
type BridgeRelayersPalletInstance = RI;
|
||||
type PriorityBoostPerMessage = P;
|
||||
type RemoteGrandpaChainBlockNumber = pezpallet_bridge_grandpa::BridgedBlockNumber<R, GI>;
|
||||
type LaneId = LaneIdOf<R, Self::BridgeMessagesPalletInstance>;
|
||||
|
||||
fn parse_and_check_for_obsolete_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<
|
||||
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
|
||||
TransactionValidityError,
|
||||
> {
|
||||
let calls = BCU::unpack(call, 2);
|
||||
let total_calls = calls.len();
|
||||
let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
|
||||
|
||||
let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info());
|
||||
let relay_finality_call =
|
||||
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
|
||||
|
||||
Ok(match (total_calls, relay_finality_call, msgs_call) {
|
||||
(2, Some(relay_finality_call), Some(msgs_call)) =>
|
||||
Some(ExtensionCallInfo::RelayFinalityAndMsgs(relay_finality_call, msgs_call)),
|
||||
(1, None, Some(msgs_call)) => Some(ExtensionCallInfo::Msgs(msgs_call)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_obsolete_parsed_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<&R::RuntimeCall, TransactionValidityError> {
|
||||
call.check_obsolete_submit_finality_proof()?;
|
||||
call.check_obsolete_call()?;
|
||||
Ok(call)
|
||||
}
|
||||
|
||||
fn check_call_result(
|
||||
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
|
||||
call_data: &mut ExtensionCallData,
|
||||
relayer: &R::AccountId,
|
||||
) -> bool {
|
||||
verify_submit_finality_proof_succeeded::<Self, GI>(call_info, call_data, relayer) &&
|
||||
verify_messages_call_succeeded::<Self>(call_info, call_data, relayer)
|
||||
}
|
||||
}
|
||||
|
||||
/// If the batch call contains the GRANDPA chain state update call, verify that it
|
||||
/// has been successful.
|
||||
///
|
||||
/// Only returns false when GRANDPA chain state update call has failed.
|
||||
pub(crate) fn verify_submit_finality_proof_succeeded<C, GI>(
|
||||
call_info: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber, C::LaneId>,
|
||||
call_data: &mut ExtensionCallData,
|
||||
relayer: &<C::Runtime as SystemConfig>::AccountId,
|
||||
) -> bool
|
||||
where
|
||||
C: ExtensionConfig,
|
||||
GI: 'static,
|
||||
C::Runtime: BridgeGrandpaConfig<GI>,
|
||||
<C::Runtime as BridgeGrandpaConfig<GI>>::BridgedChain:
|
||||
Chain<BlockNumber = C::RemoteGrandpaChainBlockNumber>,
|
||||
{
|
||||
let Some(finality_proof_info) = call_info.submit_finality_proof_info() else { return true };
|
||||
|
||||
if !SubmitFinalityProofHelper::<C::Runtime, GI>::was_successful(
|
||||
finality_proof_info.block_number,
|
||||
) {
|
||||
// we only refund relayer if all calls have updated chain state
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
id_provider=%C::IdProvider::STR,
|
||||
lane_id=?call_info.messages_call_info().lane_id(),
|
||||
?relayer,
|
||||
"Relayer has submitted invalid GRANDPA chain finality proof"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// there's a conflict between how bridge GRANDPA pezpallet works and a `utility.batchAll`
|
||||
// transaction. If relay chain header is mandatory, the GRANDPA pezpallet returns
|
||||
// `Pays::No`, because such transaction is mandatory for operating the bridge. But
|
||||
// `utility.batchAll` transaction always requires payment. But in both cases we'll
|
||||
// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
|
||||
// to submit dedicated transaction.
|
||||
|
||||
// submitter has means to include extra weight/bytes in the `submit_finality_proof`
|
||||
// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
|
||||
call_data.extra_weight.saturating_accrue(finality_proof_info.extra_weight);
|
||||
call_data.extra_size.saturating_accrue(finality_proof_info.extra_size);
|
||||
|
||||
true
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Adapter that allows using `pezpallet-bridge-relayers` as a signed extension in the
|
||||
//! bridge with any remote chain. This adapter does not refund any finality transactions.
|
||||
|
||||
use crate::{extension::verify_messages_call_succeeded, Config as BridgeRelayersConfig};
|
||||
|
||||
use bp_relayers::{ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
|
||||
use pezbp_runtime::StaticStrProvider;
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::dispatch::{DispatchInfo, PostDispatchInfo};
|
||||
use pezpallet_bridge_messages::{
|
||||
CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{Dispatchable, Get},
|
||||
transaction_validity::{TransactionPriority, TransactionValidityError},
|
||||
};
|
||||
|
||||
/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation
|
||||
/// transactions. Finality transactions are not refunded.
|
||||
pub struct WithMessagesExtensionConfig<
|
||||
IdProvider,
|
||||
Runtime,
|
||||
BridgeMessagesPalletInstance,
|
||||
BridgeRelayersPalletInstance,
|
||||
PriorityBoostPerMessage,
|
||||
>(
|
||||
PhantomData<(
|
||||
// signed extension identifier
|
||||
IdProvider,
|
||||
// runtime with `pezpallet-bridge-messages` pezpallet deployed
|
||||
Runtime,
|
||||
// instance of BridgedChain `pezpallet-bridge-messages`, tracked by this extension
|
||||
BridgeMessagesPalletInstance,
|
||||
// instance of `pezpallet-bridge-relayers`, tracked by this extension
|
||||
BridgeRelayersPalletInstance,
|
||||
// message delivery transaction priority boost for every additional message
|
||||
PriorityBoostPerMessage,
|
||||
)>,
|
||||
);
|
||||
|
||||
impl<ID, R, MI, RI, P> ExtensionConfig for WithMessagesExtensionConfig<ID, R, MI, RI, P>
|
||||
where
|
||||
ID: StaticStrProvider,
|
||||
R: BridgeRelayersConfig<RI> + BridgeMessagesConfig<MI>,
|
||||
MI: 'static,
|
||||
RI: 'static,
|
||||
P: Get<TransactionPriority>,
|
||||
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
|
||||
+ BridgeMessagesCallSubType<R, MI>,
|
||||
{
|
||||
type IdProvider = ID;
|
||||
type Runtime = R;
|
||||
type BridgeMessagesPalletInstance = MI;
|
||||
type BridgeRelayersPalletInstance = RI;
|
||||
type PriorityBoostPerMessage = P;
|
||||
type RemoteGrandpaChainBlockNumber = ();
|
||||
type LaneId = LaneIdOf<R, Self::BridgeMessagesPalletInstance>;
|
||||
|
||||
fn parse_and_check_for_obsolete_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<
|
||||
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
|
||||
TransactionValidityError,
|
||||
> {
|
||||
let call = Self::check_obsolete_parsed_call(call)?;
|
||||
Ok(call.call_info().map(ExtensionCallInfo::Msgs))
|
||||
}
|
||||
|
||||
fn check_obsolete_parsed_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<&R::RuntimeCall, TransactionValidityError> {
|
||||
call.check_obsolete_call()?;
|
||||
Ok(call)
|
||||
}
|
||||
|
||||
fn check_call_result(
|
||||
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
|
||||
call_data: &mut ExtensionCallData,
|
||||
relayer: &R::AccountId,
|
||||
) -> bool {
|
||||
verify_messages_call_succeeded::<Self>(call_info, call_data, relayer)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,431 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Bridge transaction priority calculator.
|
||||
//!
|
||||
//! We want to prioritize message delivery transactions with more messages over
|
||||
//! transactions with less messages. That's because we reject delivery transactions
|
||||
//! if it contains already delivered message. And if some transaction delivers
|
||||
//! single message with nonce `N`, then the transaction with nonces `N..=N+100` will
|
||||
//! be rejected. This can lower bridge throughput down to one message per block.
|
||||
|
||||
use pezframe_support::traits::Get;
|
||||
use pezsp_runtime::transaction_validity::TransactionPriority;
|
||||
|
||||
// reexport everything from `integrity_tests` module
|
||||
#[allow(unused_imports)]
|
||||
pub use integrity_tests::*;
|
||||
|
||||
/// We'll deal with different bridge items here - messages, headers, ...
|
||||
/// To avoid being too verbose with generic code, let's just define a separate alias.
|
||||
pub type ItemCount = u64;
|
||||
|
||||
/// Compute priority boost for transaction that brings given number of bridge
|
||||
/// items (messages, headers, ...), when every additional item adds `PriorityBoostPerItem`
|
||||
/// to transaction priority.
|
||||
pub fn compute_priority_boost<PriorityBoostPerItem>(n_items: ItemCount) -> TransactionPriority
|
||||
where
|
||||
PriorityBoostPerItem: Get<TransactionPriority>,
|
||||
{
|
||||
// we don't want any boost for transaction with single (additional) item => minus one
|
||||
PriorityBoostPerItem::get().saturating_mul(n_items.saturating_sub(1))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "integrity-test"))]
|
||||
mod integrity_tests {}
|
||||
|
||||
#[cfg(feature = "integrity-test")]
|
||||
mod integrity_tests {
|
||||
use super::{compute_priority_boost, ItemCount};
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use pezbp_runtime::PreComputedSize;
|
||||
use pezframe_support::{
|
||||
dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo},
|
||||
traits::Get,
|
||||
};
|
||||
use pezpallet_transaction_payment::OnChargeTransaction;
|
||||
use pezsp_runtime::{
|
||||
traits::{Dispatchable, UniqueSaturatedInto, Zero},
|
||||
transaction_validity::TransactionPriority,
|
||||
FixedPointOperand, SaturatedConversion, Saturating,
|
||||
};
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as pezpallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<
|
||||
T,
|
||||
>>::Balance;
|
||||
|
||||
/// Ensures that the value of `PriorityBoostPerItem` matches the value of
|
||||
/// `tip_boost_per_item`.
|
||||
///
|
||||
/// We want two transactions, `TX1` with `N` items and `TX2` with `N+1` items, have almost
|
||||
/// the same priority if we'll add `tip_boost_per_item` tip to the `TX1`. We want to be sure
|
||||
/// that if we add plain `PriorityBoostPerItem` priority to `TX1`, the priority will be close
|
||||
/// to `TX2` as well.
|
||||
fn ensure_priority_boost_is_sane<PriorityBoostPerItem, Balance>(
|
||||
param_name: &str,
|
||||
max_items: ItemCount,
|
||||
tip_boost_per_item: Balance,
|
||||
estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority,
|
||||
) where
|
||||
PriorityBoostPerItem: Get<TransactionPriority>,
|
||||
ItemCount: UniqueSaturatedInto<Balance>,
|
||||
Balance: FixedPointOperand + Zero,
|
||||
{
|
||||
let priority_boost_per_item = PriorityBoostPerItem::get();
|
||||
for n_items in 1..=max_items {
|
||||
let base_priority = estimate_priority(n_items, Zero::zero());
|
||||
let priority_boost = compute_priority_boost::<PriorityBoostPerItem>(n_items);
|
||||
let priority_with_boost = base_priority
|
||||
.checked_add(priority_boost)
|
||||
.expect("priority overflow: try lowering `max_items` or `tip_boost_per_item`?");
|
||||
|
||||
let tip = tip_boost_per_item.saturating_mul((n_items - 1).unique_saturated_into());
|
||||
let priority_with_tip = estimate_priority(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 {param_name} value ({}) must be fixed to: {}",
|
||||
priority_boost_per_item,
|
||||
compute_priority_boost_per_item(
|
||||
max_items,
|
||||
tip_boost_per_item,
|
||||
estimate_priority
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute priority boost that we give to bridge transaction for every
|
||||
/// additional bridge item.
|
||||
#[cfg(feature = "integrity-test")]
|
||||
fn compute_priority_boost_per_item<Balance>(
|
||||
max_items: ItemCount,
|
||||
tip_boost_per_item: Balance,
|
||||
estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
ItemCount: UniqueSaturatedInto<Balance>,
|
||||
Balance: FixedPointOperand + Zero,
|
||||
{
|
||||
// estimate priority of transaction that delivers one item and has large tip
|
||||
let small_with_tip_priority =
|
||||
estimate_priority(1, tip_boost_per_item.saturating_mul(max_items.saturated_into()));
|
||||
// estimate priority of transaction that delivers maximal number of items, but has no tip
|
||||
let large_without_tip_priority = estimate_priority(max_items, Zero::zero());
|
||||
|
||||
small_with_tip_priority
|
||||
.saturating_sub(large_without_tip_priority)
|
||||
.saturating_div(max_items - 1)
|
||||
}
|
||||
|
||||
/// Computations, specific to bridge relay chains transactions.
|
||||
pub mod per_relay_header {
|
||||
use super::*;
|
||||
|
||||
use bp_header_pez_chain::{
|
||||
max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa,
|
||||
};
|
||||
use pezpallet_bridge_grandpa::WeightInfoExt;
|
||||
|
||||
/// Ensures that the value of `PriorityBoostPerHeader` matches the value of
|
||||
/// `tip_boost_per_header`.
|
||||
///
|
||||
/// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have
|
||||
/// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want
|
||||
/// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority
|
||||
/// will be close to `TX2` as well.
|
||||
pub fn ensure_priority_boost_is_sane<Runtime, GrandpaInstance, PriorityBoostPerHeader>(
|
||||
tip_boost_per_header: BalanceOf<Runtime>,
|
||||
) where
|
||||
Runtime:
|
||||
pezpallet_transaction_payment::Config + pezpallet_bridge_grandpa::Config<GrandpaInstance>,
|
||||
GrandpaInstance: 'static,
|
||||
PriorityBoostPerHeader: Get<TransactionPriority>,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// the meaning of `max_items` here is different when comparing with message
|
||||
// transactions - with messages we have a strict limit on maximal number of
|
||||
// messages we can fit into a single transaction. With headers, current best
|
||||
// header may be improved by any "number of items". But this number is only
|
||||
// used to verify priority boost, so it should be fine to select this arbitrary
|
||||
// value - it SHALL NOT affect any value, it just adds more tests for the value.
|
||||
let maximal_improved_by = 4_096;
|
||||
super::ensure_priority_boost_is_sane::<PriorityBoostPerHeader, BalanceOf<Runtime>>(
|
||||
"PriorityBoostPerRelayHeader",
|
||||
maximal_improved_by,
|
||||
tip_boost_per_header,
|
||||
|_n_headers, tip| {
|
||||
estimate_relay_header_submit_transaction_priority::<Runtime, GrandpaInstance>(
|
||||
tip,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Estimate relay header delivery transaction priority.
|
||||
#[cfg(feature = "integrity-test")]
|
||||
fn estimate_relay_header_submit_transaction_priority<Runtime, GrandpaInstance>(
|
||||
tip: BalanceOf<Runtime>,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
Runtime:
|
||||
pezpallet_transaction_payment::Config + pezpallet_bridge_grandpa::Config<GrandpaInstance>,
|
||||
GrandpaInstance: 'static,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// just an estimation of extra transaction bytes that are added to every transaction
|
||||
// (including signature, signed extensions extra and etc + in our case it includes
|
||||
// all call arguments except the proof itself)
|
||||
let base_tx_size = 512;
|
||||
// let's say we are relaying largest relay chain headers
|
||||
let tx_call_size = max_expected_submit_finality_proof_arguments_size::<
|
||||
Runtime::BridgedChain,
|
||||
>(true, Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1);
|
||||
|
||||
// finally we are able to estimate transaction size and weight
|
||||
let transaction_size = base_tx_size.saturating_add(tx_call_size);
|
||||
let transaction_weight = <Runtime as ::pezpallet_bridge_grandpa::Config<
|
||||
GrandpaInstance,
|
||||
>>::WeightInfo::submit_finality_proof_weight(
|
||||
Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1,
|
||||
Runtime::BridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
|
||||
);
|
||||
|
||||
pezpallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
|
||||
&DispatchInfo {
|
||||
call_weight: transaction_weight,
|
||||
extension_weight: Default::default(),
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
},
|
||||
transaction_size as _,
|
||||
tip,
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Computations, specific to bridge teyrchains transactions.
|
||||
pub mod per_teyrchain_header {
|
||||
use super::*;
|
||||
|
||||
use pezbp_runtime::Teyrchain;
|
||||
use pezpallet_bridge_teyrchains::WeightInfoExt;
|
||||
|
||||
/// Ensures that the value of `PriorityBoostPerHeader` matches the value of
|
||||
/// `tip_boost_per_header`.
|
||||
///
|
||||
/// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have
|
||||
/// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want
|
||||
/// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority
|
||||
/// will be close to `TX2` as well.
|
||||
pub fn ensure_priority_boost_is_sane<
|
||||
Runtime,
|
||||
TeyrchainsInstance,
|
||||
Para,
|
||||
PriorityBoostPerHeader,
|
||||
>(
|
||||
tip_boost_per_header: BalanceOf<Runtime>,
|
||||
) where
|
||||
Runtime: pezpallet_transaction_payment::Config
|
||||
+ pezpallet_bridge_teyrchains::Config<TeyrchainsInstance>,
|
||||
TeyrchainsInstance: 'static,
|
||||
Para: Teyrchain,
|
||||
PriorityBoostPerHeader: Get<TransactionPriority>,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// the meaning of `max_items` here is different when comparing with message
|
||||
// transactions - with messages we have a strict limit on maximal number of
|
||||
// messages we can fit into a single transaction. With headers, current best
|
||||
// header may be improved by any "number of items". But this number is only
|
||||
// used to verify priority boost, so it should be fine to select this arbitrary
|
||||
// value - it SHALL NOT affect any value, it just adds more tests for the value.
|
||||
let maximal_improved_by = 4_096;
|
||||
super::ensure_priority_boost_is_sane::<PriorityBoostPerHeader, BalanceOf<Runtime>>(
|
||||
"PriorityBoostPerTeyrchainHeader",
|
||||
maximal_improved_by,
|
||||
tip_boost_per_header,
|
||||
|_n_headers, tip| {
|
||||
estimate_teyrchain_header_submit_transaction_priority::<
|
||||
Runtime,
|
||||
TeyrchainsInstance,
|
||||
Para,
|
||||
>(tip)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Estimate teyrchain header delivery transaction priority.
|
||||
#[cfg(feature = "integrity-test")]
|
||||
fn estimate_teyrchain_header_submit_transaction_priority<
|
||||
Runtime,
|
||||
TeyrchainsInstance,
|
||||
Para,
|
||||
>(
|
||||
tip: BalanceOf<Runtime>,
|
||||
) -> TransactionPriority
|
||||
where
|
||||
Runtime: pezpallet_transaction_payment::Config
|
||||
+ pezpallet_bridge_teyrchains::Config<TeyrchainsInstance>,
|
||||
TeyrchainsInstance: 'static,
|
||||
Para: Teyrchain,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// just an estimation of extra transaction bytes that are added to every transaction
|
||||
// (including signature, signed extensions extra and etc + in our case it includes
|
||||
// all call arguments except the proof itself)
|
||||
let base_tx_size = 512;
|
||||
// let's say we are relaying largest teyrchain headers and proof takes some more bytes
|
||||
let tx_call_size = <Runtime as pezpallet_bridge_teyrchains::Config<
|
||||
TeyrchainsInstance,
|
||||
>>::WeightInfo::expected_extra_storage_proof_size()
|
||||
.saturating_add(Para::MAX_HEADER_SIZE);
|
||||
|
||||
// finally we are able to estimate transaction size and weight
|
||||
let transaction_size = base_tx_size.saturating_add(tx_call_size);
|
||||
let transaction_weight = <Runtime as pezpallet_bridge_teyrchains::Config<
|
||||
TeyrchainsInstance,
|
||||
>>::WeightInfo::submit_teyrchain_heads_weight(
|
||||
Runtime::DbWeight::get(),
|
||||
&PreComputedSize(transaction_size as _),
|
||||
// just one teyrchain - all other submissions won't receive any boost
|
||||
1,
|
||||
);
|
||||
|
||||
pezpallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
|
||||
&DispatchInfo {
|
||||
call_weight: transaction_weight,
|
||||
extension_weight: Default::default(),
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
},
|
||||
transaction_size as _,
|
||||
tip,
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Computations, specific to bridge messages transactions.
|
||||
pub mod per_message {
|
||||
use super::*;
|
||||
|
||||
use bp_messages::ChainWithMessages;
|
||||
use pezpallet_bridge_messages::WeightInfoExt;
|
||||
|
||||
/// 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: pezpallet_transaction_payment::Config
|
||||
+ pezpallet_bridge_messages::Config<MessagesInstance>,
|
||||
MessagesInstance: 'static,
|
||||
PriorityBoostPerMessage: Get<TransactionPriority>,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
let maximal_messages_in_delivery_transaction =
|
||||
Runtime::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
|
||||
super::ensure_priority_boost_is_sane::<PriorityBoostPerMessage, BalanceOf<Runtime>>(
|
||||
"PriorityBoostPerMessage",
|
||||
maximal_messages_in_delivery_transaction,
|
||||
tip_boost_per_message,
|
||||
|n_messages, tip| {
|
||||
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(
|
||||
n_messages, tip,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 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: pezpallet_transaction_payment::Config
|
||||
+ pezpallet_bridge_messages::Config<MessagesInstance>,
|
||||
MessagesInstance: 'static,
|
||||
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
|
||||
{
|
||||
// just an estimation of extra transaction bytes that are added to every transaction
|
||||
// (including signature, signed extensions extra and etc + in our case it includes
|
||||
// all call arguments except the proof itself)
|
||||
let base_tx_size = 512;
|
||||
// let's say we are relaying similar small messages and for every message we add more
|
||||
// trie nodes to the proof (x0.5 because we expect some nodes to be reused)
|
||||
let estimated_message_size = 512;
|
||||
// let's say all our messages have the same dispatch weight
|
||||
let estimated_message_dispatch_weight = <Runtime as pezpallet_bridge_messages::Config<
|
||||
MessagesInstance,
|
||||
>>::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 as pezpallet_bridge_messages::Config<
|
||||
MessagesInstance,
|
||||
>>::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 as pezpallet_bridge_messages::Config<
|
||||
MessagesInstance,
|
||||
>>::WeightInfo::receive_messages_proof_weight(
|
||||
&PreComputedSize(transaction_size as _),
|
||||
messages as _,
|
||||
estimated_message_dispatch_weight.saturating_mul(messages),
|
||||
);
|
||||
|
||||
pezpallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
|
||||
&DispatchInfo {
|
||||
call_weight: transaction_weight,
|
||||
extension_weight: Default::default(),
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
},
|
||||
transaction_size as _,
|
||||
tip,
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Adapter that allows using `pezpallet-bridge-relayers` as a signed extension in the
|
||||
//! bridge with remote teyrchain.
|
||||
|
||||
use crate::{
|
||||
extension::{
|
||||
grandpa_adapter::verify_submit_finality_proof_succeeded, verify_messages_call_succeeded,
|
||||
},
|
||||
Config as BridgeRelayersConfig, LOG_TARGET,
|
||||
};
|
||||
|
||||
use bp_relayers::{BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
|
||||
use pezbp_runtime::{StaticStrProvider, Teyrchain};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::dispatch::{DispatchInfo, PostDispatchInfo};
|
||||
use pezframe_system::Config as SystemConfig;
|
||||
use pezpallet_bridge_grandpa::{
|
||||
CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig,
|
||||
};
|
||||
use pezpallet_bridge_messages::{
|
||||
CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf,
|
||||
};
|
||||
use pezpallet_bridge_teyrchains::{
|
||||
CallSubType as BridgeTeyrchainsCallSubtype, Config as BridgeTeyrchainsConfig,
|
||||
SubmitTeyrchainHeadsHelper,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{Dispatchable, Get},
|
||||
transaction_validity::{TransactionPriority, TransactionValidityError},
|
||||
};
|
||||
|
||||
/// Adapter to be used in signed extension configuration, when bridging with remote teyrchains.
|
||||
pub struct WithTeyrchainExtensionConfig<
|
||||
// signed extension identifier
|
||||
IdProvider,
|
||||
// runtime that implements `BridgeMessagesConfig<BridgeMessagesPalletInstance>`, which
|
||||
// uses `BridgeTeyrchainsConfig<BridgeTeyrchainsPalletInstance>` to receive messages and
|
||||
// confirmations from the remote chain.
|
||||
Runtime,
|
||||
// batch call unpacker
|
||||
BatchCallUnpacker,
|
||||
// instance of the `pezpallet-bridge-teyrchains`, tracked by this extension
|
||||
BridgeTeyrchainsPalletInstance,
|
||||
// instance of BridgedChain `pezpallet-bridge-messages`, tracked by this extension
|
||||
BridgeMessagesPalletInstance,
|
||||
// instance of `pezpallet-bridge-relayers`, tracked by this extension
|
||||
BridgeRelayersPalletInstance,
|
||||
// message delivery transaction priority boost for every additional message
|
||||
PriorityBoostPerMessage,
|
||||
>(
|
||||
PhantomData<(
|
||||
IdProvider,
|
||||
Runtime,
|
||||
BatchCallUnpacker,
|
||||
BridgeTeyrchainsPalletInstance,
|
||||
BridgeMessagesPalletInstance,
|
||||
BridgeRelayersPalletInstance,
|
||||
PriorityBoostPerMessage,
|
||||
)>,
|
||||
);
|
||||
|
||||
impl<ID, R, BCU, PI, MI, RI, P> ExtensionConfig
|
||||
for WithTeyrchainExtensionConfig<ID, R, BCU, PI, MI, RI, P>
|
||||
where
|
||||
ID: StaticStrProvider,
|
||||
R: BridgeRelayersConfig<RI>
|
||||
+ BridgeMessagesConfig<MI>
|
||||
+ BridgeTeyrchainsConfig<PI>
|
||||
+ BridgeGrandpaConfig<R::BridgesGrandpaPalletInstance>,
|
||||
BCU: BatchCallUnpacker<R>,
|
||||
PI: 'static,
|
||||
MI: 'static,
|
||||
RI: 'static,
|
||||
P: Get<TransactionPriority>,
|
||||
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
|
||||
+ BridgeGrandpaCallSubtype<R, R::BridgesGrandpaPalletInstance>
|
||||
+ BridgeTeyrchainsCallSubtype<R, PI>
|
||||
+ BridgeMessagesCallSubType<R, MI>,
|
||||
<R as BridgeMessagesConfig<MI>>::BridgedChain: Teyrchain,
|
||||
{
|
||||
type IdProvider = ID;
|
||||
type Runtime = R;
|
||||
type BridgeMessagesPalletInstance = MI;
|
||||
type BridgeRelayersPalletInstance = RI;
|
||||
type PriorityBoostPerMessage = P;
|
||||
type RemoteGrandpaChainBlockNumber =
|
||||
pezpallet_bridge_grandpa::BridgedBlockNumber<R, R::BridgesGrandpaPalletInstance>;
|
||||
type LaneId = LaneIdOf<R, Self::BridgeMessagesPalletInstance>;
|
||||
|
||||
fn parse_and_check_for_obsolete_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<
|
||||
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
|
||||
TransactionValidityError,
|
||||
> {
|
||||
let calls = BCU::unpack(call, 3);
|
||||
let total_calls = calls.len();
|
||||
let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
|
||||
|
||||
let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info());
|
||||
let para_finality_call = calls.next().transpose()?.and_then(|c| {
|
||||
let r = c.submit_teyrchain_heads_info_for(
|
||||
<R as BridgeMessagesConfig<Self::BridgeMessagesPalletInstance>>::BridgedChain::TEYRCHAIN_ID,
|
||||
);
|
||||
r
|
||||
});
|
||||
let relay_finality_call =
|
||||
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
|
||||
Ok(match (total_calls, relay_finality_call, para_finality_call, msgs_call) {
|
||||
(3, Some(relay_finality_call), Some(para_finality_call), Some(msgs_call)) =>
|
||||
Some(ExtensionCallInfo::AllFinalityAndMsgs(
|
||||
relay_finality_call,
|
||||
para_finality_call,
|
||||
msgs_call,
|
||||
)),
|
||||
(2, None, Some(para_finality_call), Some(msgs_call)) =>
|
||||
Some(ExtensionCallInfo::TeyrchainFinalityAndMsgs(para_finality_call, msgs_call)),
|
||||
(1, None, None, Some(msgs_call)) => Some(ExtensionCallInfo::Msgs(msgs_call)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn check_obsolete_parsed_call(
|
||||
call: &R::RuntimeCall,
|
||||
) -> Result<&R::RuntimeCall, TransactionValidityError> {
|
||||
call.check_obsolete_submit_finality_proof()?;
|
||||
call.check_obsolete_submit_teyrchain_heads()?;
|
||||
call.check_obsolete_call()?;
|
||||
Ok(call)
|
||||
}
|
||||
|
||||
fn check_call_result(
|
||||
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
|
||||
call_data: &mut ExtensionCallData,
|
||||
relayer: &R::AccountId,
|
||||
) -> bool {
|
||||
verify_submit_finality_proof_succeeded::<Self, R::BridgesGrandpaPalletInstance>(
|
||||
call_info, call_data, relayer,
|
||||
) && verify_submit_teyrchain_head_succeeded::<Self, PI>(call_info, call_data, relayer) &&
|
||||
verify_messages_call_succeeded::<Self>(call_info, call_data, relayer)
|
||||
}
|
||||
}
|
||||
|
||||
/// If the batch call contains the teyrchain state update call, verify that it
|
||||
/// has been successful.
|
||||
///
|
||||
/// Only returns false when teyrchain state update call has failed.
|
||||
pub(crate) fn verify_submit_teyrchain_head_succeeded<C, PI>(
|
||||
call_info: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber, C::LaneId>,
|
||||
_call_data: &mut ExtensionCallData,
|
||||
relayer: &<C::Runtime as SystemConfig>::AccountId,
|
||||
) -> bool
|
||||
where
|
||||
C: ExtensionConfig,
|
||||
PI: 'static,
|
||||
C::Runtime: BridgeTeyrchainsConfig<PI>,
|
||||
{
|
||||
let Some(para_proof_info) = call_info.submit_teyrchain_heads_info() else { return true };
|
||||
|
||||
if !SubmitTeyrchainHeadsHelper::<C::Runtime, PI>::was_successful(para_proof_info) {
|
||||
// we only refund relayer if all calls have updated chain state
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
id_provider=%C::IdProvider::STR,
|
||||
lane_id=?call_info.messages_call_info().lane_id(),
|
||||
?relayer,
|
||||
"Relayer has submitted invalid teyrchain finality proof"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,451 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A module that is responsible for migration of storage.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use pezframe_support::{
|
||||
traits::{Get, StorageVersion},
|
||||
weights::Weight,
|
||||
};
|
||||
|
||||
/// The in-code storage version.
|
||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
|
||||
|
||||
/// This module contains data structures that are valid for the initial state of `0`.
|
||||
/// (used with v1 migration).
|
||||
pub mod v0 {
|
||||
use crate::{Config, Pezpallet};
|
||||
use bp_relayers::RewardsAccountOwner;
|
||||
use pezbp_runtime::{ChainId, StorageDoubleMapKeyProvider};
|
||||
use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{pezpallet_prelude::OptionQuery, Blake2_128Concat, Identity};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::traits::AccountIdConversion;
|
||||
|
||||
/// Structure used to identify the account that pays a reward to the relayer.
|
||||
#[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)]
|
||||
pub struct RewardsAccountParams<LaneId> {
|
||||
/// lane_id
|
||||
pub lane_id: LaneId,
|
||||
/// bridged_chain_id
|
||||
pub bridged_chain_id: ChainId,
|
||||
/// owner
|
||||
pub owner: RewardsAccountOwner,
|
||||
}
|
||||
|
||||
impl<LaneId: Decode + Encode> RewardsAccountParams<LaneId> {
|
||||
/// Create a new instance of `RewardsAccountParams`.
|
||||
pub const fn new(
|
||||
lane_id: LaneId,
|
||||
bridged_chain_id: ChainId,
|
||||
owner: RewardsAccountOwner,
|
||||
) -> Self {
|
||||
Self { lane_id, bridged_chain_id, owner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<LaneId> pezsp_runtime::TypeId for RewardsAccountParams<LaneId> {
|
||||
const TYPE_ID: [u8; 4] = *b"brap";
|
||||
}
|
||||
|
||||
pub(crate) struct RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>(
|
||||
PhantomData<(AccountId, RewardBalance, LaneId)>,
|
||||
);
|
||||
|
||||
impl<AccountId, RewardBalance, LaneId> StorageDoubleMapKeyProvider
|
||||
for RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>
|
||||
where
|
||||
AccountId: 'static + Codec + EncodeLike + Send + Sync,
|
||||
RewardBalance: 'static + Codec + EncodeLike + Send + Sync,
|
||||
LaneId: Codec + EncodeLike + Send + Sync,
|
||||
{
|
||||
const MAP_NAME: &'static str = "RelayerRewards";
|
||||
|
||||
type Hasher1 = Blake2_128Concat;
|
||||
type Key1 = AccountId;
|
||||
type Hasher2 = Identity;
|
||||
type Key2 = RewardsAccountParams<LaneId>;
|
||||
type Value = RewardBalance;
|
||||
}
|
||||
|
||||
pub(crate) type RelayerRewardsKeyProviderOf<T, I, LaneId> = RelayerRewardsKeyProvider<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
<T as Config<I>>::RewardBalance,
|
||||
LaneId,
|
||||
>;
|
||||
|
||||
#[pezframe_support::storage_alias]
|
||||
pub(crate) type RelayerRewards<T: Config<I>, I: 'static, LaneId> = StorageDoubleMap<
|
||||
Pezpallet<T, I>,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher1,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key1,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher2,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key2,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Value,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
/// Reward account generator for `v0`.
|
||||
pub struct PayRewardFromAccount<Account, LaneId>(PhantomData<(Account, LaneId)>);
|
||||
impl<Account, LaneId> PayRewardFromAccount<Account, LaneId>
|
||||
where
|
||||
Account: Decode + Encode,
|
||||
LaneId: Decode + Encode,
|
||||
{
|
||||
/// Return account that pays rewards based on the provided parameters.
|
||||
pub fn rewards_account(params: RewardsAccountParams<LaneId>) -> Account {
|
||||
params.into_sub_account_truncating(b"rewards-account")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This migration updates `RelayerRewards` where `RewardsAccountParams` was used as the key with
|
||||
/// `lane_id` as the first attribute, which affects `into_sub_account_truncating`. We are migrating
|
||||
/// this key to use the new `RewardsAccountParams` where `lane_id` is the last attribute.
|
||||
pub mod v1 {
|
||||
use super::*;
|
||||
use crate::{Config, Pezpallet};
|
||||
use bp_messages::LaneIdType;
|
||||
use bp_relayers::RewardsAccountParams;
|
||||
use pezbp_runtime::StorageDoubleMapKeyProvider;
|
||||
use codec::{Codec, EncodeLike};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::OptionQuery, traits::UncheckedOnRuntimeUpgrade, Blake2_128Concat, Identity,
|
||||
};
|
||||
use pezsp_arithmetic::traits::Zero;
|
||||
|
||||
pub(crate) struct RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>(
|
||||
PhantomData<(AccountId, RewardBalance, LaneId)>,
|
||||
);
|
||||
|
||||
impl<AccountId, RewardBalance, LaneId> StorageDoubleMapKeyProvider
|
||||
for RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>
|
||||
where
|
||||
AccountId: 'static + Codec + EncodeLike + Send + Sync,
|
||||
RewardBalance: 'static + Codec + EncodeLike + Send + Sync,
|
||||
LaneId: Codec + EncodeLike + Send + Sync,
|
||||
{
|
||||
const MAP_NAME: &'static str = "RelayerRewards";
|
||||
|
||||
type Hasher1 = Blake2_128Concat;
|
||||
type Key1 = AccountId;
|
||||
type Hasher2 = Identity;
|
||||
type Key2 = v1::RewardsAccountParams<LaneId>;
|
||||
type Value = RewardBalance;
|
||||
}
|
||||
|
||||
pub(crate) type RelayerRewardsKeyProviderOf<T, I, LaneId> = RelayerRewardsKeyProvider<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
<T as Config<I>>::RewardBalance,
|
||||
LaneId,
|
||||
>;
|
||||
|
||||
#[pezframe_support::storage_alias]
|
||||
pub(crate) type RelayerRewards<T: Config<I>, I: 'static, LaneId> = StorageDoubleMap<
|
||||
Pezpallet<T, I>,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher1,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key1,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher2,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key2,
|
||||
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Value,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
// Copy of `Pezpallet::<T, I>::register_relayer_reward` compatible with v1.
|
||||
fn register_relayer_reward_for_v1<
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
LaneId: LaneIdType + Send + Sync,
|
||||
>(
|
||||
rewards_account_params: v1::RewardsAccountParams<LaneId>,
|
||||
relayer: &T::AccountId,
|
||||
reward_balance: T::RewardBalance,
|
||||
) {
|
||||
use pezsp_runtime::Saturating;
|
||||
|
||||
if reward_balance.is_zero() {
|
||||
return;
|
||||
}
|
||||
|
||||
v1::RelayerRewards::<T, I, LaneId>::mutate(
|
||||
relayer,
|
||||
rewards_account_params,
|
||||
|old_reward: &mut Option<T::RewardBalance>| {
|
||||
let new_reward =
|
||||
old_reward.unwrap_or_else(Zero::zero).saturating_add(reward_balance);
|
||||
*old_reward = Some(new_reward);
|
||||
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
?relayer,
|
||||
?rewards_account_params,
|
||||
?new_reward,
|
||||
"Relayer can now claim reward"
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Migrates the pezpallet storage to v1.
|
||||
pub struct UncheckedMigrationV0ToV1<T, I, LaneId>(PhantomData<(T, I, LaneId)>);
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
const LOG_TARGET: &str = "runtime::bridge-relayers-migration";
|
||||
|
||||
impl<T: Config<I>, I: 'static, LaneId: LaneIdType + Send + Sync> UncheckedOnRuntimeUpgrade
|
||||
for UncheckedMigrationV0ToV1<T, I, LaneId>
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
|
||||
// list all rewards (we cannot do this as one step because of `drain` limitation)
|
||||
let mut rewards_to_migrate =
|
||||
Vec::with_capacity(v0::RelayerRewards::<T, I, LaneId>::iter().count());
|
||||
for (key1, key2, reward) in v0::RelayerRewards::<T, I, LaneId>::drain() {
|
||||
rewards_to_migrate.push((key1, key2, reward));
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
}
|
||||
|
||||
// re-register rewards with new format of `RewardsAccountParams`.
|
||||
for (key1, key2, reward) in rewards_to_migrate {
|
||||
// expand old key
|
||||
let v0::RewardsAccountParams { owner, lane_id, bridged_chain_id } = key2;
|
||||
|
||||
// re-register reward
|
||||
register_relayer_reward_for_v1::<T, I, LaneId>(
|
||||
v1::RewardsAccountParams::new(lane_id, bridged_chain_id, owner),
|
||||
&key1,
|
||||
reward,
|
||||
);
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
}
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::DispatchError> {
|
||||
use codec::Encode;
|
||||
use pezframe_support::BoundedBTreeMap;
|
||||
use pezsp_runtime::traits::ConstU32;
|
||||
|
||||
// collect actual rewards
|
||||
let mut rewards: BoundedBTreeMap<
|
||||
(T::AccountId, LaneId),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = BoundedBTreeMap::new();
|
||||
for (key1, key2, reward) in v0::RelayerRewards::<T, I, LaneId>::iter() {
|
||||
tracing::info!(target: LOG_TARGET, ?key1, ?key2, ?reward, "Reward to migrate");
|
||||
rewards = rewards
|
||||
.try_mutate(|inner| {
|
||||
inner
|
||||
.entry((key1.clone(), key2.lane_id))
|
||||
.and_modify(|value| *value += reward)
|
||||
.or_insert(reward);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
tracing::info!(target: LOG_TARGET, ?rewards, "Found total rewards to migrate");
|
||||
|
||||
Ok(rewards.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
|
||||
use codec::Decode;
|
||||
use pezframe_support::BoundedBTreeMap;
|
||||
use pezsp_runtime::traits::ConstU32;
|
||||
|
||||
let rewards_before: BoundedBTreeMap<
|
||||
(T::AccountId, LaneId),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = Decode::decode(&mut &state[..]).unwrap();
|
||||
|
||||
// collect migrated rewards
|
||||
let mut rewards_after: BoundedBTreeMap<
|
||||
(T::AccountId, LaneId),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = BoundedBTreeMap::new();
|
||||
for (key1, key2, reward) in v1::RelayerRewards::<T, I, LaneId>::iter() {
|
||||
tracing::info!(target: LOG_TARGET, ?key1, ?key2, ?reward, "Migrated rewards");
|
||||
rewards_after = rewards_after
|
||||
.try_mutate(|inner| {
|
||||
inner
|
||||
.entry((key1.clone(), *key2.lane_id()))
|
||||
.and_modify(|value| *value += reward)
|
||||
.or_insert(reward);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
tracing::info!(target: LOG_TARGET, ?rewards_after, "Found total migrated rewards");
|
||||
|
||||
pezframe_support::ensure!(
|
||||
rewards_before == rewards_after,
|
||||
"The rewards were not migrated correctly!."
|
||||
);
|
||||
|
||||
tracing::info!(target: LOG_TARGET, "migrated all.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`UncheckedMigrationV0ToV1`] wrapped in a
|
||||
/// [`VersionedMigration`](pezframe_support::migrations::VersionedMigration), ensuring the
|
||||
/// migration is only performed when on-chain version is 0.
|
||||
pub type MigrationToV1<T, I, LaneId> = pezframe_support::migrations::VersionedMigration<
|
||||
0,
|
||||
1,
|
||||
UncheckedMigrationV0ToV1<T, I, LaneId>,
|
||||
Pezpallet<T, I>,
|
||||
<T as pezframe_system::Config>::DbWeight,
|
||||
>;
|
||||
}
|
||||
|
||||
/// The pezpallet in version 1 only supported rewards collected under the key of
|
||||
/// `RewardsAccountParams`. This migration essentially converts existing `RewardsAccountParams` keys
|
||||
/// to the generic type `T::Reward`.
|
||||
pub mod v2 {
|
||||
use super::*;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use crate::RelayerRewards;
|
||||
use crate::{Config, Pezpallet};
|
||||
use bp_messages::LaneIdType;
|
||||
use bp_relayers::RewardsAccountParams;
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::traits::UncheckedOnRuntimeUpgrade;
|
||||
|
||||
/// Migrates the pezpallet storage to v2.
|
||||
pub struct UncheckedMigrationV1ToV2<T, I, LaneId>(PhantomData<(T, I, LaneId)>);
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
const LOG_TARGET: &str = "runtime::bridge-relayers-migration";
|
||||
|
||||
impl<T: Config<I>, I: 'static, LaneId: LaneIdType + Send + Sync> UncheckedOnRuntimeUpgrade
|
||||
for UncheckedMigrationV1ToV2<T, I, LaneId>
|
||||
where
|
||||
<T as Config<I>>::Reward: From<RewardsAccountParams<LaneId>>,
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
|
||||
// list all rewards (we cannot do this as one step because of `drain` limitation)
|
||||
let mut rewards_to_migrate =
|
||||
Vec::with_capacity(v1::RelayerRewards::<T, I, LaneId>::iter().count());
|
||||
for (key1, key2, reward) in v1::RelayerRewards::<T, I, LaneId>::drain() {
|
||||
rewards_to_migrate.push((key1, key2, reward));
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
}
|
||||
|
||||
// re-register rewards with new format.
|
||||
for (key1, key2, reward) in rewards_to_migrate {
|
||||
// convert old key to the new
|
||||
let new_key2: T::Reward = key2.into();
|
||||
|
||||
// re-register reward (drained above)
|
||||
Pezpallet::<T, I>::register_relayer_reward(new_key2, &key1, reward);
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
}
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::DispatchError> {
|
||||
use codec::Encode;
|
||||
use pezframe_support::BoundedBTreeMap;
|
||||
use pezsp_runtime::traits::ConstU32;
|
||||
|
||||
// collect actual rewards
|
||||
let mut rewards: BoundedBTreeMap<
|
||||
(T::AccountId, Vec<u8>),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = BoundedBTreeMap::new();
|
||||
for (key1, key2, reward) in v1::RelayerRewards::<T, I, LaneId>::iter() {
|
||||
let new_key2: T::Reward = key2.into();
|
||||
tracing::info!(target: LOG_TARGET, ?key1, ?key2, ?new_key2, ?reward, "Reward to migrate");
|
||||
rewards = rewards
|
||||
.try_mutate(|inner| {
|
||||
inner
|
||||
.entry((key1.clone(), new_key2.encode()))
|
||||
.and_modify(|value| *value += reward)
|
||||
.or_insert(reward);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
tracing::info!(target: LOG_TARGET, ?rewards, "Found total rewards to migrate");
|
||||
|
||||
Ok(rewards.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::BoundedBTreeMap;
|
||||
use pezsp_runtime::traits::ConstU32;
|
||||
|
||||
let rewards_before: BoundedBTreeMap<
|
||||
(T::AccountId, Vec<u8>),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = Decode::decode(&mut &state[..]).unwrap();
|
||||
|
||||
// collect migrated rewards
|
||||
let mut rewards_after: BoundedBTreeMap<
|
||||
(T::AccountId, Vec<u8>),
|
||||
T::RewardBalance,
|
||||
ConstU32<{ u32::MAX }>,
|
||||
> = BoundedBTreeMap::new();
|
||||
for (key1, key2, reward) in v2::RelayerRewards::<T, I>::iter() {
|
||||
tracing::info!(target: LOG_TARGET, ?key1, ?key2, ?reward, "Migrated rewards");
|
||||
rewards_after = rewards_after
|
||||
.try_mutate(|inner| {
|
||||
inner
|
||||
.entry((key1.clone(), key2.encode()))
|
||||
.and_modify(|value| *value += reward)
|
||||
.or_insert(reward);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
tracing::info!(target: LOG_TARGET, ?rewards_after, "Found total migrated rewards");
|
||||
|
||||
pezframe_support::ensure!(
|
||||
rewards_before == rewards_after,
|
||||
"The rewards were not migrated correctly!."
|
||||
);
|
||||
|
||||
tracing::info!(target: LOG_TARGET, "migrated all.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// [`UncheckedMigrationV1ToV2`] wrapped in a
|
||||
/// [`VersionedMigration`](pezframe_support::migrations::VersionedMigration), ensuring the
|
||||
/// migration is only performed when on-chain version is 1.
|
||||
pub type MigrationToV2<T, I, LaneId> = pezframe_support::migrations::VersionedMigration<
|
||||
1,
|
||||
2,
|
||||
UncheckedMigrationV1ToV2<T, I, LaneId>,
|
||||
Pezpallet<T, I>,
|
||||
<T as pezframe_system::Config>::DbWeight,
|
||||
>;
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pezpallet_bridge_relayers;
|
||||
|
||||
use bp_header_pez_chain::ChainWithGrandpa;
|
||||
use bp_messages::{
|
||||
target_chain::{DispatchMessage, MessageDispatch},
|
||||
ChainWithMessages, HashedLaneId, LaneIdType, MessageNonce,
|
||||
};
|
||||
use bp_relayers::{
|
||||
PayRewardFromAccount, PaymentProcedure, RewardsAccountOwner, RewardsAccountParams,
|
||||
};
|
||||
use pezbp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Teyrchain};
|
||||
use bp_teyrchains::SingleParaStoredHeaderDataBuilder;
|
||||
use codec::Encode;
|
||||
use pezframe_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::fungible::Mutate,
|
||||
weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight},
|
||||
};
|
||||
use pezpallet_transaction_payment::Multiplier;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{
|
||||
traits::{BlakeTwo256, ConstU32, ConstU64, ConstU8},
|
||||
BuildStorage, FixedPointNumber, Perquintill, StateVersion,
|
||||
};
|
||||
|
||||
/// 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;
|
||||
/// Header of `ThisChain`.
|
||||
pub type ThisChainHeader = pezsp_runtime::generic::Header<ThisChainBlockNumber, ThisChainHasher>;
|
||||
/// Block of `ThisChain`.
|
||||
pub type ThisChainBlock = pezframe_system::mocking::MockBlockU32<TestRuntime>;
|
||||
|
||||
/// Account identifier at the `BridgedChain`.
|
||||
pub type BridgedChainAccountId = u128;
|
||||
/// Balance at the `BridgedChain`.
|
||||
pub type BridgedChainBalance = u128;
|
||||
/// Block number at the `BridgedChain`.
|
||||
pub type BridgedChainBlockNumber = u32;
|
||||
/// Hash at the `BridgedChain`.
|
||||
pub type BridgedChainHash = H256;
|
||||
/// Hasher at the `BridgedChain`.
|
||||
pub type BridgedChainHasher = BlakeTwo256;
|
||||
/// Header of the `BridgedChain`.
|
||||
pub type BridgedChainHeader =
|
||||
pezsp_runtime::generic::Header<BridgedChainBlockNumber, BridgedChainHasher>;
|
||||
|
||||
/// Bridged chain id used in tests.
|
||||
pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg";
|
||||
/// Maximal extrinsic size at the `BridgedChain`.
|
||||
pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024;
|
||||
|
||||
/// Lane identifier type used for tests.
|
||||
pub type TestLaneIdType = HashedLaneId;
|
||||
/// Lane that we're using in tests.
|
||||
pub fn test_lane_id() -> TestLaneIdType {
|
||||
TestLaneIdType::try_new(1, 2).unwrap()
|
||||
}
|
||||
/// Reward measurement type.
|
||||
pub type RewardBalance = u64;
|
||||
|
||||
/// Underlying chain of `ThisChain`.
|
||||
pub struct ThisUnderlyingChain;
|
||||
|
||||
impl Chain for ThisUnderlyingChain {
|
||||
const ID: ChainId = *b"tuch";
|
||||
|
||||
type BlockNumber = ThisChainBlockNumber;
|
||||
type Hash = ThisChainHash;
|
||||
type Hasher = ThisChainHasher;
|
||||
type Header = ThisChainHeader;
|
||||
type AccountId = ThisChainAccountId;
|
||||
type Balance = ThisChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for ThisUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
|
||||
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
|
||||
}
|
||||
|
||||
/// Underlying chain of `BridgedChain`.
|
||||
pub struct BridgedUnderlyingTeyrchain;
|
||||
|
||||
impl Chain for BridgedUnderlyingTeyrchain {
|
||||
const ID: ChainId = TEST_BRIDGED_CHAIN_ID;
|
||||
|
||||
type BlockNumber = BridgedChainBlockNumber;
|
||||
type Hash = BridgedChainHash;
|
||||
type Hasher = BridgedChainHasher;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = BridgedChainAccountId;
|
||||
type Balance = BridgedChainBalance;
|
||||
type Nonce = u32;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for BridgedUnderlyingTeyrchain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = 16;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 64;
|
||||
}
|
||||
|
||||
impl ChainWithMessages for BridgedUnderlyingTeyrchain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
|
||||
}
|
||||
|
||||
impl Teyrchain for BridgedUnderlyingTeyrchain {
|
||||
const TEYRCHAIN_ID: u32 = 42;
|
||||
const MAX_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
pub type TestStakeAndSlash = pezpallet_bridge_relayers::StakeAndSlashNamed<
|
||||
ThisChainAccountId,
|
||||
ThisChainBlockNumber,
|
||||
Balances,
|
||||
ReserveId,
|
||||
Stake,
|
||||
Lease,
|
||||
>;
|
||||
|
||||
pezframe_support::construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system,
|
||||
Utility: pezpallet_utility,
|
||||
Balances: pezpallet_balances,
|
||||
TransactionPayment: pezpallet_transaction_payment,
|
||||
BridgeRelayers: pezpallet_bridge_relayers,
|
||||
BridgeGrandpa: pezpallet_bridge_grandpa,
|
||||
BridgeTeyrchains: pezpallet_bridge_teyrchains,
|
||||
BridgeMessages: pezpallet_bridge_messages,
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BridgedParasPalletName: &'static str = "Paras";
|
||||
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
|
||||
pub const ExistentialDeposit: ThisChainBalance = 1;
|
||||
pub const ReserveId: [u8; 8] = *b"brdgrlrs";
|
||||
pub const Stake: ThisChainBalance = 1_000;
|
||||
pub const Lease: ThisChainBlockNumber = 8;
|
||||
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 = pezsp_runtime::traits::Bounded::max_value();
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = ThisChainBlock;
|
||||
// TODO: remove when https://github.com/pezkuwichain/kurdistan-sdk/issues/120 merged
|
||||
type BlockHashCount = ConstU32<10>;
|
||||
type AccountData = pezpallet_balances::AccountData<ThisChainBalance>;
|
||||
type DbWeight = DbWeight;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for TestRuntime {
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_utility::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type PalletsOrigin = OriginCaller;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_transaction_payment::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_transaction_payment::Config for TestRuntime {
|
||||
type OnChargeTransaction = pezpallet_transaction_payment::FungibleAdapter<Balances, ()>;
|
||||
type OperationalFeeMultiplier = ConstU8<5>;
|
||||
type WeightToFee = IdentityFee<ThisChainBalance>;
|
||||
type LengthToFee = ConstantMultiplier<ThisChainBalance, TransactionByteFee>;
|
||||
type FeeMultiplierUpdate = pezpallet_transaction_payment::TargetedFeeAdjustment<
|
||||
TestRuntime,
|
||||
TargetBlockFullness,
|
||||
AdjustmentVariable,
|
||||
MinimumMultiplier,
|
||||
MaximumMultiplier,
|
||||
>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_grandpa::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = BridgedUnderlyingTeyrchain;
|
||||
type MaxFreeHeadersPerBlock = ConstU32<4>;
|
||||
type FreeHeadersInterval = ConstU32<1_024>;
|
||||
type HeadersToKeep = ConstU32<8>;
|
||||
type WeightInfo = pezpallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_teyrchains::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgesGrandpaPalletInstance = ();
|
||||
type ParasPalletName = BridgedParasPalletName;
|
||||
type ParaStoredHeaderDataBuilder =
|
||||
SingleParaStoredHeaderDataBuilder<BridgedUnderlyingTeyrchain>;
|
||||
type HeadsToKeep = ConstU32<8>;
|
||||
type MaxParaHeadDataSize = ConstU32<1024>;
|
||||
type WeightInfo = pezpallet_bridge_teyrchains::weights::BridgeWeight<TestRuntime>;
|
||||
type OnNewHead = ();
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_messages::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = pezpallet_bridge_messages::weights::BridgeWeight<TestRuntime>;
|
||||
|
||||
type OutboundPayload = Vec<u8>;
|
||||
type InboundPayload = Vec<u8>;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
type DeliveryPayments = ();
|
||||
type DeliveryConfirmationPayments = pezpallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter<
|
||||
TestRuntime,
|
||||
(),
|
||||
(),
|
||||
ConstU64<100_000>,
|
||||
>;
|
||||
type OnMessagesDelivered = ();
|
||||
|
||||
type MessageDispatch = DummyMessageDispatch;
|
||||
type ThisChain = ThisUnderlyingChain;
|
||||
type BridgedChain = BridgedUnderlyingTeyrchain;
|
||||
type BridgedHeaderChain = BridgeGrandpa;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_relayers::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RewardBalance = RewardBalance;
|
||||
type Reward = RewardsAccountParams<pezpallet_bridge_messages::LaneIdOf<TestRuntime, ()>>;
|
||||
type PaymentProcedure = TestPaymentProcedure;
|
||||
type StakeAndSlash = TestStakeAndSlash;
|
||||
type Balance = ThisChainBalance;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl pezpallet_bridge_relayers::benchmarking::Config for TestRuntime {
|
||||
fn bench_reward() -> Self::Reward {
|
||||
RewardsAccountParams::new(
|
||||
TestLaneIdType::default(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare_rewards_account(
|
||||
account_params: RewardsAccountParams<TestLaneIdType>,
|
||||
reward: Self::RewardBalance,
|
||||
) -> Option<ThisChainAccountId> {
|
||||
let rewards_account = PayRewardFromAccount::<
|
||||
Balances,
|
||||
ThisChainAccountId,
|
||||
TestLaneIdType,
|
||||
RewardBalance,
|
||||
>::rewards_account(account_params);
|
||||
Self::deposit_account(rewards_account, reward.into());
|
||||
|
||||
Some(REGULAR_RELAYER2)
|
||||
}
|
||||
|
||||
fn deposit_account(account: Self::AccountId, balance: Self::Balance) {
|
||||
pezframe_support::assert_ok!(Balances::mint_into(
|
||||
&account,
|
||||
balance.saturating_add(ExistentialDeposit::get())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Regular relayer that may receive rewards.
|
||||
pub const REGULAR_RELAYER: ThisChainAccountId = 1;
|
||||
/// Regular relayer that may receive rewards.
|
||||
pub const REGULAR_RELAYER2: ThisChainAccountId = 3;
|
||||
|
||||
/// Relayer that can't receive rewards.
|
||||
pub const FAILING_RELAYER: ThisChainAccountId = 2;
|
||||
|
||||
/// Relayer that is able to register.
|
||||
pub const REGISTER_RELAYER: ThisChainAccountId = 42;
|
||||
|
||||
/// Payment procedure that rejects payments to the `FAILING_RELAYER`.
|
||||
pub struct TestPaymentProcedure;
|
||||
|
||||
impl TestPaymentProcedure {
|
||||
pub fn rewards_account(params: RewardsAccountParams<TestLaneIdType>) -> ThisChainAccountId {
|
||||
PayRewardFromAccount::<(), ThisChainAccountId, TestLaneIdType, RewardBalance>::rewards_account(
|
||||
params,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PaymentProcedure<ThisChainAccountId, RewardsAccountParams<TestLaneIdType>, RewardBalance>
|
||||
for TestPaymentProcedure
|
||||
{
|
||||
type Error = ();
|
||||
type Beneficiary = ThisChainAccountId;
|
||||
|
||||
fn pay_reward(
|
||||
relayer: &ThisChainAccountId,
|
||||
_reward_kind: RewardsAccountParams<TestLaneIdType>,
|
||||
_reward: RewardBalance,
|
||||
_beneficiary: Self::Beneficiary,
|
||||
) -> Result<(), Self::Error> {
|
||||
match *relayer {
|
||||
FAILING_RELAYER => Err(()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy message dispatcher.
|
||||
pub struct DummyMessageDispatch;
|
||||
|
||||
impl DummyMessageDispatch {
|
||||
pub fn deactivate(lane: TestLaneIdType) {
|
||||
pezframe_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false);
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDispatch for DummyMessageDispatch {
|
||||
type DispatchPayload = Vec<u8>;
|
||||
type DispatchLevelResult = ();
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
pezframe_support::storage::unhashed::take::<bool>(&(b"inactive", lane).encode()[..]) !=
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
_message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
_: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
|
||||
}
|
||||
}
|
||||
|
||||
/// Reward account params that we are using in tests.
|
||||
pub fn test_reward_account_param() -> RewardsAccountParams<TestLaneIdType> {
|
||||
RewardsAccountParams::new(
|
||||
TestLaneIdType::try_new(1, 2).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
|
||||
pezsp_io::TestExternalities::new(t)
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
Balances::mint_into(®ISTER_RELAYER, ExistentialDeposit::get() + 10 * Stake::get())
|
||||
.unwrap();
|
||||
|
||||
test()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Code that allows relayers pezpallet to be used as a payment mechanism for
|
||||
//! the `pezpallet-bridge-messages` pezpallet using `RewardsAccountParams`.
|
||||
|
||||
use crate::{Config, Pezpallet};
|
||||
|
||||
use alloc::collections::vec_deque::VecDeque;
|
||||
use bp_messages::{
|
||||
source_chain::{DeliveryConfirmationPayments, RelayersRewards},
|
||||
MessageNonce,
|
||||
};
|
||||
pub use bp_relayers::PayRewardFromAccount;
|
||||
use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
|
||||
use pezbp_runtime::Chain;
|
||||
use core::{marker::PhantomData, ops::RangeInclusive};
|
||||
use pezframe_support::{pezsp_runtime::SaturatedConversion, traits::Get};
|
||||
use pezpallet_bridge_messages::LaneIdOf;
|
||||
use pezsp_arithmetic::traits::{Saturating, Zero};
|
||||
|
||||
/// Adapter that allows relayers pezpallet to be used as a delivery+dispatch payment mechanism
|
||||
/// for the `pezpallet-bridge-messages` pezpallet and using `RewardsAccountParams`.
|
||||
pub struct DeliveryConfirmationPaymentsAdapter<T, MI, RI, DeliveryReward>(
|
||||
PhantomData<(T, MI, RI, DeliveryReward)>,
|
||||
);
|
||||
|
||||
impl<T, MI, RI, DeliveryReward> DeliveryConfirmationPayments<T::AccountId, LaneIdOf<T, MI>>
|
||||
for DeliveryConfirmationPaymentsAdapter<T, MI, RI, DeliveryReward>
|
||||
where
|
||||
T: Config<RI> + pezpallet_bridge_messages::Config<MI>,
|
||||
MI: 'static,
|
||||
RI: 'static,
|
||||
DeliveryReward: Get<T::RewardBalance>,
|
||||
<T as Config<RI>>::Reward: From<RewardsAccountParams<LaneIdOf<T, MI>>>,
|
||||
{
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_reward(
|
||||
lane_id: LaneIdOf<T, MI>,
|
||||
pez_messages_relayers: VecDeque<bp_messages::UnrewardedRelayer<T::AccountId>>,
|
||||
confirmation_relayer: &T::AccountId,
|
||||
received_range: &RangeInclusive<bp_messages::MessageNonce>,
|
||||
) -> MessageNonce {
|
||||
let relayers_rewards =
|
||||
bp_messages::calc_relayers_rewards::<T::AccountId>(pez_messages_relayers, received_range);
|
||||
let rewarded_relayers = relayers_rewards.len();
|
||||
|
||||
register_relayers_rewards::<T, RI, MI>(
|
||||
confirmation_relayer,
|
||||
relayers_rewards,
|
||||
RewardsAccountParams::new(
|
||||
lane_id,
|
||||
T::BridgedChain::ID,
|
||||
RewardsAccountOwner::BridgedChain,
|
||||
),
|
||||
DeliveryReward::get(),
|
||||
);
|
||||
|
||||
rewarded_relayers as _
|
||||
}
|
||||
}
|
||||
|
||||
// Update rewards to given relayers, optionally rewarding confirmation relayer.
|
||||
fn register_relayers_rewards<
|
||||
T: Config<RI> + pezpallet_bridge_messages::Config<MI>,
|
||||
RI: 'static,
|
||||
MI: 'static,
|
||||
>(
|
||||
confirmation_relayer: &T::AccountId,
|
||||
relayers_rewards: RelayersRewards<T::AccountId>,
|
||||
lane_id: RewardsAccountParams<LaneIdOf<T, MI>>,
|
||||
delivery_fee: T::RewardBalance,
|
||||
) where
|
||||
<T as Config<RI>>::Reward: From<RewardsAccountParams<LaneIdOf<T, MI>>>,
|
||||
{
|
||||
// reward every relayer except `confirmation_relayer`
|
||||
let mut confirmation_relayer_reward = T::RewardBalance::zero();
|
||||
for (relayer, messages) in relayers_rewards {
|
||||
// sane runtime configurations guarantee that the number of messages will be below
|
||||
// `u32::MAX`
|
||||
let relayer_reward =
|
||||
T::RewardBalance::saturated_from(messages).saturating_mul(delivery_fee);
|
||||
|
||||
if relayer != *confirmation_relayer {
|
||||
Pezpallet::<T, RI>::register_relayer_reward(lane_id.into(), &relayer, relayer_reward);
|
||||
} else {
|
||||
confirmation_relayer_reward =
|
||||
confirmation_relayer_reward.saturating_add(relayer_reward);
|
||||
}
|
||||
}
|
||||
|
||||
// finally - pay reward to confirmation relayer
|
||||
Pezpallet::<T, RI>::register_relayer_reward(
|
||||
lane_id.into(),
|
||||
confirmation_relayer,
|
||||
confirmation_relayer_reward,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, RelayerRewards};
|
||||
use bp_messages::LaneIdType;
|
||||
use bp_relayers::PaymentProcedure;
|
||||
use pezframe_support::{
|
||||
assert_ok,
|
||||
traits::fungible::{Inspect, Mutate},
|
||||
};
|
||||
|
||||
const RELAYER_1: ThisChainAccountId = 1;
|
||||
const RELAYER_2: ThisChainAccountId = 2;
|
||||
const RELAYER_3: ThisChainAccountId = 3;
|
||||
|
||||
fn relayers_rewards() -> RelayersRewards<ThisChainAccountId> {
|
||||
vec![(RELAYER_1, 2), (RELAYER_2, 3)].into_iter().collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() {
|
||||
run_test(|| {
|
||||
register_relayers_rewards::<TestRuntime, (), ()>(
|
||||
&RELAYER_2,
|
||||
relayers_rewards(),
|
||||
test_reward_account_param(),
|
||||
50,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_1, test_reward_account_param()),
|
||||
Some(100)
|
||||
);
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_2, test_reward_account_param()),
|
||||
Some(150)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmation_relayer_is_not_rewarded_if_it_has_not_delivered_any_messages() {
|
||||
run_test(|| {
|
||||
register_relayers_rewards::<TestRuntime, (), ()>(
|
||||
&RELAYER_3,
|
||||
relayers_rewards(),
|
||||
test_reward_account_param(),
|
||||
50,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_1, test_reward_account_param()),
|
||||
Some(100)
|
||||
);
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_2, test_reward_account_param()),
|
||||
Some(150)
|
||||
);
|
||||
assert_eq!(
|
||||
RelayerRewards::<TestRuntime>::get(RELAYER_3, test_reward_account_param()),
|
||||
None
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pay_reward_from_account_actually_pays_reward() {
|
||||
type Balances = pezpallet_balances::Pezpallet<TestRuntime>;
|
||||
type PayLaneRewardFromAccount =
|
||||
PayRewardFromAccount<Balances, ThisChainAccountId, TestLaneIdType, RewardBalance>;
|
||||
|
||||
run_test(|| {
|
||||
let in_lane_0 = RewardsAccountParams::new(
|
||||
TestLaneIdType::try_new(1, 2).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
);
|
||||
let out_lane_1 = RewardsAccountParams::new(
|
||||
TestLaneIdType::try_new(1, 3).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::BridgedChain,
|
||||
);
|
||||
|
||||
let in_lane0_rewards_account = PayLaneRewardFromAccount::rewards_account(in_lane_0);
|
||||
let out_lane1_rewards_account = PayLaneRewardFromAccount::rewards_account(out_lane_1);
|
||||
|
||||
assert_ok!(Balances::mint_into(&in_lane0_rewards_account, 200));
|
||||
assert_ok!(Balances::mint_into(&out_lane1_rewards_account, 100));
|
||||
assert_eq!(Balances::balance(&in_lane0_rewards_account), 200);
|
||||
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
|
||||
assert_eq!(Balances::balance(&1), 0);
|
||||
assert_eq!(Balances::balance(&2), 0);
|
||||
|
||||
assert_ok!(PayLaneRewardFromAccount::pay_reward(&1, in_lane_0, 100, 1_u64));
|
||||
assert_eq!(Balances::balance(&in_lane0_rewards_account), 100);
|
||||
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
|
||||
assert_eq!(Balances::balance(&1), 100);
|
||||
assert_eq!(Balances::balance(&2), 0);
|
||||
|
||||
assert_ok!(PayLaneRewardFromAccount::pay_reward(&1, out_lane_1, 100, 1_u64));
|
||||
assert_eq!(Balances::balance(&in_lane0_rewards_account), 100);
|
||||
assert_eq!(Balances::balance(&out_lane1_rewards_account), 0);
|
||||
assert_eq!(Balances::balance(&1), 200);
|
||||
assert_eq!(Balances::balance(&2), 0);
|
||||
|
||||
assert_ok!(PayLaneRewardFromAccount::pay_reward(&1, in_lane_0, 100, 2_u64));
|
||||
assert_eq!(Balances::balance(&in_lane0_rewards_account), 0);
|
||||
assert_eq!(Balances::balance(&out_lane1_rewards_account), 0);
|
||||
assert_eq!(Balances::balance(&1), 200);
|
||||
assert_eq!(Balances::balance(&2), 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Code that allows `NamedReservableCurrency` to be used as a `StakeAndSlash`
|
||||
//! mechanism of the relayers pezpallet.
|
||||
|
||||
use bp_relayers::StakeAndSlash;
|
||||
use codec::Codec;
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use pezframe_support::traits::{tokens::BalanceStatus, NamedReservableCurrency};
|
||||
use pezsp_runtime::{traits::Get, DispatchError, DispatchResult};
|
||||
|
||||
/// `StakeAndSlash` that works with `NamedReservableCurrency` and uses named
|
||||
/// reservations.
|
||||
///
|
||||
/// **WARNING**: this implementation assumes that the relayers pezpallet is configured to
|
||||
/// use the [`bp_relayers::PayRewardFromAccount`] as its relayers payment scheme.
|
||||
pub struct StakeAndSlashNamed<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>(
|
||||
PhantomData<(AccountId, BlockNumber, Currency, ReserveId, Stake, Lease)>,
|
||||
);
|
||||
|
||||
impl<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>
|
||||
StakeAndSlash<AccountId, BlockNumber, Currency::Balance>
|
||||
for StakeAndSlashNamed<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>
|
||||
where
|
||||
AccountId: Codec + Debug,
|
||||
Currency: NamedReservableCurrency<AccountId>,
|
||||
ReserveId: Get<Currency::ReserveIdentifier>,
|
||||
Stake: Get<Currency::Balance>,
|
||||
Lease: Get<BlockNumber>,
|
||||
{
|
||||
type RequiredStake = Stake;
|
||||
type RequiredRegistrationLease = Lease;
|
||||
|
||||
fn reserve(relayer: &AccountId, amount: Currency::Balance) -> DispatchResult {
|
||||
Currency::reserve_named(&ReserveId::get(), relayer, amount)
|
||||
}
|
||||
|
||||
fn unreserve(relayer: &AccountId, amount: Currency::Balance) -> Currency::Balance {
|
||||
Currency::unreserve_named(&ReserveId::get(), relayer, amount)
|
||||
}
|
||||
|
||||
fn repatriate_reserved(
|
||||
relayer: &AccountId,
|
||||
beneficiary: &AccountId,
|
||||
amount: Currency::Balance,
|
||||
) -> Result<Currency::Balance, DispatchError> {
|
||||
Currency::repatriate_reserved_named(
|
||||
&ReserveId::get(),
|
||||
relayer,
|
||||
&beneficiary,
|
||||
amount,
|
||||
BalanceStatus::Free,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::*;
|
||||
use bp_relayers::ExplicitOrAccountParams;
|
||||
|
||||
use pezframe_support::traits::fungible::Mutate;
|
||||
use pezsp_runtime::traits::IdentifyAccount;
|
||||
|
||||
fn test_stake() -> ThisChainBalance {
|
||||
Stake::get()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserve_works() {
|
||||
run_test(|| {
|
||||
assert!(TestStakeAndSlash::reserve(&1, test_stake()).is_err());
|
||||
assert_eq!(Balances::free_balance(1), 0);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
|
||||
Balances::mint_into(&2, test_stake() - 1).unwrap();
|
||||
assert!(TestStakeAndSlash::reserve(&2, test_stake()).is_err());
|
||||
assert_eq!(Balances::free_balance(2), test_stake() - 1);
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
|
||||
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||
assert_eq!(TestStakeAndSlash::reserve(&3, test_stake()), Ok(()));
|
||||
assert_eq!(Balances::free_balance(3), test_stake());
|
||||
assert_eq!(Balances::reserved_balance(3), test_stake());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unreserve_works() {
|
||||
run_test(|| {
|
||||
assert_eq!(TestStakeAndSlash::unreserve(&1, test_stake()), test_stake());
|
||||
assert_eq!(Balances::free_balance(1), 0);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
|
||||
Balances::mint_into(&2, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap();
|
||||
assert_eq!(
|
||||
TestStakeAndSlash::unreserve(&2, test_stake()),
|
||||
test_stake() - test_stake() / 3
|
||||
);
|
||||
assert_eq!(Balances::free_balance(2), test_stake() * 2);
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
|
||||
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
|
||||
assert_eq!(TestStakeAndSlash::unreserve(&3, test_stake()), 0);
|
||||
assert_eq!(Balances::free_balance(3), test_stake() * 2);
|
||||
assert_eq!(Balances::reserved_balance(3), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repatriate_reserved_works() {
|
||||
run_test(|| {
|
||||
let beneficiary = test_reward_account_param();
|
||||
let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
|
||||
|
||||
let mut expected_balance = ExistentialDeposit::get();
|
||||
Balances::mint_into(&beneficiary_account, expected_balance).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
TestStakeAndSlash::repatriate_reserved(
|
||||
&1,
|
||||
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
|
||||
test_stake()
|
||||
),
|
||||
Ok(test_stake())
|
||||
);
|
||||
assert_eq!(Balances::free_balance(1), 0);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
|
||||
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||
|
||||
expected_balance += test_stake() / 3;
|
||||
Balances::mint_into(&2, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap();
|
||||
assert_eq!(
|
||||
TestStakeAndSlash::repatriate_reserved(
|
||||
&2,
|
||||
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
|
||||
test_stake()
|
||||
),
|
||||
Ok(test_stake() - test_stake() / 3)
|
||||
);
|
||||
assert_eq!(Balances::free_balance(2), test_stake() * 2 - test_stake() / 3);
|
||||
assert_eq!(Balances::reserved_balance(2), 0);
|
||||
assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
|
||||
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||
|
||||
expected_balance += test_stake();
|
||||
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
|
||||
assert_eq!(
|
||||
TestStakeAndSlash::repatriate_reserved(
|
||||
&3,
|
||||
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
|
||||
test_stake()
|
||||
),
|
||||
Ok(0)
|
||||
);
|
||||
assert_eq!(Balances::free_balance(3), test_stake());
|
||||
assert_eq!(Balances::reserved_balance(3), 0);
|
||||
assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
|
||||
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repatriate_reserved_doesnt_work_when_beneficiary_account_is_missing() {
|
||||
run_test(|| {
|
||||
let beneficiary = test_reward_account_param();
|
||||
let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
|
||||
|
||||
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
|
||||
assert!(TestStakeAndSlash::repatriate_reserved(
|
||||
&3,
|
||||
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
|
||||
test_stake()
|
||||
)
|
||||
.is_err());
|
||||
assert_eq!(Balances::free_balance(3), test_stake());
|
||||
assert_eq!(Balances::reserved_balance(3), test_stake());
|
||||
assert_eq!(Balances::free_balance(beneficiary_account), 0);
|
||||
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_bridge_relayers
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-04-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/rip-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_bridge_relayers
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/relayers/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
|
||||
/// Weight functions needed for pezpallet_bridge_relayers.
|
||||
pub trait WeightInfo {
|
||||
fn claim_rewards() -> Weight;
|
||||
fn claim_rewards_to() -> Weight;
|
||||
fn register() -> Weight;
|
||||
fn deregister() -> Weight;
|
||||
fn slash_and_deregister() -> Weight;
|
||||
fn register_relayer_reward() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_bridge_relayers` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances TotalIssuance (r:1 w:0)
|
||||
///
|
||||
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn claim_rewards() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `294`
|
||||
// Estimated: `8592`
|
||||
// Minimum execution time: 77_614 nanoseconds.
|
||||
Weight::from_parts(79_987_000, 8592)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances TotalIssuance (r:1 w:0)
|
||||
///
|
||||
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn claim_rewards_to() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `294`
|
||||
// Estimated: `8592`
|
||||
// Minimum execution time: 77_614 nanoseconds.
|
||||
Weight::from_parts(79_987_000, 8592)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn register() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `87`
|
||||
// Estimated: `7843`
|
||||
// Minimum execution time: 39_590 nanoseconds.
|
||||
Weight::from_parts(40_546_000, 7843)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn deregister() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `264`
|
||||
// Estimated: `7843`
|
||||
// Minimum execution time: 43_332 nanoseconds.
|
||||
Weight::from_parts(45_087_000, 7843)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn slash_and_deregister() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `380`
|
||||
// Estimated: `11412`
|
||||
// Minimum execution time: 42_358 nanoseconds.
|
||||
Weight::from_parts(43_539_000, 11412)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
fn register_relayer_reward() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `12`
|
||||
// Estimated: `3530`
|
||||
// Minimum execution time: 6_338 nanoseconds.
|
||||
Weight::from_parts(6_526_000, 3530)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances TotalIssuance (r:1 w:0)
|
||||
///
|
||||
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn claim_rewards() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `294`
|
||||
// Estimated: `8592`
|
||||
// Minimum execution time: 77_614 nanoseconds.
|
||||
Weight::from_parts(79_987_000, 8592)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances TotalIssuance (r:1 w:0)
|
||||
///
|
||||
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn claim_rewards_to() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `294`
|
||||
// Estimated: `8592`
|
||||
// Minimum execution time: 77_614 nanoseconds.
|
||||
Weight::from_parts(79_987_000, 8592)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn register() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `87`
|
||||
// Estimated: `7843`
|
||||
// Minimum execution time: 39_590 nanoseconds.
|
||||
Weight::from_parts(40_546_000, 7843)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn deregister() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `264`
|
||||
// Estimated: `7843`
|
||||
// Minimum execution time: 43_332 nanoseconds.
|
||||
Weight::from_parts(45_087_000, 7843)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
|
||||
/// mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: Balances Reserves (r:1 w:1)
|
||||
///
|
||||
/// Proof: Balances Reserves (max_values: None, max_size: Some(849), added: 3324, mode:
|
||||
/// MaxEncodedLen)
|
||||
///
|
||||
/// Storage: System Account (r:1 w:1)
|
||||
///
|
||||
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
|
||||
/// MaxEncodedLen)
|
||||
fn slash_and_deregister() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `380`
|
||||
// Estimated: `11412`
|
||||
// Minimum execution time: 42_358 nanoseconds.
|
||||
Weight::from_parts(43_539_000, 11412)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
|
||||
/// mode: MaxEncodedLen)
|
||||
fn register_relayer_reward() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `12`
|
||||
// Estimated: `3530`
|
||||
// Minimum execution time: 6_338 nanoseconds.
|
||||
Weight::from_parts(6_526_000, 3530)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Weight-related utilities.
|
||||
|
||||
use crate::weights::WeightInfo;
|
||||
|
||||
use pezframe_support::pezpallet_prelude::Weight;
|
||||
|
||||
/// Extended weight info.
|
||||
pub trait WeightInfoExt: WeightInfo {
|
||||
/// Returns weight, that needs to be added to the pre-dispatch weight of message delivery call,
|
||||
/// if `BridgeRelayersTransactionExtension` signed extension is deployed at runtime level.
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight {
|
||||
Self::slash_and_deregister().max(Self::register_relayer_reward())
|
||||
}
|
||||
|
||||
/// Returns weight, that needs to be added to the pre-dispatch weight of message delivery
|
||||
/// confirmation call, if `BridgeRelayersTransactionExtension` signed extension is deployed at
|
||||
/// runtime level.
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
|
||||
Self::register_relayer_reward()
|
||||
}
|
||||
|
||||
/// Returns weight that we need to deduct from the message delivery call weight that has
|
||||
/// completed successfully.
|
||||
///
|
||||
/// Usually, the weight of `slash_and_deregister` is larger than the weight of the
|
||||
/// `register_relayer_reward`. So if relayer has been rewarded, we want to deduct the difference
|
||||
/// to get the actual post-dispatch weight.
|
||||
fn extra_weight_of_successful_receive_messages_proof_call() -> Weight {
|
||||
Self::slash_and_deregister().saturating_sub(Self::register_relayer_reward())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WeightInfo> WeightInfoExt for T {}
|
||||
@@ -0,0 +1,75 @@
|
||||
[package]
|
||||
name = "pezpallet-bridge-teyrchains"
|
||||
version = "0.7.0"
|
||||
description = "Module that allows bridged relay chains to exchange information on their teyrchains' heads."
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-bridge-teyrchains"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-pezkuwi-core = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
bp-teyrchains = { workspace = true }
|
||||
pezpallet-bridge-grandpa = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-header-pez-chain = { workspace = true, default-features = true }
|
||||
bp-test-utils = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-pezkuwi-core/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-teyrchains/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-bridge-grandpa/std",
|
||||
"scale-info/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"bp-teyrchains/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-bridge-grandpa/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-bridge-grandpa/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,90 @@
|
||||
# Bridge Teyrchains Pezpallet
|
||||
|
||||
The bridge teyrchains pezpallet is a light client for one or several teyrchains of the bridged relay chain.
|
||||
It serves as a source of finalized teyrchain headers and is used when you need to build a bridge with
|
||||
a teyrchain.
|
||||
|
||||
The pezpallet requires [bridge GRANDPA pezpallet](../grandpa/) to be deployed at the same chain - it is used
|
||||
to verify storage proofs, generated at the bridged relay chain.
|
||||
|
||||
## A Brief Introduction into Teyrchains Finality
|
||||
|
||||
You can find detailed information on teyrchains finality in the
|
||||
[PezkuwiChain-SDK](https://github.com/pezkuwichain/pezkuwi-sdk) repository. This section gives a brief overview of how the
|
||||
teyrchain finality works and how to build a light client for a teyrchain.
|
||||
|
||||
The main thing there is that the teyrchain generates blocks on its own, but it can't achieve finality without
|
||||
help of its relay chain. Instead, the teyrchain collators create a block and hand it over to the relay chain
|
||||
validators. Validators validate the block and register the new teyrchain head in the
|
||||
[`Heads` map](https://github.com/pezkuwichain/pezkuwi-sdk/blob/bc5005217a8c2e7c95b9011c96d7e619879b1200/polkadot/runtime/parachains/src/paras/mod.rs#L683-L686)
|
||||
of the [`paras`](https://github.com/paritytech/polkadot-sdk/tree/master/polkadot/runtime/parachains/src/paras) pezpallet,
|
||||
deployed at the relay chain. Keep in mind that this pezpallet, deployed at a relay chain, is **NOT** a bridge pezpallet,
|
||||
even though the names are similar.
|
||||
|
||||
And what the bridge teyrchains pezpallet does, is simply verifying storage proofs of teyrchain heads within that
|
||||
`Heads` map. It does that using relay chain header, that has been previously imported by the
|
||||
[bridge GRANDPA pezpallet](../grandpa/). Once the proof is verified, the pezpallet knows that the given teyrchain
|
||||
header has been finalized by the relay chain. The teyrchain header fields may then be used to verify storage
|
||||
proofs, coming from the teyrchain. This allows the pezpallet to be used e.g. as a source of finality for the messages
|
||||
pezpallet.
|
||||
|
||||
## Pezpallet Operations
|
||||
|
||||
The main entrypoint of the pezpallet is the `submit_teyrchain_heads` call. It has three arguments:
|
||||
|
||||
- storage proof of teyrchain heads from the `Heads` map;
|
||||
|
||||
- teyrchain identifiers and hashes of their heads from the storage proof;
|
||||
|
||||
- the relay block, at which the storage proof has been generated.
|
||||
|
||||
The pezpallet may track multiple teyrchains. And the teyrchains may use different primitives - one may use 128-bit block
|
||||
numbers, other - 32-bit. To avoid extra decode operations, the pezpallet is using relay chain block number to order
|
||||
teyrchain headers. Any finalized descendant of finalized relay block `RB`, which has teyrchain block `PB` in
|
||||
its `Heads` map, is guaranteed to have either `PB`, or its descendant. So teyrchain block number grows with relay
|
||||
block number.
|
||||
|
||||
The pezpallet may reject teyrchain head if it already knows better (or the same) head. In addition, pezpallet rejects
|
||||
heads of untracked teyrchains.
|
||||
|
||||
The pezpallet doesn't track anything behind teyrchain heads. So it requires no initialization - it is ready to accept
|
||||
headers right after deployment.
|
||||
|
||||
## Non-Essential Functionality
|
||||
|
||||
There may be a special account in every runtime where the bridge teyrchains module is deployed. This
|
||||
account, named 'module owner', is like a module-level sudo account - he's able to halt and
|
||||
resume all module operations without requiring runtime upgrade. Calls that are related to this
|
||||
account are:
|
||||
|
||||
- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account;
|
||||
|
||||
- `fn set_operating_mode()`: the module owner (or sudo account) may call this function to stop all
|
||||
module operations. After this call, all finality proofs will be rejected until further `set_operating_mode` call'.
|
||||
This call may be used when something extraordinary happens with the bridge.
|
||||
|
||||
If pezpallet owner is not defined, the governance may be used to make those calls.
|
||||
|
||||
## Signed Extension to Reject Obsolete Headers
|
||||
|
||||
It'd be better for anyone (for chain and for submitters) to reject all transactions that are submitting
|
||||
already known teyrchain heads to the pezpallet. This way, we leave block space to other useful transactions and
|
||||
we don't charge concurrent submitters for their honest actions.
|
||||
|
||||
To deal with that, we have a [signed extension](./src/call_ext.rs) that may be added to the runtime.
|
||||
It does exactly what is required - rejects all transactions with already known heads. The submitter
|
||||
pays nothing for such transactions - they're simply removed from the transaction pool, when the block
|
||||
is built.
|
||||
|
||||
The signed extension, however, is a bit limited - it only works with transactions that provide single
|
||||
teyrchain head. So it won't work with multiple teyrchain heads transactions. This fits our needs
|
||||
for [Kusama <> PezkuwiChain bridge](../../docs/pezkuwi-kusama-bridge-overview.md). If you need to deal
|
||||
with other transaction formats, you may implement similar extension for your runtime.
|
||||
|
||||
You may also take a look at the [`generate_bridge_reject_obsolete_headers_and_messages`](../../bin/runtime-common/src/lib.rs)
|
||||
macro that bundles several similar signed extensions in a single one.
|
||||
|
||||
## Teyrchains Finality Relay
|
||||
|
||||
We have an offchain actor, who is watching for new teyrchain heads and submits them to the bridged chain.
|
||||
It is the teyrchains relay - you may look at the [crate level documentation and the code](../../relays/teyrchains/).
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Teyrchains finality pezpallet benchmarking.
|
||||
|
||||
use crate::{
|
||||
weights_ext::DEFAULT_TEYRCHAIN_HEAD_SIZE, Call, RelayBlockHash, RelayBlockHasher,
|
||||
RelayBlockNumber,
|
||||
};
|
||||
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHash, ParaHeadsProof, ParaId};
|
||||
use pezbp_runtime::UnverifiedStorageProofParams;
|
||||
use pezframe_benchmarking::{account, benchmarks_instance_pallet};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_std::prelude::*;
|
||||
|
||||
/// Pezpallet we're benchmarking here.
|
||||
pub struct Pezpallet<T: Config<I>, I: 'static = ()>(crate::Pezpallet<T, I>);
|
||||
|
||||
/// Trait that must be implemented by runtime to benchmark the teyrchains finality pezpallet.
|
||||
pub trait Config<I: 'static>: crate::Config<I> {
|
||||
/// Returns vector of supported teyrchains.
|
||||
fn teyrchains() -> Vec<ParaId>;
|
||||
/// Generate teyrchain heads proof and prepare environment for verifying this proof.
|
||||
fn prepare_teyrchain_heads_proof(
|
||||
teyrchains: &[ParaId],
|
||||
teyrchain_head_size: u32,
|
||||
proof_params: UnverifiedStorageProofParams,
|
||||
) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>);
|
||||
}
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
where_clause {
|
||||
where
|
||||
<T as pezpallet_bridge_grandpa::Config<T::BridgesGrandpaPalletInstance>>::BridgedChain:
|
||||
pezbp_runtime::Chain<
|
||||
BlockNumber = RelayBlockNumber,
|
||||
Hash = RelayBlockHash,
|
||||
Hasher = RelayBlockHasher,
|
||||
>,
|
||||
}
|
||||
|
||||
// Benchmark `submit_teyrchain_heads` extrinsic with different number of teyrchains.
|
||||
submit_teyrchain_heads_with_n_teyrchains {
|
||||
let p in 1..(T::teyrchains().len() + 1) as u32;
|
||||
|
||||
let sender = account("sender", 0, 0);
|
||||
let mut teyrchains = T::teyrchains();
|
||||
let _ = if p <= teyrchains.len() as u32 {
|
||||
teyrchains.split_off(p as usize)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
tracing::trace!(target: crate::LOG_TARGET, "=== {:?}", teyrchains.len());
|
||||
let (relay_block_number, relay_block_hash, teyrchain_heads_proof, teyrchains_heads) = T::prepare_teyrchain_heads_proof(
|
||||
&teyrchains,
|
||||
DEFAULT_TEYRCHAIN_HEAD_SIZE,
|
||||
UnverifiedStorageProofParams::default(),
|
||||
);
|
||||
let at_relay_block = (relay_block_number, relay_block_hash);
|
||||
}: submit_teyrchain_heads(RawOrigin::Signed(sender), at_relay_block, teyrchains_heads, teyrchain_heads_proof)
|
||||
verify {
|
||||
for teyrchain in teyrchains {
|
||||
assert!(crate::Pezpallet::<T, I>::best_teyrchain_head(teyrchain).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark `submit_teyrchain_heads` extrinsic with 1kb proof size.
|
||||
submit_teyrchain_heads_with_1kb_proof {
|
||||
let sender = account("sender", 0, 0);
|
||||
let teyrchains = vec![T::teyrchains()[0]];
|
||||
let (relay_block_number, relay_block_hash, teyrchain_heads_proof, teyrchains_heads) = T::prepare_teyrchain_heads_proof(
|
||||
&teyrchains,
|
||||
DEFAULT_TEYRCHAIN_HEAD_SIZE,
|
||||
UnverifiedStorageProofParams::from_db_size(1024),
|
||||
);
|
||||
let at_relay_block = (relay_block_number, relay_block_hash);
|
||||
}: submit_teyrchain_heads(RawOrigin::Signed(sender), at_relay_block, teyrchains_heads, teyrchain_heads_proof)
|
||||
verify {
|
||||
for teyrchain in teyrchains {
|
||||
assert!(crate::Pezpallet::<T, I>::best_teyrchain_head(teyrchain).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark `submit_teyrchain_heads` extrinsic with 16kb proof size.
|
||||
submit_teyrchain_heads_with_16kb_proof {
|
||||
let sender = account("sender", 0, 0);
|
||||
let teyrchains = vec![T::teyrchains()[0]];
|
||||
let (relay_block_number, relay_block_hash, teyrchain_heads_proof, teyrchains_heads) = T::prepare_teyrchain_heads_proof(
|
||||
&teyrchains,
|
||||
DEFAULT_TEYRCHAIN_HEAD_SIZE,
|
||||
UnverifiedStorageProofParams::from_db_size(16 * 1024),
|
||||
);
|
||||
let at_relay_block = (relay_block_number, relay_block_hash);
|
||||
}: submit_teyrchain_heads(RawOrigin::Signed(sender), at_relay_block, teyrchains_heads, teyrchain_heads_proof)
|
||||
verify {
|
||||
for teyrchain in teyrchains {
|
||||
assert!(crate::Pezpallet::<T, I>::best_teyrchain_head(teyrchain).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pezpallet, crate::mock::new_test_ext(), crate::mock::TestRuntime)
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{Config, GrandpaPalletOf, Pezpallet, RelayBlockNumber};
|
||||
use bp_header_pez_chain::HeaderChain;
|
||||
use pezbp_runtime::{HeaderId, OwnedBridgeModule};
|
||||
use bp_teyrchains::{BestParaHeadHash, SubmitTeyrchainHeadsInfo};
|
||||
use pezframe_support::{
|
||||
dispatch::CallableCallFor,
|
||||
traits::{Get, IsSubType},
|
||||
};
|
||||
use pezpallet_bridge_grandpa::SubmitFinalityProofHelper;
|
||||
use pezsp_runtime::{
|
||||
traits::Zero,
|
||||
transaction_validity::{InvalidTransaction, TransactionValidityError},
|
||||
RuntimeDebug,
|
||||
};
|
||||
|
||||
/// Verified `SubmitTeyrchainHeadsInfo`.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub struct VerifiedSubmitTeyrchainHeadsInfo {
|
||||
/// Base call information.
|
||||
pub base: SubmitTeyrchainHeadsInfo,
|
||||
/// A difference between bundled bridged relay chain header and relay chain header number
|
||||
/// used to prove best bridged teyrchain header, known to us before the call.
|
||||
pub improved_by: RelayBlockNumber,
|
||||
}
|
||||
|
||||
/// Helper struct that provides methods for working with the `SubmitTeyrchainHeads` call.
|
||||
pub struct SubmitTeyrchainHeadsHelper<T: Config<I>, I: 'static> {
|
||||
_phantom_data: pezsp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> SubmitTeyrchainHeadsHelper<T, I> {
|
||||
/// Check that is called from signed extension and takes the `is_free_execution_expected`
|
||||
/// into account.
|
||||
pub fn check_obsolete_from_extension(
|
||||
update: &SubmitTeyrchainHeadsInfo,
|
||||
) -> Result<RelayBlockNumber, TransactionValidityError> {
|
||||
// first do all base checks
|
||||
let improved_by = Self::check_obsolete(update)?;
|
||||
|
||||
// if we don't expect free execution - no more checks
|
||||
if !update.is_free_execution_expected {
|
||||
return Ok(improved_by);
|
||||
}
|
||||
|
||||
// reject if no more free slots remaining in the block
|
||||
if !SubmitFinalityProofHelper::<T, T::BridgesGrandpaPalletInstance>::has_free_header_slots()
|
||||
{
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
"The free teyrchain head can't be updated: no more free slots left in the block."
|
||||
);
|
||||
|
||||
return Err(InvalidTransaction::Call.into());
|
||||
}
|
||||
|
||||
// if free headers interval is not configured and call is expected to execute
|
||||
// for free => it is a relayer error, it should've been able to detect that.
|
||||
let free_headers_interval = match T::FreeHeadersInterval::get() {
|
||||
Some(free_headers_interval) => free_headers_interval,
|
||||
None => return Ok(improved_by),
|
||||
};
|
||||
|
||||
// reject if we are importing teyrchain headers too often
|
||||
if improved_by < free_headers_interval {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
%improved_by,
|
||||
"The free teyrchain head can't be updated: it improves previous
|
||||
best head while at least {free_headers_interval} is expected."
|
||||
);
|
||||
|
||||
return Err(InvalidTransaction::Stale.into());
|
||||
}
|
||||
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check if the para head provided by the `SubmitTeyrchainHeads` is better than the best one
|
||||
/// we know.
|
||||
pub fn check_obsolete(
|
||||
update: &SubmitTeyrchainHeadsInfo,
|
||||
) -> Result<RelayBlockNumber, TransactionValidityError> {
|
||||
// check if we know better teyrchain head already
|
||||
let improved_by = match crate::ParasInfo::<T, I>::get(update.para_id) {
|
||||
Some(stored_best_head) => {
|
||||
let improved_by = match update
|
||||
.at_relay_block
|
||||
.0
|
||||
.checked_sub(stored_best_head.best_head_hash.at_relay_block_number)
|
||||
{
|
||||
Some(improved_by) if improved_by > Zero::zero() => improved_by,
|
||||
_ => {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
"The teyrchain head can't be updated. The teyrchain head \
|
||||
was already updated at better relay chain block {} >= {}.",
|
||||
stored_best_head.best_head_hash.at_relay_block_number,
|
||||
update.at_relay_block.0
|
||||
);
|
||||
return Err(InvalidTransaction::Stale.into());
|
||||
},
|
||||
};
|
||||
|
||||
if stored_best_head.best_head_hash.head_hash == update.para_head_hash {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
para_head_hash=%update.para_head_hash,
|
||||
"The teyrchain head can't be updated. The teyrchain head hash \
|
||||
was already updated at block {} < {}.",
|
||||
stored_best_head.best_head_hash.at_relay_block_number,
|
||||
update.at_relay_block.0
|
||||
);
|
||||
return Err(InvalidTransaction::Stale.into());
|
||||
}
|
||||
|
||||
improved_by
|
||||
},
|
||||
None => RelayBlockNumber::MAX,
|
||||
};
|
||||
|
||||
// let's check if our chain had no reorgs and we still know the relay chain header
|
||||
// used to craft the proof
|
||||
if GrandpaPalletOf::<T, I>::finalized_header_state_root(update.at_relay_block.1).is_none() {
|
||||
tracing::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
para_id=?update.para_id,
|
||||
at_relay_block=?update.at_relay_block,
|
||||
"The teyrchain head can't be updated. Relay chain header used to create \
|
||||
teyrchain proof is missing from the storage."
|
||||
);
|
||||
|
||||
return Err(InvalidTransaction::Call.into());
|
||||
}
|
||||
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check if the `SubmitTeyrchainHeads` was successfully executed.
|
||||
pub fn was_successful(update: &SubmitTeyrchainHeadsInfo) -> bool {
|
||||
match crate::ParasInfo::<T, I>::get(update.para_id) {
|
||||
Some(stored_best_head) =>
|
||||
stored_best_head.best_head_hash ==
|
||||
BestParaHeadHash {
|
||||
at_relay_block_number: update.at_relay_block.0,
|
||||
head_hash: update.para_head_hash,
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing a call that is a sub type of this pezpallet's call.
|
||||
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||
IsSubType<CallableCallFor<Pezpallet<T, I>, T>>
|
||||
{
|
||||
/// Create a new instance of `SubmitTeyrchainHeadsInfo` from a `SubmitTeyrchainHeads` call with
|
||||
/// one single teyrchain entry.
|
||||
fn one_entry_submit_teyrchain_heads_info(&self) -> Option<SubmitTeyrchainHeadsInfo> {
|
||||
match self.is_sub_type() {
|
||||
Some(crate::Call::<T, I>::submit_teyrchain_heads {
|
||||
ref at_relay_block,
|
||||
ref teyrchains,
|
||||
..
|
||||
}) => match &teyrchains[..] {
|
||||
&[(para_id, para_head_hash)] => Some(SubmitTeyrchainHeadsInfo {
|
||||
at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1),
|
||||
para_id,
|
||||
para_head_hash,
|
||||
is_free_execution_expected: false,
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
Some(crate::Call::<T, I>::submit_teyrchain_heads_ex {
|
||||
ref at_relay_block,
|
||||
ref teyrchains,
|
||||
is_free_execution_expected,
|
||||
..
|
||||
}) => match &teyrchains[..] {
|
||||
&[(para_id, para_head_hash)] => Some(SubmitTeyrchainHeadsInfo {
|
||||
at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1),
|
||||
para_id,
|
||||
para_head_hash,
|
||||
is_free_execution_expected: *is_free_execution_expected,
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new instance of `SubmitTeyrchainHeadsInfo` from a `SubmitTeyrchainHeads` call with
|
||||
/// one single teyrchain entry, if the entry is for the provided teyrchain id.
|
||||
fn submit_teyrchain_heads_info_for(&self, para_id: u32) -> Option<SubmitTeyrchainHeadsInfo> {
|
||||
self.one_entry_submit_teyrchain_heads_info()
|
||||
.filter(|update| update.para_id.0 == para_id)
|
||||
}
|
||||
|
||||
/// Validate teyrchain heads in order to avoid "mining" transactions that provide
|
||||
/// outdated bridged teyrchain heads. Without this validation, even honest relayers
|
||||
/// may lose their funds if there are multiple relays running and submitting the
|
||||
/// same information.
|
||||
///
|
||||
/// This validation only works with transactions that are updating single teyrchain
|
||||
/// head. We can't use unbounded validation - it may take too long and either break
|
||||
/// block production, or "eat" significant portion of block production time literally
|
||||
/// for nothing. In addition, the single-teyrchain-head-per-transaction is how the
|
||||
/// pezpallet will be used in our environment.
|
||||
fn check_obsolete_submit_teyrchain_heads(
|
||||
&self,
|
||||
) -> Result<Option<VerifiedSubmitTeyrchainHeadsInfo>, TransactionValidityError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let update = match self.one_entry_submit_teyrchain_heads_info() {
|
||||
Some(update) => update,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
if Pezpallet::<T, I>::ensure_not_halted().is_err() {
|
||||
return Err(InvalidTransaction::Call.into());
|
||||
}
|
||||
|
||||
SubmitTeyrchainHeadsHelper::<T, I>::check_obsolete_from_extension(&update)
|
||||
.map(|improved_by| Some(VerifiedSubmitTeyrchainHeadsInfo { base: update, improved_by }))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I: 'static> CallSubType<T, I> for T::RuntimeCall
|
||||
where
|
||||
T: Config<I>,
|
||||
T::RuntimeCall: IsSubType<CallableCallFor<Pezpallet<T, I>, T>>,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
mock::{run_test, FreeHeadersInterval, RuntimeCall, TestRuntime},
|
||||
CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockHash, RelayBlockNumber,
|
||||
};
|
||||
use bp_header_pez_chain::StoredHeaderData;
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHash, ParaHeadsProof, ParaId};
|
||||
use pezbp_runtime::BasicOperatingMode;
|
||||
use bp_teyrchains::BestParaHeadHash;
|
||||
|
||||
fn validate_submit_teyrchain_heads(
|
||||
num: RelayBlockNumber,
|
||||
teyrchains: Vec<(ParaId, ParaHash)>,
|
||||
) -> bool {
|
||||
RuntimeCall::Teyrchains(crate::Call::<TestRuntime, ()>::submit_teyrchain_heads_ex {
|
||||
at_relay_block: (num, [num as u8; 32].into()),
|
||||
teyrchains,
|
||||
teyrchain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
.check_obsolete_submit_teyrchain_heads()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn validate_free_submit_teyrchain_heads(
|
||||
num: RelayBlockNumber,
|
||||
teyrchains: Vec<(ParaId, ParaHash)>,
|
||||
) -> bool {
|
||||
RuntimeCall::Teyrchains(crate::Call::<TestRuntime, ()>::submit_teyrchain_heads_ex {
|
||||
at_relay_block: (num, [num as u8; 32].into()),
|
||||
teyrchains,
|
||||
teyrchain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
|
||||
is_free_execution_expected: true,
|
||||
})
|
||||
.check_obsolete_submit_teyrchain_heads()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn insert_relay_block(num: RelayBlockNumber) {
|
||||
pezpallet_bridge_grandpa::ImportedHeaders::<TestRuntime, crate::Instance1>::insert(
|
||||
RelayBlockHash::from([num as u8; 32]),
|
||||
StoredHeaderData { number: num, state_root: RelayBlockHash::from([10u8; 32]) },
|
||||
);
|
||||
}
|
||||
|
||||
fn sync_to_relay_header_10() {
|
||||
ParasInfo::<TestRuntime, ()>::insert(
|
||||
ParaId(1),
|
||||
ParaInfo {
|
||||
best_head_hash: BestParaHeadHash {
|
||||
at_relay_block_number: 10,
|
||||
head_hash: [1u8; 32].into(),
|
||||
},
|
||||
next_imported_hash_position: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_header_from_the_obsolete_relay_block() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#5 => tx is
|
||||
// rejected
|
||||
sync_to_relay_header_10();
|
||||
assert!(!validate_submit_teyrchain_heads(5, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_header_from_the_same_relay_block() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
||||
// rejected
|
||||
sync_to_relay_header_10();
|
||||
assert!(!validate_submit_teyrchain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_header_from_new_relay_block_with_same_hash() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
||||
// rejected
|
||||
sync_to_relay_header_10();
|
||||
assert!(!validate_submit_teyrchain_heads(20, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_header_if_pallet_is_halted() {
|
||||
run_test(|| {
|
||||
// when pezpallet is halted => tx is rejected
|
||||
sync_to_relay_header_10();
|
||||
PalletOperatingMode::<TestRuntime, ()>::put(BasicOperatingMode::Halted);
|
||||
|
||||
assert!(!validate_submit_teyrchain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_header() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#15 => tx is
|
||||
// accepted
|
||||
sync_to_relay_header_10();
|
||||
insert_relay_block(15);
|
||||
assert!(validate_submit_teyrchain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_if_more_than_one_teyrchain_is_submitted() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#5, but another
|
||||
// teyrchain head is also supplied => tx is accepted
|
||||
sync_to_relay_header_10();
|
||||
assert!(validate_submit_teyrchain_heads(
|
||||
5,
|
||||
vec![(ParaId(1), [1u8; 32].into()), (ParaId(2), [1u8; 32].into())]
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_initial_teyrchain_head_if_missing_relay_chain_header() {
|
||||
run_test(|| {
|
||||
// when relay chain header is unknown => "obsolete"
|
||||
assert!(!validate_submit_teyrchain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
// when relay chain header is unknown => "ok"
|
||||
insert_relay_block(10);
|
||||
assert!(validate_submit_teyrchain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_free_teyrchain_head_if_missing_relay_chain_header() {
|
||||
run_test(|| {
|
||||
sync_to_relay_header_10();
|
||||
// when relay chain header is unknown => "obsolete"
|
||||
assert!(!validate_submit_teyrchain_heads(15, vec![(ParaId(2), [15u8; 32].into())]));
|
||||
// when relay chain header is unknown => "ok"
|
||||
insert_relay_block(15);
|
||||
assert!(validate_submit_teyrchain_heads(15, vec![(ParaId(2), [15u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_free_teyrchain_head_if_no_free_slots_remaining() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#15 => tx should
|
||||
// be accepted
|
||||
sync_to_relay_header_10();
|
||||
insert_relay_block(15);
|
||||
// ... but since we have specified `is_free_execution_expected = true`, it'll be
|
||||
// rejected
|
||||
assert!(!validate_free_submit_teyrchain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
||||
// ... if we have specify `is_free_execution_expected = false`, it'll be accepted
|
||||
assert!(validate_submit_teyrchain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_free_teyrchain_head_if_improves_by_is_below_expected() {
|
||||
run_test(|| {
|
||||
// when current best finalized is #10 and we're trying to import header#15 => tx should
|
||||
// be accepted
|
||||
sync_to_relay_header_10();
|
||||
insert_relay_block(10 + FreeHeadersInterval::get() - 1);
|
||||
insert_relay_block(10 + FreeHeadersInterval::get());
|
||||
// try to submit at 10 + FreeHeadersInterval::get() - 1 => failure
|
||||
let relay_header = 10 + FreeHeadersInterval::get() - 1;
|
||||
assert!(!validate_free_submit_teyrchain_heads(
|
||||
relay_header,
|
||||
vec![(ParaId(1), [2u8; 32].into())]
|
||||
));
|
||||
// try to submit at 10 + FreeHeadersInterval::get() => ok
|
||||
let relay_header = 10 + FreeHeadersInterval::get();
|
||||
assert!(validate_free_submit_teyrchain_heads(
|
||||
relay_header,
|
||||
vec![(ParaId(1), [2u8; 32].into())]
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,313 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use bp_header_pez_chain::ChainWithGrandpa;
|
||||
use bp_pezkuwi_core::teyrchains::ParaId;
|
||||
use pezbp_runtime::{Chain, ChainId, Teyrchain};
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types, traits::ConstU32, weights::Weight,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
testing::H256,
|
||||
traits::{BlakeTwo256, Header as HeaderT},
|
||||
MultiSignature, StateVersion,
|
||||
};
|
||||
|
||||
use crate as pezpallet_bridge_teyrchains;
|
||||
|
||||
pub type AccountId = u64;
|
||||
|
||||
pub type RelayBlockHeader =
|
||||
pezsp_runtime::generic::Header<crate::RelayBlockNumber, crate::RelayBlockHasher>;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
pub const PARAS_PALLET_NAME: &str = "Paras";
|
||||
pub const UNTRACKED_TEYRCHAIN_ID: u32 = 10;
|
||||
// use exact expected encoded size: `vec_len_size + header_number_size + state_root_hash_size`
|
||||
pub const MAXIMAL_TEYRCHAIN_HEAD_DATA_SIZE: u32 = 1 + 8 + 32;
|
||||
// total teyrchains that we use in tests
|
||||
pub const TOTAL_TEYRCHAINS: u32 = 4;
|
||||
|
||||
pub type RegularTeyrchainHeader = pezsp_runtime::testing::Header;
|
||||
pub type RegularTeyrchainHasher = BlakeTwo256;
|
||||
pub type BigTeyrchainHeader = pezsp_runtime::generic::Header<u128, BlakeTwo256>;
|
||||
|
||||
pub struct Teyrchain1;
|
||||
|
||||
impl Chain for Teyrchain1 {
|
||||
const ID: ChainId = *b"pch1";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = RegularTeyrchainHasher;
|
||||
type Header = RegularTeyrchainHeader;
|
||||
type AccountId = u64;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
0
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Teyrchain for Teyrchain1 {
|
||||
const TEYRCHAIN_ID: u32 = 1;
|
||||
const MAX_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
pub struct Teyrchain2;
|
||||
|
||||
impl Chain for Teyrchain2 {
|
||||
const ID: ChainId = *b"pch2";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = RegularTeyrchainHasher;
|
||||
type Header = RegularTeyrchainHeader;
|
||||
type AccountId = u64;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
0
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Teyrchain for Teyrchain2 {
|
||||
const TEYRCHAIN_ID: u32 = 2;
|
||||
const MAX_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
pub struct Teyrchain3;
|
||||
|
||||
impl Chain for Teyrchain3 {
|
||||
const ID: ChainId = *b"pch3";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = RegularTeyrchainHasher;
|
||||
type Header = RegularTeyrchainHeader;
|
||||
type AccountId = u64;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
0
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Teyrchain for Teyrchain3 {
|
||||
const TEYRCHAIN_ID: u32 = 3;
|
||||
const MAX_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
// this teyrchain is using u128 as block number and stored head data size exceeds limit
|
||||
pub struct BigTeyrchain;
|
||||
|
||||
impl Chain for BigTeyrchain {
|
||||
const ID: ChainId = *b"bpch";
|
||||
|
||||
type BlockNumber = u128;
|
||||
type Hash = H256;
|
||||
type Hasher = RegularTeyrchainHasher;
|
||||
type Header = BigTeyrchainHeader;
|
||||
type AccountId = u64;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
0
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Teyrchain for BigTeyrchain {
|
||||
const TEYRCHAIN_ID: u32 = 4;
|
||||
const MAX_HEADER_SIZE: u32 = 2_048;
|
||||
}
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Grandpa1: pezpallet_bridge_grandpa::<Instance1>::{Pezpallet, Event<T>},
|
||||
Grandpa2: pezpallet_bridge_grandpa::<Instance2>::{Pezpallet, Event<T>},
|
||||
Teyrchains: pezpallet_bridge_teyrchains::{Call, Pezpallet, Event<T>},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const HeadersToKeep: u32 = 5;
|
||||
pub const FreeHeadersInterval: u32 = 15;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_grandpa::Config<pezpallet_bridge_grandpa::Instance1> for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type MaxFreeHeadersPerBlock = ConstU32<2>;
|
||||
type FreeHeadersInterval = FreeHeadersInterval;
|
||||
type HeadersToKeep = HeadersToKeep;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_grandpa::Config<pezpallet_bridge_grandpa::Instance2> for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type MaxFreeHeadersPerBlock = ConstU32<2>;
|
||||
type FreeHeadersInterval = FreeHeadersInterval;
|
||||
type HeadersToKeep = HeadersToKeep;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const HeadsToKeep: u32 = 4;
|
||||
pub const ParasPalletName: &'static str = PARAS_PALLET_NAME;
|
||||
pub GetTenFirstTeyrchains: Vec<ParaId> = (0..10).map(ParaId).collect();
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_teyrchains::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type BridgesGrandpaPalletInstance = pezpallet_bridge_grandpa::Instance1;
|
||||
type ParasPalletName = ParasPalletName;
|
||||
type ParaStoredHeaderDataBuilder = (Teyrchain1, Teyrchain2, Teyrchain3, BigTeyrchain);
|
||||
type HeadsToKeep = HeadsToKeep;
|
||||
type MaxParaHeadDataSize = ConstU32<MAXIMAL_TEYRCHAIN_HEAD_DATA_SIZE>;
|
||||
type OnNewHead = ();
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl pezpallet_bridge_teyrchains::benchmarking::Config<()> for TestRuntime {
|
||||
fn teyrchains() -> Vec<ParaId> {
|
||||
vec![
|
||||
ParaId(Teyrchain1::TEYRCHAIN_ID),
|
||||
ParaId(Teyrchain2::TEYRCHAIN_ID),
|
||||
ParaId(Teyrchain3::TEYRCHAIN_ID),
|
||||
]
|
||||
}
|
||||
|
||||
fn prepare_teyrchain_heads_proof(
|
||||
teyrchains: &[ParaId],
|
||||
_teyrchain_head_size: u32,
|
||||
_proof_params: pezbp_runtime::UnverifiedStorageProofParams,
|
||||
) -> (
|
||||
crate::RelayBlockNumber,
|
||||
crate::RelayBlockHash,
|
||||
bp_pezkuwi_core::teyrchains::ParaHeadsProof,
|
||||
Vec<(ParaId, bp_pezkuwi_core::teyrchains::ParaHash)>,
|
||||
) {
|
||||
// in mock run we only care about benchmarks correctness, not the benchmark results
|
||||
// => ignore size related arguments
|
||||
let (state_root, proof, teyrchains) =
|
||||
bp_test_utils::prepare_teyrchain_heads_proof::<RegularTeyrchainHeader>(
|
||||
teyrchains.iter().map(|p| (p.0, crate::tests::head_data(p.0, 1))).collect(),
|
||||
);
|
||||
let relay_genesis_hash = crate::tests::initialize(state_root);
|
||||
(0, relay_genesis_hash, proof, teyrchains)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBridgedChain;
|
||||
|
||||
impl Chain for TestBridgedChain {
|
||||
const ID: ChainId = *b"tbch";
|
||||
|
||||
type BlockNumber = crate::RelayBlockNumber;
|
||||
type Hash = crate::RelayBlockHash;
|
||||
type Hasher = crate::RelayBlockHasher;
|
||||
type Header = RelayBlockHeader;
|
||||
|
||||
type AccountId = AccountId;
|
||||
type Balance = u32;
|
||||
type Nonce = u32;
|
||||
type Signature = pezsp_runtime::testing::TestSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for TestBridgedChain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = 16;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 64;
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
pezsp_io::TestExternalities::new(Default::default())
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
System::reset_events();
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
/// Return test relay chain header with given number.
|
||||
pub fn test_relay_header(
|
||||
num: crate::RelayBlockNumber,
|
||||
state_root: crate::RelayBlockHash,
|
||||
) -> RelayBlockHeader {
|
||||
RelayBlockHeader::new(
|
||||
num,
|
||||
Default::default(),
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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/>.
|
||||
|
||||
//! Tools for teyrchain head proof verification.
|
||||
|
||||
use crate::{Config, GrandpaPalletOf, RelayBlockHash, RelayBlockHasher};
|
||||
use bp_header_pez_chain::{HeaderChain, HeaderChainError};
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHead, ParaId};
|
||||
use pezbp_runtime::{RawStorageProof, StorageProofChecker, StorageProofError};
|
||||
use bp_teyrchains::teyrchain_head_storage_key_at_source;
|
||||
use codec::Decode;
|
||||
use pezframe_support::traits::Get;
|
||||
|
||||
/// Abstraction over storage proof manipulation, hiding implementation details of actual storage
|
||||
/// proofs.
|
||||
pub trait StorageProofAdapter<T: Config<I>, I: 'static> {
|
||||
/// Read and decode optional value from the proof.
|
||||
fn read_and_decode_optional_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<Option<D>, StorageProofError>;
|
||||
|
||||
/// Checks if each key was read.
|
||||
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError>;
|
||||
|
||||
/// Read teyrchain head from storage proof.
|
||||
fn read_teyrchain_head(
|
||||
&mut self,
|
||||
teyrchain: ParaId,
|
||||
) -> Result<Option<ParaHead>, StorageProofError> {
|
||||
let teyrchain_head_key =
|
||||
teyrchain_head_storage_key_at_source(T::ParasPalletName::get(), teyrchain);
|
||||
self.read_and_decode_optional_value(&teyrchain_head_key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Actual storage proof adapter for teyrchain proofs.
|
||||
pub type TeyrchainsStorageProofAdapter<T, I> = RawStorageProofAdapter<T, I>;
|
||||
|
||||
/// A `StorageProofAdapter` implementation for raw storage proofs.
|
||||
pub struct RawStorageProofAdapter<T: Config<I>, I: 'static> {
|
||||
storage: StorageProofChecker<RelayBlockHasher>,
|
||||
_dummy: pezsp_std::marker::PhantomData<(T, I)>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> RawStorageProofAdapter<T, I> {
|
||||
/// Try to create a new instance of `RawStorageProofAdapter`.
|
||||
pub fn try_new_with_verified_storage_proof(
|
||||
relay_block_hash: RelayBlockHash,
|
||||
storage_proof: RawStorageProof,
|
||||
) -> Result<Self, HeaderChainError> {
|
||||
GrandpaPalletOf::<T, I>::verify_storage_proof(relay_block_hash, storage_proof)
|
||||
.map(|storage| RawStorageProofAdapter::<T, I> { storage, _dummy: Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> StorageProofAdapter<T, I> for RawStorageProofAdapter<T, I> {
|
||||
fn read_and_decode_optional_value<D: Decode>(
|
||||
&mut self,
|
||||
key: &impl AsRef<[u8]>,
|
||||
) -> Result<Option<D>, StorageProofError> {
|
||||
self.storage.read_and_decode_opt_value(key.as_ref())
|
||||
}
|
||||
|
||||
fn ensure_no_unused_keys(self) -> Result<(), StorageProofError> {
|
||||
self.storage.ensure_no_unused_nodes()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_bridge_teyrchains
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-06-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `serban-ROG-Zephyrus`, CPU: `12th Gen Intel(R) Core(TM) i7-12700H`
|
||||
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/unknown-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_bridge_teyrchains
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/teyrchains/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pezpallet_bridge_teyrchains.
|
||||
pub trait WeightInfo {
|
||||
fn submit_teyrchain_heads_with_n_teyrchains(p: u32) -> Weight;
|
||||
fn submit_teyrchain_heads_with_1kb_proof() -> Weight;
|
||||
fn submit_teyrchain_heads_with_16kb_proof() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_bridge_teyrchains` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `p` is `[1, 2]`.
|
||||
fn submit_teyrchain_heads_with_n_teyrchains(_p: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 30_211 nanoseconds.
|
||||
Weight::from_parts(32_633_893, 3038)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
fn submit_teyrchain_heads_with_1kb_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 30_830 nanoseconds.
|
||||
Weight::from_parts(31_801_000, 3038)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
fn submit_teyrchain_heads_with_16kb_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 44_736 nanoseconds.
|
||||
Weight::from_parts(45_296_000, 3038)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
///
|
||||
/// The range of component `p` is `[1, 2]`.
|
||||
fn submit_teyrchain_heads_with_n_teyrchains(_p: u32) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 30_211 nanoseconds.
|
||||
Weight::from_parts(32_633_893, 3038)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
fn submit_teyrchain_heads_with_1kb_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 30_830 nanoseconds.
|
||||
Weight::from_parts(31_801_000, 3038)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: BridgeUnknownTeyrchains PalletOperatingMode (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains PalletOperatingMode (max_values: Some(1), max_size: Some(1),
|
||||
/// added: 496, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0)
|
||||
///
|
||||
/// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68),
|
||||
/// added: 2048, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ParasInfo (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ParasInfo (max_values: Some(1), max_size: Some(60), added:
|
||||
/// 555, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHashes (r:1 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHashes (max_values: Some(1024), max_size:
|
||||
/// Some(64), added: 1549, mode: MaxEncodedLen)
|
||||
///
|
||||
/// Storage: BridgeUnknownTeyrchains ImportedParaHeads (r:0 w:1)
|
||||
///
|
||||
/// Proof: BridgeUnknownTeyrchains ImportedParaHeads (max_values: Some(1024), max_size:
|
||||
/// Some(196), added: 1681, mode: MaxEncodedLen)
|
||||
fn submit_teyrchain_heads_with_16kb_proof() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3038`
|
||||
// Minimum execution time: 44_736 nanoseconds.
|
||||
Weight::from_parts(45_296_000, 3038)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Weight-related utilities.
|
||||
|
||||
use crate::weights::{BridgeWeight, WeightInfo};
|
||||
|
||||
use pezbp_runtime::Size;
|
||||
use pezframe_support::weights::{RuntimeDbWeight, Weight};
|
||||
|
||||
/// Size of the regular teyrchain head.
|
||||
///
|
||||
/// It's not that we are expecting all teyrchain heads to share the same size or that we would
|
||||
/// reject all heads that have larger/lesser size. It is about head size that we use in benchmarks.
|
||||
/// Relayer would need to pay additional fee for extra bytes.
|
||||
///
|
||||
/// 384 is a bit larger (1.3 times) than the size of the randomly chosen Pezkuwi block.
|
||||
pub const DEFAULT_TEYRCHAIN_HEAD_SIZE: u32 = 384;
|
||||
|
||||
/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at
|
||||
/// some generic chain.
|
||||
pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
|
||||
|
||||
/// Extended weight info.
|
||||
pub trait WeightInfoExt: WeightInfo {
|
||||
// Our configuration assumes that the runtime has special signed extensions used to:
|
||||
//
|
||||
// 1) boost priority of `submit_teyrchain_heads` transactions;
|
||||
//
|
||||
// 2) slash relayer if he submits an invalid transaction.
|
||||
//
|
||||
// We read and update storage values of other pallets (`pezpallet-bridge-relayers` and
|
||||
// balances/assets pezpallet). So we need to add this weight to the weight of our call.
|
||||
// Hence two following methods.
|
||||
|
||||
/// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions
|
||||
/// that are declared at runtime level.
|
||||
fn submit_teyrchain_heads_overhead_from_runtime() -> Weight;
|
||||
|
||||
/// Storage proof overhead, that is included in every storage proof.
|
||||
///
|
||||
/// The relayer would pay some extra fee for additional proof bytes, since they mean
|
||||
/// more hashing operations.
|
||||
fn expected_extra_storage_proof_size() -> u32;
|
||||
|
||||
/// Weight of the teyrchain heads delivery extrinsic.
|
||||
fn submit_teyrchain_heads_weight(
|
||||
db_weight: RuntimeDbWeight,
|
||||
proof: &impl Size,
|
||||
teyrchains_count: u32,
|
||||
) -> Weight {
|
||||
// weight of the `submit_teyrchain_heads` with exactly `teyrchains_count` teyrchain
|
||||
// heads of the default size (`DEFAULT_TEYRCHAIN_HEAD_SIZE`)
|
||||
let base_weight = Self::submit_teyrchain_heads_with_n_teyrchains(teyrchains_count);
|
||||
|
||||
// overhead because of extra storage proof bytes
|
||||
let expected_proof_size = teyrchains_count
|
||||
.saturating_mul(DEFAULT_TEYRCHAIN_HEAD_SIZE)
|
||||
.saturating_add(Self::expected_extra_storage_proof_size());
|
||||
let actual_proof_size = proof.size();
|
||||
let proof_size_overhead = Self::storage_proof_size_overhead(
|
||||
actual_proof_size.saturating_sub(expected_proof_size),
|
||||
);
|
||||
|
||||
// potential pruning weight (refunded if hasn't happened)
|
||||
let pruning_weight =
|
||||
Self::teyrchain_head_pruning_weight(db_weight).saturating_mul(teyrchains_count as u64);
|
||||
|
||||
base_weight
|
||||
.saturating_add(proof_size_overhead)
|
||||
.saturating_add(pruning_weight)
|
||||
.saturating_add(Self::submit_teyrchain_heads_overhead_from_runtime())
|
||||
}
|
||||
|
||||
/// Returns weight of single teyrchain head storage update.
|
||||
///
|
||||
/// This weight only includes db write operations that happens if teyrchain head is actually
|
||||
/// updated. All extra weights (weight of storage proof validation, additional checks, ...) is
|
||||
/// not included.
|
||||
fn teyrchain_head_storage_write_weight(db_weight: RuntimeDbWeight) -> Weight {
|
||||
// it's just a couple of operations - we need to write the hash (`ImportedParaHashes`) and
|
||||
// the head itself (`ImportedParaHeads`. Pruning is not included here
|
||||
db_weight.writes(2)
|
||||
}
|
||||
|
||||
/// Returns weight of single teyrchain head pruning.
|
||||
fn teyrchain_head_pruning_weight(db_weight: RuntimeDbWeight) -> Weight {
|
||||
// it's just one write operation, we don't want any benchmarks for that
|
||||
db_weight.writes(1)
|
||||
}
|
||||
|
||||
/// Returns weight that needs to be accounted when storage proof of given size is received.
|
||||
fn storage_proof_size_overhead(extra_proof_bytes: u32) -> Weight {
|
||||
let extra_byte_weight = (Self::submit_teyrchain_heads_with_16kb_proof() -
|
||||
Self::submit_teyrchain_heads_with_1kb_proof()) /
|
||||
(15 * 1024);
|
||||
extra_byte_weight.saturating_mul(extra_proof_bytes as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightInfoExt for () {
|
||||
fn submit_teyrchain_heads_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: pezframe_system::Config> WeightInfoExt for BridgeWeight<T> {
|
||||
fn submit_teyrchain_heads_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
EXTRA_STORAGE_PROOF_SIZE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
[package]
|
||||
name = "pezpallet-xcm-bridge-hub-router"
|
||||
description = "Bridge hub interface for sibling/parent chains with dynamic fees support."
|
||||
version = "0.5.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-xcm-bridge-hub-router"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["bit-vec", "derive", "serde"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-xcm-bridge-hub-router = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
# Pezkuwi Dependencies
|
||||
pezkuwi-runtime-teyrchains = { workspace = true }
|
||||
xcm = { workspace = true }
|
||||
xcm-builder = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-xcm-bridge-hub-router/std",
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezkuwi-runtime-teyrchains/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
"xcm-builder/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-xcm-bridge-hub-router/runtime-benchmarks",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezkuwi-runtime-teyrchains/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"xcm-builder/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezkuwi-runtime-teyrchains/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! XCM bridge hub router pezpallet benchmarks.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::{Bridge, BridgeState, Call};
|
||||
use pezframe_benchmarking::{benchmarks_instance_pallet, BenchmarkError};
|
||||
use pezframe_support::traits::{EnsureOrigin, Get, Hooks, UnfilteredDispatchable};
|
||||
use pezkuwi_runtime_teyrchains::FeeTracker;
|
||||
use pezsp_runtime::{traits::Zero, Saturating};
|
||||
use xcm::prelude::*;
|
||||
|
||||
/// Pezpallet we're benchmarking here.
|
||||
pub struct Pezpallet<T: Config<I>, I: 'static = ()>(crate::Pezpallet<T, I>);
|
||||
|
||||
/// Trait that must be implemented by runtime to be able to benchmark pezpallet properly.
|
||||
pub trait Config<I: 'static>: crate::Config<I> {
|
||||
/// Fill up queue so it becomes congested.
|
||||
fn make_congested();
|
||||
|
||||
/// Returns destination which is valid for this router instance.
|
||||
/// (Needs to pass `T::Bridges`)
|
||||
/// Make sure that `SendXcm` will pass.
|
||||
fn ensure_bridged_target_destination() -> Result<Location, BenchmarkError> {
|
||||
Ok(Location::new(
|
||||
Self::UniversalLocation::get().len() as u8,
|
||||
[GlobalConsensus(Self::BridgedNetworkId::get().unwrap())],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
on_initialize_when_non_congested {
|
||||
Bridge::<T, I>::put(BridgeState {
|
||||
is_congested: false,
|
||||
delivery_fee_factor: crate::Pezpallet::<T, I>::MIN_FEE_FACTOR.saturating_mul(2.into()),
|
||||
});
|
||||
}: {
|
||||
crate::Pezpallet::<T, I>::on_initialize(Zero::zero())
|
||||
}
|
||||
|
||||
on_initialize_when_congested {
|
||||
Bridge::<T, I>::put(BridgeState {
|
||||
is_congested: false,
|
||||
delivery_fee_factor: crate::Pezpallet::<T, I>::MIN_FEE_FACTOR.saturating_mul(2.into()),
|
||||
});
|
||||
let _ = T::ensure_bridged_target_destination()?;
|
||||
T::make_congested();
|
||||
}: {
|
||||
crate::Pezpallet::<T, I>::on_initialize(Zero::zero())
|
||||
}
|
||||
|
||||
report_bridge_status {
|
||||
Bridge::<T, I>::put(BridgeState::default());
|
||||
|
||||
let origin: T::RuntimeOrigin = T::BridgeHubOrigin::try_successful_origin().expect("expected valid BridgeHubOrigin");
|
||||
let bridge_id = Default::default();
|
||||
let is_congested = true;
|
||||
|
||||
let call = Call::<T, I>::report_bridge_status { bridge_id, is_congested };
|
||||
}: { call.dispatch_bypass_filter(origin)? }
|
||||
verify {
|
||||
assert!(Bridge::<T, I>::get().is_congested);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Pezpallet that may be used instead of `SovereignPaidRemoteExporter` in the XCM router
|
||||
//! configuration. The main thing that the pezpallet offers is the dynamic message fee,
|
||||
//! that is computed based on the bridge queues state. It starts exponentially increasing
|
||||
//! if the queue between this chain and the sibling/child bridge hub is congested.
|
||||
//!
|
||||
//! All other bridge hub queues offer some backpressure mechanisms. So if at least one
|
||||
//! of all queues is congested, it will eventually lead to the growth of the queue at
|
||||
//! this chain.
|
||||
//!
|
||||
//! **A note on terminology**: when we mention the bridge hub here, we mean the chain that
|
||||
//! has the messages pezpallet deployed (`pezpallet-bridge-grandpa`, `pezpallet-bridge-messages`,
|
||||
//! `pezpallet-xcm-bridge-hub`, ...). It may be the system bridge hub teyrchain or any other
|
||||
//! chain.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_xcm_bridge_hub_router::MINIMAL_DELIVERY_FEE_FACTOR;
|
||||
pub use bp_xcm_bridge_hub_router::{BridgeState, XcmChannelStatusProvider};
|
||||
use codec::Encode;
|
||||
use pezframe_support::traits::Get;
|
||||
use pezkuwi_runtime_teyrchains::FeeTracker;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{FixedPointNumber, FixedU128};
|
||||
use pezsp_std::vec::Vec;
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{ExporterFor, InspectMessageQueues, SovereignPaidRemoteExporter};
|
||||
|
||||
pub use pezpallet::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
pub mod benchmarking;
|
||||
pub mod weights;
|
||||
|
||||
mod mock;
|
||||
|
||||
/// Maximal size of the XCM message that may be sent over bridge.
|
||||
///
|
||||
/// This should be less than the maximal size, allowed by the messages pezpallet, because
|
||||
/// the message itself is wrapped in other structs and is double encoded.
|
||||
pub const HARD_MESSAGE_SIZE_LIMIT: u32 = 32 * 1024;
|
||||
|
||||
/// The target that will be used when publishing logs related to this pezpallet.
|
||||
///
|
||||
/// This doesn't match the pattern used by other bridge pallets (`runtime::bridge-*`). But this
|
||||
/// pezpallet has significant differences with those pallets. The main one is that is intended to
|
||||
/// be deployed at sending chains. Other bridge pallets are likely to be deployed at the separate
|
||||
/// bridge hub teyrchain.
|
||||
pub const LOG_TARGET: &str = "xcm::bridge-hub-router";
|
||||
|
||||
#[pezframe_support::pezpallet]
|
||||
pub mod pezpallet {
|
||||
use super::*;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pezpallet::config]
|
||||
pub trait Config<I: 'static = ()>: pezframe_system::Config {
|
||||
/// The overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
/// Benchmarks results from runtime we're plugged into.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// Universal location of this runtime.
|
||||
type UniversalLocation: Get<InteriorLocation>;
|
||||
/// Relative location of the supported sibling bridge hub.
|
||||
type SiblingBridgeHubLocation: Get<Location>;
|
||||
/// The bridged network that this config is for if specified.
|
||||
/// Also used for filtering `Bridges` by `BridgedNetworkId`.
|
||||
/// If not specified, allows all networks pass through.
|
||||
type BridgedNetworkId: Get<Option<NetworkId>>;
|
||||
/// Configuration for supported **bridged networks/locations** with **bridge location** and
|
||||
/// **possible fee**. Allows to externalize better control over allowed **bridged
|
||||
/// networks/locations**.
|
||||
type Bridges: ExporterFor;
|
||||
/// Checks the XCM version for the destination.
|
||||
type DestinationVersion: GetVersion;
|
||||
|
||||
/// Origin of the sibling bridge hub that is allowed to report bridge status.
|
||||
type BridgeHubOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
/// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location.
|
||||
type ToBridgeHubSender: SendXcm;
|
||||
/// Local XCM channel manager.
|
||||
type LocalXcmChannelManager: XcmChannelStatusProvider;
|
||||
|
||||
/// Additional fee that is paid for every byte of the outbound message.
|
||||
type ByteFee: Get<u128>;
|
||||
/// Asset that is used to paid bridge fee.
|
||||
type FeeAsset: Get<AssetId>;
|
||||
}
|
||||
|
||||
#[pezpallet::pezpallet]
|
||||
pub struct Pezpallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
#[pezpallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pezpallet<T, I> {
|
||||
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
|
||||
// if XCM channel is still congested, we don't change anything
|
||||
if T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get()) {
|
||||
return T::WeightInfo::on_initialize_when_congested();
|
||||
}
|
||||
|
||||
// if bridge has reported congestion, we don't change anything
|
||||
let mut bridge = Self::bridge();
|
||||
if bridge.is_congested {
|
||||
return T::WeightInfo::on_initialize_when_congested();
|
||||
}
|
||||
|
||||
let previous_factor = Self::get_fee_factor(());
|
||||
// if we can't decrease the delivery fee factor anymore, we don't change anything
|
||||
if !Self::do_decrease_fee_factor(&mut bridge.delivery_fee_factor) {
|
||||
return T::WeightInfo::on_initialize_when_congested();
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
from=%previous_factor,
|
||||
to=%bridge.delivery_fee_factor,
|
||||
"Bridge channel is uncongested. Decreased fee factor"
|
||||
);
|
||||
Self::deposit_event(Event::DeliveryFeeFactorDecreased {
|
||||
new_value: bridge.delivery_fee_factor,
|
||||
});
|
||||
|
||||
Bridge::<T, I>::put(bridge);
|
||||
|
||||
T::WeightInfo::on_initialize_when_non_congested()
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Notification about congested bridge queue.
|
||||
#[pezpallet::call_index(0)]
|
||||
#[pezpallet::weight(T::WeightInfo::report_bridge_status())]
|
||||
pub fn report_bridge_status(
|
||||
origin: OriginFor<T>,
|
||||
// this argument is not currently used, but to ease future migration, we'll keep it
|
||||
// here
|
||||
bridge_id: H256,
|
||||
is_congested: bool,
|
||||
) -> DispatchResult {
|
||||
T::BridgeHubOrigin::ensure_origin(origin)?;
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
from=?bridge_id,
|
||||
congested=%is_congested,
|
||||
"Received bridge status"
|
||||
);
|
||||
|
||||
Bridge::<T, I>::mutate(|bridge| {
|
||||
bridge.is_congested = is_congested;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Bridge that we are using.
|
||||
///
|
||||
/// **bridges-v1** assumptions: all outbound messages through this router are using single lane
|
||||
/// and to single remote consensus. If there is some other remote consensus that uses the same
|
||||
/// bridge hub, the separate pezpallet instance shall be used, In `v2` we'll have all required
|
||||
/// primitives (lane-id aka bridge-id, derived from XCM locations) to support multiple bridges
|
||||
/// by the same pezpallet instance.
|
||||
#[pezpallet::storage]
|
||||
pub type Bridge<T: Config<I>, I: 'static = ()> = StorageValue<_, BridgeState, ValueQuery>;
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Bridge that we are using.
|
||||
pub fn bridge() -> BridgeState {
|
||||
Bridge::<T, I>::get()
|
||||
}
|
||||
|
||||
/// Called when new message is sent (queued to local outbound XCM queue) over the bridge.
|
||||
pub(crate) fn on_message_sent_to_bridge(message_size: u32) {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?message_size, "on_message_sent_to_bridge"
|
||||
);
|
||||
let _ = Bridge::<T, I>::try_mutate(|bridge| {
|
||||
let is_channel_with_bridge_hub_congested =
|
||||
T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get());
|
||||
let is_bridge_congested = bridge.is_congested;
|
||||
|
||||
// if outbound queue is not congested AND bridge has not reported congestion, do
|
||||
// nothing
|
||||
if !is_channel_with_bridge_hub_congested && !is_bridge_congested {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let previous_factor = Self::get_fee_factor(());
|
||||
// ok - we need to increase the fee factor, let's do that
|
||||
<Self as FeeTracker>::do_increase_fee_factor(
|
||||
&mut bridge.delivery_fee_factor,
|
||||
message_size as u128,
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
from=%previous_factor,
|
||||
to=%bridge.delivery_fee_factor,
|
||||
"Bridge channel is congested. Increased fee factor"
|
||||
);
|
||||
Self::deposit_event(Event::DeliveryFeeFactorIncreased {
|
||||
new_value: bridge.delivery_fee_factor,
|
||||
});
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[pezpallet::event]
|
||||
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// Delivery fee factor has been decreased.
|
||||
DeliveryFeeFactorDecreased {
|
||||
/// New value of the `DeliveryFeeFactor`.
|
||||
new_value: FixedU128,
|
||||
},
|
||||
/// Delivery fee factor has been increased.
|
||||
DeliveryFeeFactorIncreased {
|
||||
/// New value of the `DeliveryFeeFactor`.
|
||||
new_value: FixedU128,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// We'll be using `SovereignPaidRemoteExporter` to send remote messages over the sibling/child
|
||||
/// bridge hub.
|
||||
type ViaBridgeHubExporter<T, I> = SovereignPaidRemoteExporter<
|
||||
Pezpallet<T, I>,
|
||||
<T as Config<I>>::ToBridgeHubSender,
|
||||
<T as Config<I>>::UniversalLocation,
|
||||
>;
|
||||
|
||||
// This pezpallet acts as the `ExporterFor` for the `SovereignPaidRemoteExporter` to compute
|
||||
// message fee using fee factor.
|
||||
impl<T: Config<I>, I: 'static> ExporterFor for Pezpallet<T, I> {
|
||||
fn exporter_for(
|
||||
network: &NetworkId,
|
||||
remote_location: &InteriorLocation,
|
||||
message: &Xcm<()>,
|
||||
) -> Option<(Location, Option<Asset>)> {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?network, ?remote_location, msg=?message, "exporter_for"
|
||||
);
|
||||
// ensure that the message is sent to the expected bridged network (if specified).
|
||||
if let Some(bridged_network) = T::BridgedNetworkId::get() {
|
||||
if *network != bridged_network {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
bridged_network_id=?bridged_network, ?network, "Router does not support bridging!"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that the message is sent to the expected bridged network and location.
|
||||
let (bridge_hub_location, maybe_payment) =
|
||||
match T::Bridges::exporter_for(network, remote_location, message) {
|
||||
Some((bridge_hub_location, maybe_payment))
|
||||
if bridge_hub_location.eq(&T::SiblingBridgeHubLocation::get()) =>
|
||||
(bridge_hub_location, maybe_payment),
|
||||
_ => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
bridged_network_id=?T::BridgedNetworkId::get(),
|
||||
sibling_bridge_hub_location=?T::SiblingBridgeHubLocation::get(),
|
||||
?network,
|
||||
?remote_location,
|
||||
"Router configured does not support bridging!"
|
||||
);
|
||||
return None;
|
||||
},
|
||||
};
|
||||
|
||||
// take `base_fee` from `T::Brides`, but it has to be the same `T::FeeAsset`
|
||||
let base_fee = match maybe_payment {
|
||||
Some(payment) => match payment {
|
||||
Asset { fun: Fungible(amount), id } if id.eq(&T::FeeAsset::get()) => amount,
|
||||
invalid_asset => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
bridged_network_id=?T::BridgedNetworkId::get(),
|
||||
fee_asset=?T::FeeAsset::get(),
|
||||
with=?invalid_asset,
|
||||
?bridge_hub_location,
|
||||
?network,
|
||||
?remote_location,
|
||||
"Router is configured for `T::FeeAsset` which is not compatible for bridging!"
|
||||
);
|
||||
return None;
|
||||
},
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// compute fee amount. Keep in mind that this is only the bridge fee. The fee for sending
|
||||
// message from this chain to child/sibling bridge hub is determined by the
|
||||
// `Config::ToBridgeHubSender`
|
||||
let message_size = message.encoded_size();
|
||||
let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get());
|
||||
let fee_sum = base_fee.saturating_add(message_fee);
|
||||
let fee_factor = Self::get_fee_factor(());
|
||||
let fee = fee_factor.saturating_mul_int(fee_sum);
|
||||
|
||||
let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None };
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
to=?(network, remote_location),
|
||||
bridge_fee=?fee,
|
||||
%fee_factor,
|
||||
"Going to send message ({message_size} bytes) over bridge."
|
||||
);
|
||||
|
||||
Some((bridge_hub_location, fee))
|
||||
}
|
||||
}
|
||||
|
||||
// This pezpallet acts as the `SendXcm` to the sibling/child bridge hub instead of regular
|
||||
// XCMP/DMP transport. This allows injecting dynamic message fees into XCM programs that
|
||||
// are going to the bridged network.
|
||||
impl<T: Config<I>, I: 'static> SendXcm for Pezpallet<T, I> {
|
||||
type Ticket = (u32, <T::ToBridgeHubSender as SendXcm>::Ticket);
|
||||
|
||||
fn validate(
|
||||
dest: &mut Option<Location>,
|
||||
xcm: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
tracing::trace!(target: LOG_TARGET, msg=?xcm, destination=?dest, "validate");
|
||||
|
||||
// In case of success, the `ViaBridgeHubExporter` can modify XCM instructions and consume
|
||||
// `dest` / `xcm`, so we retain the clone of original message and the destination for later
|
||||
// `DestinationVersion` validation.
|
||||
let xcm_to_dest_clone = xcm.clone();
|
||||
let dest_clone = dest.clone();
|
||||
|
||||
// First, use the inner exporter to validate the destination to determine if it is even
|
||||
// routable. If it is not, return an error. If it is, then the XCM is extended with
|
||||
// instructions to pay the message fee at the sibling/child bridge hub. The cost will
|
||||
// include both the cost of (1) delivery to the sibling bridge hub (returned by
|
||||
// `Config::ToBridgeHubSender`) and (2) delivery to the bridged bridge hub (returned by
|
||||
// `Self::exporter_for`).
|
||||
match ViaBridgeHubExporter::<T, I>::validate(dest, xcm) {
|
||||
Ok((ticket, cost)) => {
|
||||
// If the ticket is ok, it means we are routing with this router, so we need to
|
||||
// apply more validations to the cloned `dest` and `xcm`, which are required here.
|
||||
let xcm_to_dest_clone = xcm_to_dest_clone.ok_or(SendError::MissingArgument)?;
|
||||
let dest_clone = dest_clone.ok_or(SendError::MissingArgument)?;
|
||||
|
||||
// We won't have access to `dest` and `xcm` in the `deliver` method, so we need to
|
||||
// precompute everything required here. However, `dest` and `xcm` were consumed by
|
||||
// `ViaBridgeHubExporter`, so we need to use their clones.
|
||||
let message_size = xcm_to_dest_clone.encoded_size() as _;
|
||||
|
||||
// The bridge doesn't support oversized or overweight messages. Therefore, it's
|
||||
// better to drop such messages here rather than at the bridge hub. Let's check the
|
||||
// message size."
|
||||
if message_size > HARD_MESSAGE_SIZE_LIMIT {
|
||||
return Err(SendError::ExceedsMaxMessageSize);
|
||||
}
|
||||
|
||||
// We need to ensure that the known `dest`'s XCM version can comprehend the current
|
||||
// `xcm` program. This may seem like an additional, unnecessary check, but it is
|
||||
// not. A similar check is probably performed by the `ViaBridgeHubExporter`, which
|
||||
// attempts to send a versioned message to the sibling bridge hub. However, the
|
||||
// local bridge hub may have a higher XCM version than the remote `dest`. Once
|
||||
// again, it is better to discard such messages here than at the bridge hub (e.g.,
|
||||
// to avoid losing funds).
|
||||
let destination_version = T::DestinationVersion::get_version_for(&dest_clone)
|
||||
.ok_or(SendError::DestinationUnsupported)?;
|
||||
VersionedXcm::from(xcm_to_dest_clone)
|
||||
.into_version(destination_version)
|
||||
.map_err(|()| SendError::DestinationUnsupported)?;
|
||||
|
||||
Ok(((message_size, ticket), cost))
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::trace!(target: LOG_TARGET, error=?e, "validate - ViaBridgeHubExporter");
|
||||
Err(e)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
// use router to enqueue message to the sibling/child bridge hub. This also should handle
|
||||
// payment for passing through this queue.
|
||||
let (message_size, ticket) = ticket;
|
||||
let xcm_hash = ViaBridgeHubExporter::<T, I>::deliver(ticket)?;
|
||||
|
||||
// increase delivery fee factor if required
|
||||
Self::on_message_sent_to_bridge(message_size);
|
||||
|
||||
tracing::trace!(target: LOG_TARGET, ?xcm_hash, "deliver - message sent");
|
||||
Ok(xcm_hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> InspectMessageQueues for Pezpallet<T, I> {
|
||||
fn clear_messages() {}
|
||||
|
||||
/// This router needs to implement `InspectMessageQueues` but doesn't have to
|
||||
/// return any messages, since it just reuses the `XcmpQueue` router.
|
||||
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> FeeTracker for Pezpallet<T, I> {
|
||||
type Id = ();
|
||||
|
||||
const MIN_FEE_FACTOR: FixedU128 = MINIMAL_DELIVERY_FEE_FACTOR;
|
||||
|
||||
fn get_fee_factor(_id: Self::Id) -> FixedU128 {
|
||||
Self::bridge().delivery_fee_factor
|
||||
}
|
||||
|
||||
fn set_fee_factor(_id: Self::Id, val: FixedU128) {
|
||||
let mut bridge = Self::bridge();
|
||||
bridge.delivery_fee_factor = val;
|
||||
Bridge::<T, I>::put(bridge);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezframe_support::assert_ok;
|
||||
use mock::*;
|
||||
|
||||
use pezframe_support::traits::Hooks;
|
||||
use pezframe_system::{EventRecord, Phase};
|
||||
use pezsp_runtime::traits::One;
|
||||
|
||||
fn congested_bridge(delivery_fee_factor: FixedU128) -> BridgeState {
|
||||
BridgeState { is_congested: true, delivery_fee_factor }
|
||||
}
|
||||
|
||||
fn uncongested_bridge(delivery_fee_factor: FixedU128) -> BridgeState {
|
||||
BridgeState { is_congested: false, delivery_fee_factor }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_fee_factor_is_one() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
Bridge::<TestRuntime, ()>::get(),
|
||||
uncongested_bridge(Pezpallet::<TestRuntime, ()>::MIN_FEE_FACTOR),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_factor_is_not_decreased_from_on_initialize_when_xcm_channel_is_congested() {
|
||||
run_test(|| {
|
||||
Bridge::<TestRuntime, ()>::put(uncongested_bridge(FixedU128::from_rational(125, 100)));
|
||||
TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get());
|
||||
|
||||
// it should not decrease, because queue is congested
|
||||
let old_delivery = XcmBridgeHubRouter::bridge();
|
||||
XcmBridgeHubRouter::on_initialize(One::one());
|
||||
assert_eq!(XcmBridgeHubRouter::bridge(), old_delivery);
|
||||
assert_eq!(System::events(), vec![]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_factor_is_not_decreased_from_on_initialize_when_bridge_has_reported_congestion() {
|
||||
run_test(|| {
|
||||
Bridge::<TestRuntime, ()>::put(congested_bridge(FixedU128::from_rational(125, 100)));
|
||||
|
||||
// it should not decrease, because bridge congested
|
||||
let old_bridge = XcmBridgeHubRouter::bridge();
|
||||
XcmBridgeHubRouter::on_initialize(One::one());
|
||||
assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge);
|
||||
assert_eq!(System::events(), vec![]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_factor_is_decreased_from_on_initialize_when_xcm_channel_is_uncongested() {
|
||||
run_test(|| {
|
||||
let initial_fee_factor = FixedU128::from_rational(125, 100);
|
||||
Bridge::<TestRuntime, ()>::put(uncongested_bridge(initial_fee_factor));
|
||||
|
||||
// it should eventually decrease to one
|
||||
while XcmBridgeHubRouter::bridge().delivery_fee_factor >
|
||||
Pezpallet::<TestRuntime, ()>::MIN_FEE_FACTOR
|
||||
{
|
||||
XcmBridgeHubRouter::on_initialize(One::one());
|
||||
}
|
||||
|
||||
// verify that it doesn't decrease anymore
|
||||
XcmBridgeHubRouter::on_initialize(One::one());
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::bridge(),
|
||||
uncongested_bridge(Pezpallet::<TestRuntime, ()>::MIN_FEE_FACTOR)
|
||||
);
|
||||
|
||||
// check emitted event
|
||||
let first_system_event = System::events().first().cloned();
|
||||
assert_eq!(
|
||||
first_system_event,
|
||||
Some(EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::XcmBridgeHubRouter(Event::DeliveryFeeFactorDecreased {
|
||||
new_value: initial_fee_factor / XcmBridgeHubRouter::EXPONENTIAL_FEE_BASE,
|
||||
}),
|
||||
topics: vec![],
|
||||
})
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_applicable_if_destination_is_within_other_network() {
|
||||
run_test(|| {
|
||||
// unroutable dest
|
||||
let dest = Location::new(2, [GlobalConsensus(ByGenesis([0; 32])), Teyrchain(1000)]);
|
||||
let xcm: Xcm<()> = vec![ClearOrigin].into();
|
||||
|
||||
// check that router does not consume when `NotApplicable`
|
||||
let mut xcm_wrapper = Some(xcm.clone());
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper),
|
||||
Err(SendError::NotApplicable),
|
||||
);
|
||||
// XCM is NOT consumed and untouched
|
||||
assert_eq!(Some(xcm.clone()), xcm_wrapper);
|
||||
|
||||
// check the full `send_xcm`
|
||||
assert_eq!(send_xcm::<XcmBridgeHubRouter>(dest, xcm,), Err(SendError::NotApplicable),);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exceeds_max_message_size_if_size_is_above_hard_limit() {
|
||||
run_test(|| {
|
||||
// routable dest with XCM version
|
||||
let dest =
|
||||
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)]);
|
||||
// oversized XCM
|
||||
let xcm: Xcm<()> = vec![ClearOrigin; HARD_MESSAGE_SIZE_LIMIT as usize].into();
|
||||
|
||||
// dest is routable with the inner router
|
||||
assert_ok!(ViaBridgeHubExporter::<TestRuntime, ()>::validate(
|
||||
&mut Some(dest.clone()),
|
||||
&mut Some(xcm.clone())
|
||||
));
|
||||
|
||||
// check for oversized message
|
||||
let mut xcm_wrapper = Some(xcm.clone());
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper),
|
||||
Err(SendError::ExceedsMaxMessageSize),
|
||||
);
|
||||
// XCM is consumed by the inner router
|
||||
assert!(xcm_wrapper.is_none());
|
||||
|
||||
// check the full `send_xcm`
|
||||
assert_eq!(
|
||||
send_xcm::<XcmBridgeHubRouter>(dest, xcm,),
|
||||
Err(SendError::ExceedsMaxMessageSize),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destination_unsupported_if_wrap_version_fails() {
|
||||
run_test(|| {
|
||||
// routable dest but we don't know XCM version
|
||||
let dest = UnknownXcmVersionForRoutableLocation::get();
|
||||
let xcm: Xcm<()> = vec![ClearOrigin].into();
|
||||
|
||||
// dest is routable with the inner router
|
||||
assert_ok!(ViaBridgeHubExporter::<TestRuntime, ()>::validate(
|
||||
&mut Some(dest.clone()),
|
||||
&mut Some(xcm.clone())
|
||||
));
|
||||
|
||||
// check that it does not pass XCM version check
|
||||
let mut xcm_wrapper = Some(xcm.clone());
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper),
|
||||
Err(SendError::DestinationUnsupported),
|
||||
);
|
||||
// XCM is consumed by the inner router
|
||||
assert!(xcm_wrapper.is_none());
|
||||
|
||||
// check the full `send_xcm`
|
||||
assert_eq!(
|
||||
send_xcm::<XcmBridgeHubRouter>(dest, xcm,),
|
||||
Err(SendError::DestinationUnsupported),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_proper_delivery_price() {
|
||||
run_test(|| {
|
||||
let dest = Location::new(2, [GlobalConsensus(BridgedNetworkId::get())]);
|
||||
let xcm: Xcm<()> = vec![ClearOrigin].into();
|
||||
let msg_size = xcm.encoded_size();
|
||||
|
||||
// initially the base fee is used: `BASE_FEE + BYTE_FEE * msg_size + HRMP_FEE`
|
||||
let expected_fee = BASE_FEE + BYTE_FEE * (msg_size as u128) + HRMP_FEE;
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut Some(xcm.clone()))
|
||||
.unwrap()
|
||||
.1
|
||||
.get(0),
|
||||
Some(&(BridgeFeeAsset::get(), expected_fee).into()),
|
||||
);
|
||||
|
||||
// but when factor is larger than one, it increases the fee, so it becomes:
|
||||
// `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE`
|
||||
let factor = FixedU128::from_rational(125, 100);
|
||||
Bridge::<TestRuntime, ()>::put(uncongested_bridge(factor));
|
||||
let expected_fee =
|
||||
(FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) *
|
||||
factor)
|
||||
.into_inner() / FixedU128::DIV +
|
||||
HRMP_FEE;
|
||||
assert_eq!(
|
||||
XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm)).unwrap().1.get(0),
|
||||
Some(&(BridgeFeeAsset::get(), expected_fee).into()),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sent_message_doesnt_increase_factor_if_queue_is_uncongested() {
|
||||
run_test(|| {
|
||||
let old_bridge = XcmBridgeHubRouter::bridge();
|
||||
assert_eq!(
|
||||
send_xcm::<XcmBridgeHubRouter>(
|
||||
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)]),
|
||||
vec![ClearOrigin].into(),
|
||||
)
|
||||
.map(drop),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
assert!(TestToBridgeHubSender::is_message_sent());
|
||||
assert_eq!(old_bridge, XcmBridgeHubRouter::bridge());
|
||||
|
||||
assert_eq!(System::events(), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sent_message_increases_factor_if_xcm_channel_is_congested() {
|
||||
run_test(|| {
|
||||
TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get());
|
||||
|
||||
let old_bridge = XcmBridgeHubRouter::bridge();
|
||||
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
|
||||
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)]),
|
||||
vec![ClearOrigin].into(),
|
||||
)
|
||||
.map(drop));
|
||||
|
||||
assert!(TestToBridgeHubSender::is_message_sent());
|
||||
assert!(
|
||||
old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
|
||||
);
|
||||
|
||||
// check emitted event
|
||||
let first_system_event = System::events().first().cloned();
|
||||
assert!(matches!(
|
||||
first_system_event,
|
||||
Some(EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::XcmBridgeHubRouter(
|
||||
Event::DeliveryFeeFactorIncreased { .. }
|
||||
),
|
||||
..
|
||||
})
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sent_message_increases_factor_if_bridge_has_reported_congestion() {
|
||||
run_test(|| {
|
||||
Bridge::<TestRuntime, ()>::put(congested_bridge(
|
||||
Pezpallet::<TestRuntime, ()>::MIN_FEE_FACTOR,
|
||||
));
|
||||
|
||||
let old_bridge = XcmBridgeHubRouter::bridge();
|
||||
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
|
||||
Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)]),
|
||||
vec![ClearOrigin].into(),
|
||||
)
|
||||
.map(drop));
|
||||
|
||||
assert!(TestToBridgeHubSender::is_message_sent());
|
||||
assert!(
|
||||
old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
|
||||
);
|
||||
|
||||
// check emitted event
|
||||
let first_system_event = System::events().first().cloned();
|
||||
assert!(matches!(
|
||||
first_system_event,
|
||||
Some(EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: RuntimeEvent::XcmBridgeHubRouter(
|
||||
Event::DeliveryFeeFactorIncreased { .. }
|
||||
),
|
||||
..
|
||||
})
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_messages_does_not_return_anything() {
|
||||
run_test(|| {
|
||||
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
|
||||
(Parent, Parent, GlobalConsensus(BridgedNetworkId::get()), Teyrchain(1000)).into(),
|
||||
vec![ClearOrigin].into()
|
||||
));
|
||||
assert_eq!(XcmBridgeHubRouter::get_messages(), vec![]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pezpallet_xcm_bridge_hub_router;
|
||||
|
||||
use bp_xcm_bridge_hub_router::XcmChannelStatusProvider;
|
||||
use codec::Encode;
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types,
|
||||
traits::{Contains, Equals},
|
||||
};
|
||||
use pezsp_runtime::{traits::ConstU128, BuildStorage};
|
||||
use pezsp_std::cell::RefCell;
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{InspectMessageQueues, NetworkExportTable, NetworkExportTableItem};
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
/// HRMP fee.
|
||||
pub const HRMP_FEE: u128 = 500;
|
||||
/// Base bridge fee.
|
||||
pub const BASE_FEE: u128 = 1_000_000;
|
||||
/// Byte bridge fee.
|
||||
pub const BYTE_FEE: u128 = 1_000;
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime
|
||||
{
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
XcmBridgeHubRouter: pezpallet_xcm_bridge_hub_router::{Pezpallet, Storage, Event<T>},
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub ThisNetworkId: NetworkId = Pezkuwi;
|
||||
pub BridgedNetworkId: NetworkId = Kusama;
|
||||
pub UniversalLocation: InteriorLocation = [GlobalConsensus(ThisNetworkId::get()), Teyrchain(1000)].into();
|
||||
pub SiblingBridgeHubLocation: Location = ParentThen([Teyrchain(1002)].into()).into();
|
||||
pub BridgeFeeAsset: AssetId = Location::parent().into();
|
||||
pub BridgeTable: Vec<NetworkExportTableItem>
|
||||
= vec![
|
||||
NetworkExportTableItem::new(
|
||||
BridgedNetworkId::get(),
|
||||
None,
|
||||
SiblingBridgeHubLocation::get(),
|
||||
Some((BridgeFeeAsset::get(), BASE_FEE).into())
|
||||
)
|
||||
];
|
||||
pub UnknownXcmVersionForRoutableLocation: Location = Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Teyrchain(9999)]);
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
impl pezpallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type SiblingBridgeHubLocation = SiblingBridgeHubLocation;
|
||||
type BridgedNetworkId = BridgedNetworkId;
|
||||
type Bridges = NetworkExportTable<BridgeTable>;
|
||||
type DestinationVersion =
|
||||
LatestOrNoneForLocationVersionChecker<Equals<UnknownXcmVersionForRoutableLocation>>;
|
||||
|
||||
type BridgeHubOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type ToBridgeHubSender = TestToBridgeHubSender;
|
||||
type LocalXcmChannelManager = TestLocalXcmChannelManager;
|
||||
|
||||
type ByteFee = ConstU128<BYTE_FEE>;
|
||||
type FeeAsset = BridgeFeeAsset;
|
||||
}
|
||||
|
||||
pub struct LatestOrNoneForLocationVersionChecker<Location>(pezsp_std::marker::PhantomData<Location>);
|
||||
impl<LocationValue: Contains<Location>> GetVersion
|
||||
for LatestOrNoneForLocationVersionChecker<LocationValue>
|
||||
{
|
||||
fn get_version_for(dest: &Location) -> Option<XcmVersion> {
|
||||
if LocationValue::contains(dest) {
|
||||
return None;
|
||||
}
|
||||
Some(XCM_VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestToBridgeHubSender;
|
||||
|
||||
impl TestToBridgeHubSender {
|
||||
pub fn is_message_sent() -> bool {
|
||||
!Self::get_messages().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static SENT_XCM: RefCell<Vec<(Location, Xcm<()>)>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
impl SendXcm for TestToBridgeHubSender {
|
||||
type Ticket = (Location, Xcm<()>);
|
||||
|
||||
fn validate(
|
||||
destination: &mut Option<Location>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
let pair = (destination.take().unwrap(), message.take().unwrap());
|
||||
Ok((pair, (BridgeFeeAsset::get(), HRMP_FEE).into()))
|
||||
}
|
||||
|
||||
fn deliver(pair: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
let hash = fake_message_hash(&pair.1);
|
||||
SENT_XCM.with(|q| q.borrow_mut().push(pair));
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl InspectMessageQueues for TestToBridgeHubSender {
|
||||
fn clear_messages() {
|
||||
SENT_XCM.with(|q| q.borrow_mut().clear());
|
||||
}
|
||||
|
||||
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
|
||||
SENT_XCM.with(|q| {
|
||||
(*q.borrow())
|
||||
.clone()
|
||||
.iter()
|
||||
.map(|(location, message)| {
|
||||
(
|
||||
VersionedLocation::from(location.clone()),
|
||||
vec![VersionedXcm::from(message.clone())],
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestLocalXcmChannelManager;
|
||||
|
||||
impl TestLocalXcmChannelManager {
|
||||
pub fn make_congested(with: &Location) {
|
||||
pezframe_support::storage::unhashed::put(
|
||||
&(b"TestLocalXcmChannelManager.Congested", with).encode()[..],
|
||||
&true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl XcmChannelStatusProvider for TestLocalXcmChannelManager {
|
||||
fn is_congested(with: &Location) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(
|
||||
&(b"TestLocalXcmChannelManager.Congested", with).encode()[..],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return test externalities to use in tests.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
|
||||
pezsp_io::TestExternalities::new(t)
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
System::reset_events();
|
||||
|
||||
test()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn fake_message_hash<T>(message: &Xcm<T>) -> XcmHash {
|
||||
message.using_encoded(pezsp_io::hashing::blake2_256)
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for pezpallet_xcm_bridge_hub_router
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-08-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `covid`, CPU: `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz`
|
||||
//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/rip-bridge-node
|
||||
// benchmark
|
||||
// pezpallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pezpallet=pezpallet_xcm_bridge_hub_router
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=Compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./modules/xcm-bridge-hub-router/src/weights.rs
|
||||
// --template=./.maintain/bridge-weight-template.hbs
|
||||
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pezframe_support::{
|
||||
traits::Get,
|
||||
weights::{constants::RocksDbWeight, Weight},
|
||||
};
|
||||
use pezsp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pezpallet_xcm_bridge_hub_router.
|
||||
pub trait WeightInfo {
|
||||
fn on_initialize_when_non_congested() -> Weight;
|
||||
fn on_initialize_when_congested() -> Weight;
|
||||
fn report_bridge_status() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_xcm_bridge_hub_router` that are generated using one of the Bridge testnets.
|
||||
///
|
||||
/// Those weights are test only and must never be used in production.
|
||||
pub struct BridgeWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BridgeWeight<T> {
|
||||
///
|
||||
/// Storage: `XcmBridgeHubRouter::DeliveryFeeFactor` (r:1 w:1)
|
||||
///
|
||||
/// Proof: `XcmBridgeHubRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16),
|
||||
/// added: 511, mode: `MaxEncodedLen`)
|
||||
fn on_initialize_when_non_congested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `52`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 11_141 nanoseconds.
|
||||
Weight::from_parts(11_339_000, 3517)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
|
||||
/// (r:1 w:0)
|
||||
///
|
||||
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
|
||||
/// w:0)
|
||||
fn on_initialize_when_congested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `82`
|
||||
// Estimated: `3547`
|
||||
// Minimum execution time: 4_239 nanoseconds.
|
||||
Weight::from_parts(4_383_000, 3547).saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
}
|
||||
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
|
||||
///
|
||||
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
|
||||
/// 512, mode: `MaxEncodedLen`)
|
||||
fn report_bridge_status() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `53`
|
||||
// Estimated: `1502`
|
||||
// Minimum execution time: 10_427 nanoseconds.
|
||||
Weight::from_parts(10_682_000, 1502)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
|
||||
/// (r:1 w:0)
|
||||
///
|
||||
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
|
||||
/// w:0)
|
||||
///
|
||||
/// Storage: `XcmBridgeHubRouter::DeliveryFeeFactor` (r:1 w:1)
|
||||
///
|
||||
/// Proof: `XcmBridgeHubRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16),
|
||||
/// added: 511, mode: `MaxEncodedLen`)
|
||||
fn on_initialize_when_non_congested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `52`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 11_141 nanoseconds.
|
||||
Weight::from_parts(11_339_000, 3517)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765`
|
||||
/// (r:1 w:0)
|
||||
///
|
||||
/// Proof: UNKNOWN KEY `0x456d756c617465645369626c696e6758636d704368616e6e656c2e436f6e6765` (r:1
|
||||
/// w:0)
|
||||
fn on_initialize_when_congested() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `82`
|
||||
// Estimated: `3547`
|
||||
// Minimum execution time: 4_239 nanoseconds.
|
||||
Weight::from_parts(4_383_000, 3547).saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
}
|
||||
/// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1)
|
||||
///
|
||||
/// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added:
|
||||
/// 512, mode: `MaxEncodedLen`)
|
||||
fn report_bridge_status() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `53`
|
||||
// Estimated: `1502`
|
||||
// Minimum execution time: 10_427 nanoseconds.
|
||||
Weight::from_parts(10_682_000, 1502)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
[package]
|
||||
name = "pezpallet-xcm-bridge-hub"
|
||||
description = "Module that adds dynamic bridges/lanes support to XCM infrastructure at the bridge hub."
|
||||
version = "0.2.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezpallet-xcm-bridge-hub"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-messages = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
bp-xcm-bridge-hub = { workspace = true }
|
||||
pezpallet-bridge-messages = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
# Pezkuwi Dependencies
|
||||
xcm = { workspace = true }
|
||||
xcm-builder = { workspace = true }
|
||||
xcm-executor = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-xcm-bridge-hub-router = { workspace = true }
|
||||
pezpallet-balances = { workspace = true }
|
||||
pezpallet-xcm-bridge-hub-router = { workspace = true }
|
||||
pezkuwi-teyrchain-primitives = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-messages/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-xcm-bridge-hub-router/std",
|
||||
"bp-xcm-bridge-hub/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-bridge-messages/std",
|
||||
"pezpallet-xcm-bridge-hub-router/std",
|
||||
"pezkuwi-teyrchain-primitives/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"tracing/std",
|
||||
"xcm-builder/std",
|
||||
"xcm-executor/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-xcm-bridge-hub-router/runtime-benchmarks",
|
||||
"bp-xcm-bridge-hub/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-bridge-messages/runtime-benchmarks",
|
||||
"pezpallet-xcm-bridge-hub-router/runtime-benchmarks",
|
||||
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"xcm-builder/runtime-benchmarks",
|
||||
"xcm-executor/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-bridge-messages/try-runtime",
|
||||
"pezpallet-xcm-bridge-hub-router/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,278 @@
|
||||
// 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/>.
|
||||
|
||||
//! The code that allows to use the pezpallet (`pezpallet-xcm-bridge-hub`) as inbound
|
||||
//! bridge messages dispatcher. Internally, it just forwards inbound blob to the
|
||||
//! XCM-level blob dispatcher, which pushes message to some other queue (e.g.
|
||||
//! to HRMP queue with the sibling target chain).
|
||||
//!
|
||||
//! This code is executed at the target bridge hub.
|
||||
|
||||
use crate::{Config, Pezpallet, LOG_TARGET};
|
||||
|
||||
use bp_messages::target_chain::{DispatchMessage, MessageDispatch};
|
||||
use pezbp_runtime::messages::MessageDispatchResult;
|
||||
use bp_xcm_bridge_hub::{LocalXcmChannelManager, XcmAsPlainPayload};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_support::{weights::Weight, CloneNoBound, EqNoBound, PartialEqNoBound};
|
||||
use pezpallet_bridge_messages::{Config as BridgeMessagesConfig, WeightInfoExt};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::SaturatedConversion;
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{DispatchBlob, DispatchBlobError};
|
||||
|
||||
/// Message dispatch result type for single message.
|
||||
#[derive(
|
||||
CloneNoBound,
|
||||
EqNoBound,
|
||||
PartialEqNoBound,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Debug,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub enum XcmBlobMessageDispatchResult {
|
||||
/// We've been unable to decode message payload.
|
||||
InvalidPayload,
|
||||
/// Message has been dispatched.
|
||||
Dispatched,
|
||||
/// Message has **NOT** been dispatched because of given error.
|
||||
NotDispatched(#[codec(skip)] Option<DispatchBlobError>),
|
||||
}
|
||||
|
||||
/// An easy way to access associated messages pezpallet weights.
|
||||
type MessagesPalletWeights<T, I> =
|
||||
<T as BridgeMessagesConfig<<T as Config<I>>::BridgeMessagesPalletInstance>>::WeightInfo;
|
||||
|
||||
impl<T: Config<I>, I: 'static> MessageDispatch for Pezpallet<T, I>
|
||||
where
|
||||
T: BridgeMessagesConfig<T::BridgeMessagesPalletInstance, InboundPayload = XcmAsPlainPayload>,
|
||||
{
|
||||
type DispatchPayload = XcmAsPlainPayload;
|
||||
type DispatchLevelResult = XcmBlobMessageDispatchResult;
|
||||
type LaneId = T::LaneId;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
Pezpallet::<T, I>::bridge_by_lane_id(&lane)
|
||||
.and_then(|(_, bridge)| (*bridge.bridge_origin_relative_location).try_into().ok())
|
||||
.map(|recipient: Location| !T::LocalXcmChannelManager::is_congested(&recipient))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
match message.data.payload {
|
||||
Ok(ref payload) => {
|
||||
let payload_size = payload.encoded_size().saturated_into();
|
||||
MessagesPalletWeights::<T, I>::message_dispatch_weight(payload_size)
|
||||
},
|
||||
Err(_) => Weight::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
message: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
let payload = match message.data.payload {
|
||||
Ok(payload) => payload,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
lane_id=?message.key.lane_id,
|
||||
message_nonce=?message.key.nonce,
|
||||
"dispatch - payload error"
|
||||
);
|
||||
return MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
|
||||
};
|
||||
},
|
||||
};
|
||||
let dispatch_level_result = match T::BlobDispatcher::dispatch_blob(payload) {
|
||||
Ok(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
lane_id=?message.key.lane_id,
|
||||
message_nonce=?message.key.nonce,
|
||||
"dispatch - `DispatchBlob::dispatch_blob` was ok"
|
||||
);
|
||||
XcmBlobMessageDispatchResult::Dispatched
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
lane_id=?message.key.lane_id,
|
||||
message_nonce=?message.key.nonce,
|
||||
"dispatch - `DispatchBlob::dispatch_blob` failed"
|
||||
);
|
||||
XcmBlobMessageDispatchResult::NotDispatched(Some(e))
|
||||
},
|
||||
};
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf};
|
||||
|
||||
use bp_messages::{target_chain::DispatchMessageData, LaneIdType, MessageKey};
|
||||
use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState};
|
||||
use pezframe_support::assert_ok;
|
||||
use pezpallet_bridge_messages::InboundLaneStorage;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
fn bridge() -> (Box<BridgeLocations>, TestLaneIdType) {
|
||||
let origin = OpenBridgeOrigin::sibling_teyrchain_origin();
|
||||
let with = bridged_asset_hub_universal_location();
|
||||
let locations =
|
||||
XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap();
|
||||
let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
|
||||
(locations, lane_id)
|
||||
}
|
||||
|
||||
fn run_test_with_opened_bridge(test: impl FnOnce()) {
|
||||
run_test(|| {
|
||||
let (bridge, lane_id) = bridge();
|
||||
|
||||
if !Bridges::<TestRuntime, ()>::contains_key(bridge.bridge_id()) {
|
||||
// insert bridge
|
||||
Bridges::<TestRuntime, ()>::insert(
|
||||
bridge.bridge_id(),
|
||||
Bridge {
|
||||
bridge_origin_relative_location: Box::new(
|
||||
bridge.bridge_origin_relative_location().clone().into(),
|
||||
),
|
||||
bridge_origin_universal_location: Box::new(
|
||||
bridge.bridge_origin_universal_location().clone().into(),
|
||||
),
|
||||
bridge_destination_universal_location: Box::new(
|
||||
bridge.bridge_destination_universal_location().clone().into(),
|
||||
),
|
||||
state: BridgeState::Opened,
|
||||
bridge_owner_account: LocationToAccountId::convert_location(
|
||||
bridge.bridge_origin_relative_location(),
|
||||
)
|
||||
.expect("valid accountId"),
|
||||
deposit: 0,
|
||||
lane_id,
|
||||
},
|
||||
);
|
||||
LaneToBridge::<TestRuntime, ()>::insert(lane_id, bridge.bridge_id());
|
||||
|
||||
// create lanes
|
||||
let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
|
||||
if lanes_manager.create_inbound_lane(lane_id).is_ok() {
|
||||
assert_eq!(
|
||||
0,
|
||||
lanes_manager
|
||||
.active_inbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.storage()
|
||||
.data()
|
||||
.last_confirmed_nonce
|
||||
);
|
||||
}
|
||||
if lanes_manager.create_outbound_lane(lane_id).is_ok() {
|
||||
assert!(lanes_manager
|
||||
.active_outbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.is_empty());
|
||||
}
|
||||
}
|
||||
assert_ok!(XcmOverBridge::do_try_state());
|
||||
|
||||
test();
|
||||
});
|
||||
}
|
||||
|
||||
fn invalid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
|
||||
DispatchMessage {
|
||||
key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
|
||||
data: DispatchMessageData { payload: Err(codec::Error::from("test")) },
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
|
||||
DispatchMessage {
|
||||
key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
|
||||
data: DispatchMessageData { payload: Ok(vec![42]) },
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatcher_is_inactive_when_channel_with_target_chain_is_congested() {
|
||||
run_test_with_opened_bridge(|| {
|
||||
TestLocalXcmChannelManager::make_congested();
|
||||
assert!(!XcmOverBridge::is_active(bridge().1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatcher_is_active_when_channel_with_target_chain_is_not_congested() {
|
||||
run_test_with_opened_bridge(|| {
|
||||
assert!(XcmOverBridge::is_active(bridge().1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_weight_is_zero_if_we_have_failed_to_decode_message() {
|
||||
run_test(|| {
|
||||
assert_eq!(XcmOverBridge::dispatch_weight(&mut invalid_message()), Weight::zero());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_weight_is_non_zero_if_we_have_decoded_message() {
|
||||
run_test(|| {
|
||||
assert_ne!(XcmOverBridge::dispatch_weight(&mut valid_message()), Weight::zero());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_is_not_dispatched_when_we_have_failed_to_decode_message() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
XcmOverBridge::dispatch(invalid_message()),
|
||||
MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
|
||||
},
|
||||
);
|
||||
assert!(!TestBlobDispatcher::is_dispatched());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_is_dispatched_when_we_have_decoded_message() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
XcmOverBridge::dispatch(valid_message()),
|
||||
MessageDispatchResult {
|
||||
unspent_weight: Weight::zero(),
|
||||
dispatch_level_result: XcmBlobMessageDispatchResult::Dispatched,
|
||||
},
|
||||
);
|
||||
assert!(TestBlobDispatcher::is_dispatched());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,873 @@
|
||||
// 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/>.
|
||||
|
||||
//! The code that allows to use the pezpallet (`pezpallet-xcm-bridge-hub`) as XCM message
|
||||
//! exporter at the sending bridge hub. Internally, it just enqueues outbound blob
|
||||
//! in the messages pezpallet queue.
|
||||
//!
|
||||
//! This code is executed at the source bridge hub.
|
||||
|
||||
use crate::{Config, Pezpallet, LOG_TARGET};
|
||||
|
||||
use crate::{BridgeOf, Bridges};
|
||||
|
||||
use bp_messages::{
|
||||
source_chain::{MessagesBridge, OnMessagesDelivered},
|
||||
MessageNonce,
|
||||
};
|
||||
use bp_xcm_bridge_hub::{BridgeId, BridgeState, LocalXcmChannelManager, XcmAsPlainPayload};
|
||||
use pezframe_support::{ensure, traits::Get};
|
||||
use pezpallet_bridge_messages::{
|
||||
Config as BridgeMessagesConfig, Error, Pezpallet as BridgeMessagesPallet,
|
||||
};
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{HaulBlob, HaulBlobError, HaulBlobExporter};
|
||||
use xcm_executor::traits::ExportXcm;
|
||||
|
||||
/// Maximal number of messages in the outbound bridge queue. Once we reach this limit, we
|
||||
/// suspend a bridge.
|
||||
const OUTBOUND_LANE_CONGESTED_THRESHOLD: MessageNonce = 8_192;
|
||||
|
||||
/// After we have suspended the bridge, we wait until number of messages in the outbound bridge
|
||||
/// queue drops to this count, before sending resuming the bridge.
|
||||
const OUTBOUND_LANE_UNCONGESTED_THRESHOLD: MessageNonce = 1_024;
|
||||
|
||||
/// An easy way to access `HaulBlobExporter`.
|
||||
pub type PalletAsHaulBlobExporter<T, I> = HaulBlobExporter<
|
||||
DummyHaulBlob,
|
||||
<T as Config<I>>::BridgedNetwork,
|
||||
<T as Config<I>>::DestinationVersion,
|
||||
<T as Config<I>>::MessageExportPrice,
|
||||
>;
|
||||
/// An easy way to access associated messages pezpallet.
|
||||
type MessagesPallet<T, I> = BridgeMessagesPallet<T, <T as Config<I>>::BridgeMessagesPalletInstance>;
|
||||
|
||||
impl<T: Config<I>, I: 'static> ExportXcm for Pezpallet<T, I>
|
||||
where
|
||||
T: BridgeMessagesConfig<T::BridgeMessagesPalletInstance, OutboundPayload = XcmAsPlainPayload>,
|
||||
{
|
||||
type Ticket = (
|
||||
BridgeId,
|
||||
BridgeOf<T, I>,
|
||||
<MessagesPallet<T, I> as MessagesBridge<T::OutboundPayload, T::LaneId>>::SendMessageArgs,
|
||||
XcmHash,
|
||||
);
|
||||
|
||||
fn validate(
|
||||
network: NetworkId,
|
||||
channel: u32,
|
||||
universal_source: &mut Option<InteriorLocation>,
|
||||
destination: &mut Option<InteriorLocation>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> Result<(Self::Ticket, Assets), SendError> {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?network,
|
||||
?channel,
|
||||
?universal_source,
|
||||
?destination,
|
||||
"Validate for network"
|
||||
);
|
||||
|
||||
// `HaulBlobExporter` may consume the `universal_source` and `destination` arguments, so
|
||||
// let's save them before
|
||||
let bridge_origin_universal_location =
|
||||
universal_source.clone().ok_or(SendError::MissingArgument)?;
|
||||
// Note: watch out this is `ExportMessage::destination`, which is relative to the `network`,
|
||||
// which means it does not contain `GlobalConsensus`, We need to find `BridgeId` with
|
||||
// `Self::bridge_locations` which requires **universal** location for destination.
|
||||
let bridge_destination_universal_location = {
|
||||
let dest = destination.clone().ok_or(SendError::MissingArgument)?;
|
||||
match dest.global_consensus() {
|
||||
Ok(dest_network) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?dest,
|
||||
?dest_network,
|
||||
?network,
|
||||
"Destination is already universal, checking if matches: {:?}",
|
||||
dest_network == network
|
||||
);
|
||||
ensure!(dest_network == network, SendError::NotApplicable);
|
||||
// ok, `dest` looks like a universal location, so let's use it
|
||||
dest
|
||||
},
|
||||
Err(_) => {
|
||||
// `dest` is not a universal location, so we need to prepend it with
|
||||
// `GlobalConsensus`.
|
||||
dest.pushed_front_with(GlobalConsensus(network)).map_err(|error_data| {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET, error=?error_data,
|
||||
"Destination is not a universal and prepending failed!"
|
||||
);
|
||||
SendError::NotApplicable
|
||||
})?
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// prepare the origin relative location
|
||||
let bridge_origin_relative_location =
|
||||
bridge_origin_universal_location.relative_to(&T::UniversalLocation::get());
|
||||
|
||||
// then we are able to compute the `BridgeId` and find `LaneId` used to send messages
|
||||
let locations = Self::bridge_locations(
|
||||
bridge_origin_relative_location,
|
||||
bridge_destination_universal_location.into(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET, error=?e,
|
||||
"Validate `bridge_locations` with error"
|
||||
);
|
||||
SendError::NotApplicable
|
||||
})?;
|
||||
let bridge = Self::bridge(locations.bridge_id()).ok_or_else(|| {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
bridge_origin_relative_location=?locations.bridge_origin_relative_location(),
|
||||
bridge_destination_universal_location=?locations.bridge_destination_universal_location(),
|
||||
"No opened bridge for requested"
|
||||
);
|
||||
SendError::NotApplicable
|
||||
})?;
|
||||
|
||||
// check if we are able to route the message. We use existing `HaulBlobExporter` for that.
|
||||
// It will make all required changes and will encode message properly, so that the
|
||||
// `DispatchBlob` at the bridged bridge hub will be able to decode it
|
||||
let ((blob, id), price) = PalletAsHaulBlobExporter::<T, I>::validate(
|
||||
network,
|
||||
channel,
|
||||
universal_source,
|
||||
destination,
|
||||
message,
|
||||
)?;
|
||||
|
||||
let bridge_message = MessagesPallet::<T, I>::validate_message(bridge.lane_id, &blob)
|
||||
.map_err(|e| {
|
||||
match e {
|
||||
Error::LanesManager(ref ei) => {
|
||||
tracing::error!(target: LOG_TARGET, error=?ei, "LanesManager")
|
||||
},
|
||||
Error::MessageRejectedByPallet(ref ei) => {
|
||||
tracing::error!(target: LOG_TARGET, error=?ei, "MessageRejectedByPallet")
|
||||
},
|
||||
Error::ReceptionConfirmation(ref ei) => {
|
||||
tracing::error!(target: LOG_TARGET, error=?ei, "ReceptionConfirmation")
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
topic_id=?id,
|
||||
bridge_id=?locations,
|
||||
lane_id=?bridge.lane_id,
|
||||
"XCM message cannot be exported"
|
||||
);
|
||||
SendError::Transport("BridgeValidateError")
|
||||
})?;
|
||||
|
||||
Ok(((*locations.bridge_id(), bridge, bridge_message, id), price))
|
||||
}
|
||||
|
||||
fn deliver(
|
||||
(bridge_id, bridge, bridge_message, id): Self::Ticket,
|
||||
) -> Result<XcmHash, SendError> {
|
||||
let artifacts = MessagesPallet::<T, I>::send_message(bridge_message);
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
topic_id=?id,
|
||||
bridge_id=?bridge_id,
|
||||
lane_id=?bridge.lane_id,
|
||||
nonce=%artifacts.nonce,
|
||||
"XCM message has been enqueued"
|
||||
);
|
||||
|
||||
// maybe we need switch to congested state
|
||||
Self::on_bridge_message_enqueued(bridge_id, bridge, artifacts.enqueued_messages);
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> OnMessagesDelivered<T::LaneId> for Pezpallet<T, I> {
|
||||
fn on_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) {
|
||||
Self::on_bridge_messages_delivered(lane_id, enqueued_messages);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
|
||||
/// Called when new message is pushed onto outbound bridge queue.
|
||||
fn on_bridge_message_enqueued(
|
||||
bridge_id: BridgeId,
|
||||
bridge: BridgeOf<T, I>,
|
||||
enqueued_messages: MessageNonce,
|
||||
) {
|
||||
// if the bridge queue is not congested, we don't want to do anything
|
||||
let is_congested = enqueued_messages > OUTBOUND_LANE_CONGESTED_THRESHOLD;
|
||||
if !is_congested {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/83 we either need fishermens
|
||||
// to watch this rule violation (suspended, but keep sending new messages), or we need a
|
||||
// hard limit for that like other XCM queues have
|
||||
|
||||
// check if the lane is already suspended. If it is, do nothing. We still accept new
|
||||
// messages to the suspended bridge, hoping that it'll be actually resumed soon
|
||||
if bridge.state == BridgeState::Suspended {
|
||||
return;
|
||||
}
|
||||
|
||||
// else - suspend the bridge
|
||||
let result_bridge_origin_relative_location =
|
||||
(*bridge.bridge_origin_relative_location).clone().try_into();
|
||||
let bridge_origin_relative_location = match &result_bridge_origin_relative_location {
|
||||
Ok(bridge_origin_relative_location) => bridge_origin_relative_location,
|
||||
Err(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?bridge_id,
|
||||
origin_location=?bridge.bridge_origin_relative_location,
|
||||
"Failed to convert"
|
||||
);
|
||||
|
||||
return;
|
||||
},
|
||||
};
|
||||
let suspend_result =
|
||||
T::LocalXcmChannelManager::suspend_bridge(bridge_origin_relative_location, bridge_id);
|
||||
match suspend_result {
|
||||
Ok(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?bridge_id,
|
||||
originated_by=?bridge.bridge_origin_relative_location,
|
||||
"Suspended"
|
||||
);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
?bridge_id,
|
||||
originated_by=?bridge.bridge_origin_relative_location,
|
||||
"Failed to suspended"
|
||||
);
|
||||
|
||||
return;
|
||||
},
|
||||
}
|
||||
|
||||
// and remember that we have suspended the bridge
|
||||
Bridges::<T, I>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Suspended;
|
||||
});
|
||||
}
|
||||
|
||||
/// Must be called whenever we receive a message delivery confirmation.
|
||||
fn on_bridge_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) {
|
||||
// if the bridge queue is still congested, we don't want to do anything
|
||||
let is_congested = enqueued_messages > OUTBOUND_LANE_UNCONGESTED_THRESHOLD;
|
||||
if is_congested {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have not suspended the bridge before (or it is closed), we don't want to do
|
||||
// anything
|
||||
let (bridge_id, bridge) = match Self::bridge_by_lane_id(&lane_id) {
|
||||
Some(bridge) if bridge.1.state == BridgeState::Suspended => bridge,
|
||||
_ => {
|
||||
// if there is no bridge or it has been closed, then we don't need to send resume
|
||||
// signal to the local origin - it has closed bridge itself, so it should have
|
||||
// alrady pruned everything else
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// else - resume the bridge
|
||||
let bridge_origin_relative_location = (*bridge.bridge_origin_relative_location).try_into();
|
||||
let bridge_origin_relative_location = match bridge_origin_relative_location {
|
||||
Ok(bridge_origin_relative_location) => bridge_origin_relative_location,
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
"Failed to convert",
|
||||
);
|
||||
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let resume_result =
|
||||
T::LocalXcmChannelManager::resume_bridge(&bridge_origin_relative_location, bridge_id);
|
||||
match resume_result {
|
||||
Ok(_) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
originated_by=?bridge_origin_relative_location,
|
||||
"Resumed",
|
||||
);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
originated_by=?bridge_origin_relative_location,
|
||||
"Failed to resume"
|
||||
);
|
||||
|
||||
return;
|
||||
},
|
||||
}
|
||||
|
||||
// and forget that we have previously suspended the bridge
|
||||
Bridges::<T, I>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Opened;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy implementation of the `HaulBlob` trait that is never called.
|
||||
///
|
||||
/// We are using `HaulBlobExporter`, which requires `HaulBlob` implementation. It assumes that
|
||||
/// there's a single channel between two bridge hubs - `HaulBlob` only accepts the blob and nothing
|
||||
/// else. But bridge messages pezpallet may have a dedicated channel (lane) for every pair of bridged
|
||||
/// chains. So we are using our own `ExportXcm` implementation, but to utilize `HaulBlobExporter` we
|
||||
/// still need this `DummyHaulBlob`.
|
||||
pub struct DummyHaulBlob;
|
||||
|
||||
impl HaulBlob for DummyHaulBlob {
|
||||
fn haul_blob(_blob: XcmAsPlainPayload) -> Result<(), HaulBlobError> {
|
||||
Err(HaulBlobError::Transport("DummyHaulBlob"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf};
|
||||
|
||||
use pezbp_runtime::RangeInclusiveExt;
|
||||
use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState};
|
||||
use pezframe_support::{assert_ok, traits::EnsureOrigin};
|
||||
use pezpallet_bridge_messages::InboundLaneStorage;
|
||||
use xcm_builder::{NetworkExportTable, UnpaidRemoteExporter};
|
||||
use xcm_executor::traits::{export_xcm, ConvertLocation};
|
||||
|
||||
fn universal_source() -> InteriorLocation {
|
||||
SiblingUniversalLocation::get()
|
||||
}
|
||||
|
||||
fn bridged_relative_destination() -> InteriorLocation {
|
||||
BridgedRelativeDestination::get()
|
||||
}
|
||||
|
||||
fn bridged_universal_destination() -> InteriorLocation {
|
||||
BridgedUniversalDestination::get()
|
||||
}
|
||||
|
||||
fn open_lane(origin: RuntimeOrigin) -> (BridgeLocations, TestLaneIdType) {
|
||||
// open expected outbound lane
|
||||
let with = bridged_asset_hub_universal_location();
|
||||
let locations =
|
||||
XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap();
|
||||
let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
|
||||
|
||||
if !Bridges::<TestRuntime, ()>::contains_key(locations.bridge_id()) {
|
||||
// insert bridge
|
||||
Bridges::<TestRuntime, ()>::insert(
|
||||
locations.bridge_id(),
|
||||
Bridge {
|
||||
bridge_origin_relative_location: Box::new(SiblingLocation::get().into()),
|
||||
bridge_origin_universal_location: Box::new(
|
||||
locations.bridge_origin_universal_location().clone().into(),
|
||||
),
|
||||
bridge_destination_universal_location: Box::new(
|
||||
locations.bridge_destination_universal_location().clone().into(),
|
||||
),
|
||||
state: BridgeState::Opened,
|
||||
bridge_owner_account: LocationToAccountId::convert_location(
|
||||
locations.bridge_origin_relative_location(),
|
||||
)
|
||||
.expect("valid accountId"),
|
||||
deposit: 0,
|
||||
lane_id,
|
||||
},
|
||||
);
|
||||
LaneToBridge::<TestRuntime, ()>::insert(lane_id, locations.bridge_id());
|
||||
|
||||
// create lanes
|
||||
let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
|
||||
if lanes_manager.create_inbound_lane(lane_id).is_ok() {
|
||||
assert_eq!(
|
||||
0,
|
||||
lanes_manager
|
||||
.active_inbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.storage()
|
||||
.data()
|
||||
.last_confirmed_nonce
|
||||
);
|
||||
}
|
||||
if lanes_manager.create_outbound_lane(lane_id).is_ok() {
|
||||
assert!(lanes_manager
|
||||
.active_outbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.is_empty());
|
||||
}
|
||||
}
|
||||
assert_ok!(XcmOverBridge::do_try_state());
|
||||
|
||||
(*locations, lane_id)
|
||||
}
|
||||
|
||||
fn open_lane_and_send_regular_message() -> (BridgeId, TestLaneIdType) {
|
||||
let (locations, lane_id) = open_lane(OpenBridgeOrigin::sibling_teyrchain_origin());
|
||||
|
||||
// now let's try to enqueue message using our `ExportXcm` implementation
|
||||
export_xcm::<XcmOverBridge>(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
locations.bridge_origin_universal_location().clone(),
|
||||
locations.bridge_destination_universal_location().clone(),
|
||||
vec![Instruction::ClearOrigin].into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
(*locations.bridge_id(), lane_id)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_works() {
|
||||
run_test(|| {
|
||||
let (_, lane_id) = open_lane_and_send_regular_message();
|
||||
|
||||
// double check that the message has been pushed to the expected lane
|
||||
// (it should already been checked during `send_message` call)
|
||||
assert!(!LanesManagerOf::<TestRuntime, ()>::new()
|
||||
.active_outbound_lane(lane_id)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_does_not_suspend_the_bridge_if_outbound_bridge_queue_is_not_congested() {
|
||||
run_test(|| {
|
||||
let (bridge_id, _) = open_lane_and_send_regular_message();
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_does_not_suspend_the_bridge_if_it_is_already_suspended() {
|
||||
run_test(|| {
|
||||
let (bridge_id, _) = open_lane_and_send_regular_message();
|
||||
Bridges::<TestRuntime, ()>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Suspended;
|
||||
});
|
||||
for _ in 1..OUTBOUND_LANE_CONGESTED_THRESHOLD {
|
||||
open_lane_and_send_regular_message();
|
||||
}
|
||||
|
||||
open_lane_and_send_regular_message();
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_suspends_the_bridge_if_outbound_bridge_queue_is_congested() {
|
||||
run_test(|| {
|
||||
let (bridge_id, _) = open_lane_and_send_regular_message();
|
||||
for _ in 1..OUTBOUND_LANE_CONGESTED_THRESHOLD {
|
||||
open_lane_and_send_regular_message();
|
||||
}
|
||||
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
|
||||
|
||||
open_lane_and_send_regular_message();
|
||||
assert!(TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_is_not_resumed_if_outbound_bridge_queue_is_still_congested() {
|
||||
run_test(|| {
|
||||
let (bridge_id, lane_id) = open_lane_and_send_regular_message();
|
||||
Bridges::<TestRuntime, ()>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Suspended;
|
||||
});
|
||||
XcmOverBridge::on_bridge_messages_delivered(
|
||||
lane_id,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1,
|
||||
);
|
||||
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_is_not_resumed_if_it_was_not_suspended_before() {
|
||||
run_test(|| {
|
||||
let (bridge_id, lane_id) = open_lane_and_send_regular_message();
|
||||
XcmOverBridge::on_bridge_messages_delivered(
|
||||
lane_id,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
|
||||
);
|
||||
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_is_resumed_when_enough_messages_are_delivered() {
|
||||
run_test(|| {
|
||||
let (bridge_id, lane_id) = open_lane_and_send_regular_message();
|
||||
Bridges::<TestRuntime, ()>::mutate_extant(bridge_id, |bridge| {
|
||||
bridge.state = BridgeState::Suspended;
|
||||
});
|
||||
XcmOverBridge::on_bridge_messages_delivered(
|
||||
lane_id,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
|
||||
);
|
||||
|
||||
assert!(TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id));
|
||||
assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_fails_if_argument_is_missing() {
|
||||
run_test(|| {
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut None,
|
||||
&mut Some(bridged_relative_destination()),
|
||||
&mut Some(Vec::new().into()),
|
||||
),
|
||||
Err(SendError::MissingArgument),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
&mut None,
|
||||
&mut Some(Vec::new().into()),
|
||||
),
|
||||
Err(SendError::MissingArgument),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_computes_correct_lane_id() {
|
||||
run_test(|| {
|
||||
assert_ne!(bridged_universal_destination(), bridged_relative_destination());
|
||||
|
||||
let locations = BridgeLocations::bridge_locations(
|
||||
UniversalLocation::get(),
|
||||
SiblingLocation::get(),
|
||||
bridged_universal_destination(),
|
||||
BridgedRelayNetwork::get(),
|
||||
)
|
||||
.unwrap();
|
||||
let expected_bridge_id = locations.bridge_id();
|
||||
let expected_lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
|
||||
|
||||
if LanesManagerOf::<TestRuntime, ()>::new()
|
||||
.create_outbound_lane(expected_lane_id)
|
||||
.is_ok()
|
||||
{
|
||||
Bridges::<TestRuntime, ()>::insert(
|
||||
expected_bridge_id,
|
||||
Bridge {
|
||||
bridge_origin_relative_location: Box::new(
|
||||
locations.bridge_origin_relative_location().clone().into(),
|
||||
),
|
||||
bridge_origin_universal_location: Box::new(
|
||||
locations.bridge_origin_universal_location().clone().into(),
|
||||
),
|
||||
bridge_destination_universal_location: Box::new(
|
||||
locations.bridge_destination_universal_location().clone().into(),
|
||||
),
|
||||
state: BridgeState::Opened,
|
||||
bridge_owner_account: [0u8; 32].into(),
|
||||
deposit: 0,
|
||||
lane_id: expected_lane_id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let ticket = XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
// Note: The `ExportMessage` expects relative `InteriorLocation` in the
|
||||
// `BridgedRelayNetwork`.
|
||||
&mut Some(bridged_relative_destination()),
|
||||
&mut Some(Vec::new().into()),
|
||||
)
|
||||
.unwrap()
|
||||
.0;
|
||||
assert_eq!(&ticket.0, expected_bridge_id);
|
||||
assert_eq!(ticket.1.lane_id, expected_lane_id);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporter_is_compatible_with_pallet_xcm_bridge_hub_router() {
|
||||
run_test(|| {
|
||||
// valid routable destination
|
||||
let dest = Location::new(2, BridgedUniversalDestination::get());
|
||||
|
||||
// open bridge
|
||||
let origin = OpenBridgeOrigin::sibling_teyrchain_origin();
|
||||
let origin_as_location =
|
||||
OpenBridgeOriginOf::<TestRuntime, ()>::try_origin(origin.clone()).unwrap();
|
||||
let (_, expected_lane_id) = open_lane(origin);
|
||||
|
||||
// check before - no messages
|
||||
assert_eq!(
|
||||
pezpallet_bridge_messages::Pezpallet::<TestRuntime, ()>::outbound_lane_data(
|
||||
expected_lane_id
|
||||
)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.saturating_len(),
|
||||
0
|
||||
);
|
||||
|
||||
// send `ExportMessage(message)` by `UnpaidRemoteExporter`.
|
||||
ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location);
|
||||
assert_ok!(send_xcm::<
|
||||
UnpaidRemoteExporter<
|
||||
NetworkExportTable<BridgeTable>,
|
||||
ExecuteXcmOverSendXcm,
|
||||
UniversalLocation,
|
||||
>,
|
||||
>(dest.clone(), Xcm::<()>::default()));
|
||||
|
||||
// we need to set `UniversalLocation` for `sibling_teyrchain_origin` for
|
||||
// `XcmOverBridgeWrappedWithExportMessageRouterInstance`.
|
||||
ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get()));
|
||||
// send `ExportMessage(message)` by `pezpallet_xcm_bridge_hub_router`.
|
||||
ExecuteXcmOverSendXcm::set_origin_for_execute(SiblingLocation::get());
|
||||
assert_ok!(send_xcm::<XcmOverBridgeWrappedWithExportMessageRouter>(
|
||||
dest.clone(),
|
||||
Xcm::<()>::default()
|
||||
));
|
||||
|
||||
// check after - a message ready to be relayed
|
||||
assert_eq!(
|
||||
pezpallet_bridge_messages::Pezpallet::<TestRuntime, ()>::outbound_lane_data(
|
||||
expected_lane_id
|
||||
)
|
||||
.unwrap()
|
||||
.queued_messages()
|
||||
.saturating_len(),
|
||||
2
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_works() {
|
||||
run_test(|| {
|
||||
let xcm: Xcm<()> = vec![ClearOrigin].into();
|
||||
|
||||
// check that router does not consume when `NotApplicable`
|
||||
let mut xcm_wrapper = Some(xcm.clone());
|
||||
let mut universal_source_wrapper = Some(universal_source());
|
||||
|
||||
// wrong `NetworkId`
|
||||
let mut dest_wrapper = Some(bridged_relative_destination());
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
NetworkId::ByGenesis([0; 32]),
|
||||
0,
|
||||
&mut universal_source_wrapper,
|
||||
&mut dest_wrapper,
|
||||
&mut xcm_wrapper,
|
||||
),
|
||||
Err(SendError::NotApplicable),
|
||||
);
|
||||
// dest and xcm is NOT consumed and untouched
|
||||
assert_eq!(&Some(xcm.clone()), &xcm_wrapper);
|
||||
assert_eq!(&Some(universal_source()), &universal_source_wrapper);
|
||||
assert_eq!(&Some(bridged_relative_destination()), &dest_wrapper);
|
||||
|
||||
// dest starts with wrong `NetworkId`
|
||||
let mut invalid_dest_wrapper = Some(
|
||||
[GlobalConsensus(NetworkId::ByGenesis([0; 32])), Teyrchain(BRIDGED_ASSET_HUB_ID)]
|
||||
.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
&mut invalid_dest_wrapper,
|
||||
&mut xcm_wrapper,
|
||||
),
|
||||
Err(SendError::NotApplicable),
|
||||
);
|
||||
// dest and xcm is NOT consumed and untouched
|
||||
assert_eq!(&Some(xcm.clone()), &xcm_wrapper);
|
||||
assert_eq!(&Some(universal_source()), &universal_source_wrapper);
|
||||
assert_eq!(
|
||||
&Some(
|
||||
[
|
||||
GlobalConsensus(NetworkId::ByGenesis([0; 32]),),
|
||||
Teyrchain(BRIDGED_ASSET_HUB_ID)
|
||||
]
|
||||
.into()
|
||||
),
|
||||
&invalid_dest_wrapper
|
||||
);
|
||||
|
||||
// no opened lane for dest
|
||||
let mut dest_without_lane_wrapper =
|
||||
Some([GlobalConsensus(BridgedRelayNetwork::get()), Teyrchain(5679)].into());
|
||||
assert_eq!(
|
||||
XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
&mut dest_without_lane_wrapper,
|
||||
&mut xcm_wrapper,
|
||||
),
|
||||
Err(SendError::NotApplicable),
|
||||
);
|
||||
// dest and xcm is NOT consumed and untouched
|
||||
assert_eq!(&Some(xcm.clone()), &xcm_wrapper);
|
||||
assert_eq!(&Some(universal_source()), &universal_source_wrapper);
|
||||
assert_eq!(
|
||||
&Some([GlobalConsensus(BridgedRelayNetwork::get(),), Teyrchain(5679)].into()),
|
||||
&dest_without_lane_wrapper
|
||||
);
|
||||
|
||||
// ok
|
||||
let _ = open_lane(OpenBridgeOrigin::sibling_teyrchain_origin());
|
||||
let mut dest_wrapper = Some(bridged_relative_destination());
|
||||
assert_ok!(XcmOverBridge::validate(
|
||||
BridgedRelayNetwork::get(),
|
||||
0,
|
||||
&mut Some(universal_source()),
|
||||
&mut dest_wrapper,
|
||||
&mut xcm_wrapper,
|
||||
));
|
||||
// dest and xcm IS consumed
|
||||
assert_eq!(None, xcm_wrapper);
|
||||
assert_eq!(&Some(universal_source()), &universal_source_wrapper);
|
||||
assert_eq!(None, dest_wrapper);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn congestion_with_pallet_xcm_bridge_hub_router_works() {
|
||||
run_test(|| {
|
||||
// valid routable destination
|
||||
let dest = Location::new(2, BridgedUniversalDestination::get());
|
||||
|
||||
fn router_bridge_state() -> pezpallet_xcm_bridge_hub_router::BridgeState {
|
||||
pezpallet_xcm_bridge_hub_router::Bridge::<
|
||||
TestRuntime,
|
||||
XcmOverBridgeWrappedWithExportMessageRouterInstance,
|
||||
>::get()
|
||||
}
|
||||
|
||||
// open two bridges
|
||||
let origin = OpenBridgeOrigin::sibling_teyrchain_origin();
|
||||
let origin_as_location =
|
||||
OpenBridgeOriginOf::<TestRuntime, ()>::try_origin(origin.clone()).unwrap();
|
||||
let (bridge_1, expected_lane_id_1) = open_lane(origin);
|
||||
|
||||
// we need to set `UniversalLocation` for `sibling_teyrchain_origin` for
|
||||
// `XcmOverBridgeWrappedWithExportMessageRouterInstance`.
|
||||
ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get()));
|
||||
|
||||
// check before
|
||||
// bridges are opened
|
||||
assert_eq!(
|
||||
XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state,
|
||||
BridgeState::Opened
|
||||
);
|
||||
|
||||
// the router is uncongested
|
||||
assert!(!router_bridge_state().is_congested);
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id()));
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
|
||||
|
||||
// make bridges congested with sending too much messages
|
||||
for _ in 1..(OUTBOUND_LANE_CONGESTED_THRESHOLD + 2) {
|
||||
// send `ExportMessage(message)` by `pezpallet_xcm_bridge_hub_router`.
|
||||
ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location.clone());
|
||||
assert_ok!(send_xcm::<XcmOverBridgeWrappedWithExportMessageRouter>(
|
||||
dest.clone(),
|
||||
Xcm::<()>::default()
|
||||
));
|
||||
}
|
||||
|
||||
// checks after
|
||||
// bridges are suspended
|
||||
assert_eq!(
|
||||
XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state,
|
||||
BridgeState::Suspended,
|
||||
);
|
||||
// the router is congested
|
||||
assert!(router_bridge_state().is_congested);
|
||||
assert!(TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id()));
|
||||
assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
|
||||
|
||||
// make bridges uncongested to trigger resume signal
|
||||
XcmOverBridge::on_bridge_messages_delivered(
|
||||
expected_lane_id_1,
|
||||
OUTBOUND_LANE_UNCONGESTED_THRESHOLD,
|
||||
);
|
||||
|
||||
// bridge is again opened
|
||||
assert_eq!(
|
||||
XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state,
|
||||
BridgeState::Opened
|
||||
);
|
||||
// the router is uncongested
|
||||
assert!(!router_bridge_state().is_congested);
|
||||
assert!(TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id()));
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,154 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! A module that is responsible for migration of storage.
|
||||
|
||||
use crate::{Config, Pezpallet, LOG_TARGET};
|
||||
use pezframe_support::{
|
||||
traits::{Get, OnRuntimeUpgrade, StorageVersion},
|
||||
weights::Weight,
|
||||
};
|
||||
use xcm::prelude::{InteriorLocation, Location};
|
||||
|
||||
/// The in-code storage version.
|
||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
|
||||
|
||||
/// This migration does not modify storage but can be used to open a bridge and link it to the
|
||||
/// specified LaneId. This is useful when we want to open a bridge and use a custom LaneId instead
|
||||
/// of the pre-calculated one provided by the `fn open_bridge extrinsic`.
|
||||
/// Or perhaps if you want to ensure that your runtime (e.g., for testing) always has an open
|
||||
/// bridge.
|
||||
pub struct OpenBridgeForLane<
|
||||
T,
|
||||
I,
|
||||
Lane,
|
||||
CreateLane,
|
||||
SourceRelativeLocation,
|
||||
BridgedUniversalLocation,
|
||||
>(
|
||||
core::marker::PhantomData<(
|
||||
T,
|
||||
I,
|
||||
Lane,
|
||||
CreateLane,
|
||||
SourceRelativeLocation,
|
||||
BridgedUniversalLocation,
|
||||
)>,
|
||||
);
|
||||
impl<
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
Lane: Get<T::LaneId>,
|
||||
CreateLane: Get<bool>,
|
||||
SourceRelativeLocation: Get<Location>,
|
||||
BridgedUniversalLocation: Get<InteriorLocation>,
|
||||
> OnRuntimeUpgrade
|
||||
for OpenBridgeForLane<T, I, Lane, CreateLane, SourceRelativeLocation, BridgedUniversalLocation>
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let bridge_origin_relative_location = SourceRelativeLocation::get();
|
||||
let bridge_destination_universal_location = BridgedUniversalLocation::get();
|
||||
let lane_id = Lane::get();
|
||||
let create_lane = CreateLane::get();
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
?create_lane,
|
||||
?bridge_origin_relative_location,
|
||||
?bridge_destination_universal_location,
|
||||
"OpenBridgeForLane - going to open bridge"
|
||||
);
|
||||
|
||||
let locations = match Pezpallet::<T, I>::bridge_locations(
|
||||
bridge_origin_relative_location.clone(),
|
||||
bridge_destination_universal_location.clone(),
|
||||
) {
|
||||
Ok(locations) => locations,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
error=?e,
|
||||
"OpenBridgeForLane - on_runtime_upgrade failed to construct bridge_locations"
|
||||
);
|
||||
return T::DbWeight::get().reads(0);
|
||||
},
|
||||
};
|
||||
|
||||
// check if already exists
|
||||
if let Some((bridge_id, bridge)) = Pezpallet::<T, I>::bridge_by_lane_id(&lane_id) {
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?bridge,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
"OpenBridgeForLane - already exist!"
|
||||
);
|
||||
if &bridge_id != locations.bridge_id() {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?bridge,
|
||||
?bridge_id,
|
||||
?lane_id,
|
||||
?bridge_origin_relative_location,
|
||||
?bridge_destination_universal_location,
|
||||
"OpenBridgeForLane - check you parameters, because a different bridge exist for requested!"
|
||||
);
|
||||
}
|
||||
|
||||
return T::DbWeight::get().reads(2);
|
||||
}
|
||||
|
||||
if let Err(e) = Pezpallet::<T, I>::do_open_bridge(locations, lane_id, create_lane) {
|
||||
tracing::error!(target: LOG_TARGET, error=?e, "OpenBridgeForLane - do_open_bridge failed");
|
||||
T::DbWeight::get().reads(6)
|
||||
} else {
|
||||
tracing::info!(target: LOG_TARGET, "OpenBridgeForLane - do_open_bridge passed!");
|
||||
T::DbWeight::get().reads_writes(6, 4)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(_state: pezsp_std::vec::Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
|
||||
let bridge_origin_relative_location = SourceRelativeLocation::get();
|
||||
let bridge_destination_universal_location = BridgedUniversalLocation::get();
|
||||
let lane_id = Lane::get();
|
||||
|
||||
// check that requested bridge is stored
|
||||
let Ok(locations) = Pezpallet::<T, I>::bridge_locations(
|
||||
bridge_origin_relative_location.clone(),
|
||||
bridge_destination_universal_location.clone(),
|
||||
) else {
|
||||
return Err(pezsp_runtime::DispatchError::Other("Invalid locations!"));
|
||||
};
|
||||
let Some((bridge_id, _)) = Pezpallet::<T, I>::bridge_by_lane_id(&lane_id) else {
|
||||
return Err(pezsp_runtime::DispatchError::Other("Missing bridge!"));
|
||||
};
|
||||
pezframe_support::ensure!(
|
||||
locations.bridge_id() == &bridge_id,
|
||||
"Bridge is not stored correctly!"
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?lane_id,
|
||||
?bridge_origin_relative_location,
|
||||
?bridge_destination_universal_location,
|
||||
"OpenBridgeForLane - post_upgrade found opened bridge"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
// 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/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pezpallet_xcm_bridge_hub;
|
||||
|
||||
use bp_messages::{
|
||||
target_chain::{DispatchMessage, MessageDispatch},
|
||||
ChainWithMessages, HashedLaneId, MessageNonce,
|
||||
};
|
||||
use pezbp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf};
|
||||
use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{
|
||||
assert_ok, derive_impl, parameter_types,
|
||||
traits::{EnsureOrigin, Equals, Everything, Get, OriginTrait},
|
||||
weights::RuntimeDbWeight,
|
||||
};
|
||||
use pezkuwi_teyrchain_primitives::primitives::Sibling;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{
|
||||
testing::Header as BizinikiwiHeader,
|
||||
traits::{BlakeTwo256, ConstU128, ConstU32, IdentityLookup},
|
||||
AccountId32, BuildStorage, StateVersion,
|
||||
};
|
||||
use pezsp_std::cell::RefCell;
|
||||
use xcm::{latest::PEZKUWICHAIN_GENESIS_HASH, prelude::*};
|
||||
use xcm_builder::{
|
||||
AllowUnpaidExecutionFrom, DispatchBlob, DispatchBlobError, FixedWeightBounds,
|
||||
InspectMessageQueues, NetworkExportTable, NetworkExportTableItem, ParentIsPreset,
|
||||
SiblingTeyrchainConvertsVia,
|
||||
};
|
||||
use xcm_executor::{traits::ConvertOrigin, XcmExecutor};
|
||||
|
||||
pub type AccountId = AccountId32;
|
||||
pub type Balance = u64;
|
||||
type Block = pezframe_system::mocking::MockBlock<TestRuntime>;
|
||||
|
||||
/// Lane identifier type used for tests.
|
||||
pub type TestLaneIdType = HashedLaneId;
|
||||
|
||||
pub const SIBLING_ASSET_HUB_ID: u32 = 2001;
|
||||
pub const THIS_BRIDGE_HUB_ID: u32 = 2002;
|
||||
pub const BRIDGED_ASSET_HUB_ID: u32 = 1001;
|
||||
|
||||
pezframe_support::construct_runtime! {
|
||||
pub enum TestRuntime {
|
||||
System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
|
||||
Balances: pezpallet_balances::{Pezpallet, Event<T>},
|
||||
Messages: pezpallet_bridge_messages::{Pezpallet, Call, Event<T>},
|
||||
XcmOverBridge: pezpallet_xcm_bridge_hub::{Pezpallet, Call, HoldReason, Event<T>},
|
||||
XcmOverBridgeWrappedWithExportMessageRouter: pezpallet_xcm_bridge_hub_router = 57,
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
|
||||
pub const ExistentialDeposit: Balance = 1;
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for TestRuntime {
|
||||
type AccountId = AccountId;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
type Block = Block;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for TestRuntime {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_messages::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = TestMessagesWeights;
|
||||
|
||||
type ThisChain = ThisUnderlyingChain;
|
||||
type BridgedChain = BridgedUnderlyingChain;
|
||||
type BridgedHeaderChain = BridgedHeaderChain;
|
||||
|
||||
type OutboundPayload = Vec<u8>;
|
||||
type InboundPayload = Vec<u8>;
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
type DeliveryPayments = ();
|
||||
type DeliveryConfirmationPayments = ();
|
||||
type OnMessagesDelivered = ();
|
||||
|
||||
type MessageDispatch = TestMessageDispatch;
|
||||
}
|
||||
|
||||
pub struct TestMessagesWeights;
|
||||
|
||||
impl pezpallet_bridge_messages::WeightInfo for TestMessagesWeights {
|
||||
fn receive_single_message_proof() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_n_messages_proof(_: u32) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_single_n_bytes_message_proof(_: u32) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_delivery_proof_for_single_message() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
fn receive_single_n_bytes_message_proof_with_dispatch(_n: u32) -> Weight {
|
||||
Weight::from_parts(1, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_bridge_messages::WeightInfoExt for TestMessagesWeights {
|
||||
fn expected_extra_storage_proof_size() -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn receive_messages_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const RelayNetwork: NetworkId = NetworkId::Kusama;
|
||||
pub UniversalLocation: InteriorLocation = [
|
||||
GlobalConsensus(RelayNetwork::get()),
|
||||
Teyrchain(THIS_BRIDGE_HUB_ID),
|
||||
].into();
|
||||
pub SiblingLocation: Location = Location::new(1, [Teyrchain(SIBLING_ASSET_HUB_ID)]);
|
||||
pub SiblingUniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Teyrchain(SIBLING_ASSET_HUB_ID)].into();
|
||||
|
||||
pub const BridgedRelayNetwork: NetworkId = NetworkId::ByGenesis([1; 32]);
|
||||
pub BridgedRelayNetworkLocation: Location = (Parent, GlobalConsensus(BridgedRelayNetwork::get())).into();
|
||||
pub BridgedRelativeDestination: InteriorLocation = [Teyrchain(BRIDGED_ASSET_HUB_ID)].into();
|
||||
pub BridgedUniversalDestination: InteriorLocation = [GlobalConsensus(BridgedRelayNetwork::get()), Teyrchain(BRIDGED_ASSET_HUB_ID)].into();
|
||||
pub const NonBridgedRelayNetwork: NetworkId = NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH);
|
||||
|
||||
pub const BridgeDeposit: Balance = 100_000;
|
||||
|
||||
// configuration for pezpallet_xcm_bridge_hub_router
|
||||
pub BridgeHubLocation: Location = Here.into();
|
||||
pub BridgeFeeAsset: AssetId = Location::here().into();
|
||||
pub BridgeTable: Vec<NetworkExportTableItem>
|
||||
= vec![
|
||||
NetworkExportTableItem::new(
|
||||
BridgedRelayNetwork::get(),
|
||||
None,
|
||||
BridgeHubLocation::get(),
|
||||
None
|
||||
)
|
||||
];
|
||||
pub UnitWeightCost: Weight = Weight::from_parts(10, 10);
|
||||
}
|
||||
|
||||
/// **Universal** `InteriorLocation` of bridged asset hub.
|
||||
pub fn bridged_asset_hub_universal_location() -> InteriorLocation {
|
||||
BridgedUniversalDestination::get()
|
||||
}
|
||||
|
||||
impl pezpallet_xcm_bridge_hub::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type BridgedNetwork = BridgedRelayNetworkLocation;
|
||||
type BridgeMessagesPalletInstance = ();
|
||||
|
||||
type MessageExportPrice = ();
|
||||
type DestinationVersion = AlwaysLatest;
|
||||
|
||||
type ForceOrigin = pezframe_system::EnsureNever<()>;
|
||||
type OpenBridgeOrigin = OpenBridgeOrigin;
|
||||
type BridgeOriginAccountIdConverter = LocationToAccountId;
|
||||
|
||||
type BridgeDeposit = BridgeDeposit;
|
||||
type Currency = Balances;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type AllowWithoutBridgeDeposit = Equals<ParentRelayChainLocation>;
|
||||
|
||||
type LocalXcmChannelManager = TestLocalXcmChannelManager;
|
||||
|
||||
type BlobDispatcher = TestBlobDispatcher;
|
||||
}
|
||||
|
||||
/// A router instance simulates a scenario where the router is deployed on a different chain than
|
||||
/// the `MessageExporter`. This means that the router sends an `ExportMessage`.
|
||||
pub type XcmOverBridgeWrappedWithExportMessageRouterInstance = ();
|
||||
impl pezpallet_xcm_bridge_hub_router::Config<XcmOverBridgeWrappedWithExportMessageRouterInstance>
|
||||
for TestRuntime
|
||||
{
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
|
||||
type UniversalLocation = ExportMessageOriginUniversalLocation;
|
||||
type SiblingBridgeHubLocation = BridgeHubLocation;
|
||||
type BridgedNetworkId = BridgedRelayNetwork;
|
||||
type Bridges = NetworkExportTable<BridgeTable>;
|
||||
type DestinationVersion = AlwaysLatest;
|
||||
|
||||
// We convert to root `here` location with `BridgeHubLocationXcmOriginAsRoot`
|
||||
type BridgeHubOrigin = pezframe_system::EnsureRoot<AccountId>;
|
||||
// **Note**: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which
|
||||
// calls the `ExportXcm` implementation of `pezpallet_xcm_bridge_hub` as the
|
||||
// `MessageExporter`.
|
||||
type ToBridgeHubSender = ExecuteXcmOverSendXcm;
|
||||
type LocalXcmChannelManager = TestLocalXcmChannelManager;
|
||||
|
||||
type ByteFee = ConstU128<0>;
|
||||
type FeeAsset = BridgeFeeAsset;
|
||||
}
|
||||
|
||||
pub struct XcmConfig;
|
||||
impl xcm_executor::Config for XcmConfig {
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type XcmSender = ();
|
||||
type XcmEventEmitter = ();
|
||||
type AssetTransactor = ();
|
||||
type OriginConverter = BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>;
|
||||
type IsReserve = ();
|
||||
type IsTeleporter = ();
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = AllowUnpaidExecutionFrom<Everything>;
|
||||
type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, ConstU32<100>>;
|
||||
type Trader = ();
|
||||
type ResponseHandler = ();
|
||||
type AssetTrap = ();
|
||||
type AssetClaims = ();
|
||||
type SubscriptionService = ();
|
||||
type PalletInstancesInfo = ();
|
||||
type MaxAssetsIntoHolding = ();
|
||||
type AssetLocker = ();
|
||||
type AssetExchanger = ();
|
||||
type FeeManager = ();
|
||||
// We just set `MessageExporter` as our `pezpallet_xcm_bridge_hub` instance.
|
||||
type MessageExporter = (XcmOverBridge,);
|
||||
type UniversalAliases = ();
|
||||
type CallDispatcher = RuntimeCall;
|
||||
type SafeCallFilter = Everything;
|
||||
type Aliasers = ();
|
||||
type TransactionalProcessor = ();
|
||||
type HrmpNewChannelOpenRequestHandler = ();
|
||||
type HrmpChannelAcceptedHandler = ();
|
||||
type HrmpChannelClosingHandler = ();
|
||||
type XcmRecorder = ();
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static EXECUTE_XCM_ORIGIN: RefCell<Option<Location>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// The `SendXcm` implementation directly executes XCM using `XcmExecutor`.
|
||||
///
|
||||
/// We ensure that the `ExportMessage` produced by `pezpallet_xcm_bridge_hub_router` is compatible with
|
||||
/// the `ExportXcm` implementation of `pezpallet_xcm_bridge_hub`.
|
||||
///
|
||||
/// Note: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which calls the
|
||||
/// `ExportXcm` implementation of `pezpallet_xcm_bridge_hub` as `MessageExporter`.
|
||||
pub struct ExecuteXcmOverSendXcm;
|
||||
impl SendXcm for ExecuteXcmOverSendXcm {
|
||||
type Ticket = Xcm<()>;
|
||||
|
||||
fn validate(
|
||||
_: &mut Option<Location>,
|
||||
message: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<Self::Ticket> {
|
||||
Ok((message.take().unwrap(), Assets::new()))
|
||||
}
|
||||
|
||||
fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
|
||||
let xcm: Xcm<RuntimeCall> = ticket.into();
|
||||
|
||||
let origin = EXECUTE_XCM_ORIGIN.with(|o| o.borrow().clone().unwrap());
|
||||
let mut hash = xcm.using_encoded(pezsp_io::hashing::blake2_256);
|
||||
let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
|
||||
origin,
|
||||
xcm,
|
||||
&mut hash,
|
||||
Weight::MAX,
|
||||
Weight::zero(),
|
||||
);
|
||||
assert_ok!(outcome.ensure_complete());
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
impl InspectMessageQueues for ExecuteXcmOverSendXcm {
|
||||
fn clear_messages() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
impl ExecuteXcmOverSendXcm {
|
||||
pub fn set_origin_for_execute(origin: Location) {
|
||||
EXECUTE_XCM_ORIGIN.with(|o| *o.borrow_mut() = Some(origin));
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic way to set different universal location for the origin which sends `ExportMessage`.
|
||||
pub struct ExportMessageOriginUniversalLocation;
|
||||
impl ExportMessageOriginUniversalLocation {
|
||||
pub(crate) fn set(universal_location: Option<InteriorLocation>) {
|
||||
EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| *o.borrow_mut() = universal_location);
|
||||
}
|
||||
}
|
||||
impl Get<InteriorLocation> for ExportMessageOriginUniversalLocation {
|
||||
fn get() -> InteriorLocation {
|
||||
EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| {
|
||||
o.borrow()
|
||||
.clone()
|
||||
.expect("`EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION` is not set!")
|
||||
})
|
||||
}
|
||||
}
|
||||
thread_local! {
|
||||
pub static EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION: RefCell<Option<InteriorLocation>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
pub struct BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>(
|
||||
pezsp_std::marker::PhantomData<RuntimeOrigin>,
|
||||
);
|
||||
impl<RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin>
|
||||
for BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>
|
||||
{
|
||||
fn convert_origin(
|
||||
origin: impl Into<Location>,
|
||||
kind: OriginKind,
|
||||
) -> Result<RuntimeOrigin, Location> {
|
||||
let origin = origin.into();
|
||||
if kind == OriginKind::Xcm && origin.eq(&BridgeHubLocation::get()) {
|
||||
Ok(RuntimeOrigin::root())
|
||||
} else {
|
||||
Err(origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for specifying how a `Location` can be converted into an `AccountId`. This is used
|
||||
/// when determining ownership of accounts for asset transacting and when attempting to use XCM
|
||||
/// `Transact` in order to determine the dispatch Origin.
|
||||
pub type LocationToAccountId = (
|
||||
// The parent (Relay-chain) origin converts to the parent `AccountId`.
|
||||
ParentIsPreset<AccountId>,
|
||||
// Sibling teyrchain origins convert to AccountId via the `ParaId::into`.
|
||||
SiblingTeyrchainConvertsVia<Sibling, AccountId>,
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub ParentRelayChainLocation: Location = Location { parents: 1, interior: Here };
|
||||
}
|
||||
pub struct OpenBridgeOrigin;
|
||||
|
||||
impl OpenBridgeOrigin {
|
||||
pub fn parent_relay_chain_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([0u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn parent_relay_chain_universal_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([1u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn sibling_teyrchain_origin() -> RuntimeOrigin {
|
||||
let mut account = [0u8; 32];
|
||||
account[..4].copy_from_slice(&SIBLING_ASSET_HUB_ID.encode()[..4]);
|
||||
RuntimeOrigin::signed(account.into())
|
||||
}
|
||||
|
||||
pub fn sibling_teyrchain_universal_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([2u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn origin_without_sovereign_account() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([3u8; 32].into())
|
||||
}
|
||||
|
||||
pub fn disallowed_origin() -> RuntimeOrigin {
|
||||
RuntimeOrigin::signed([42u8; 32].into())
|
||||
}
|
||||
}
|
||||
|
||||
impl EnsureOrigin<RuntimeOrigin> for OpenBridgeOrigin {
|
||||
type Success = Location;
|
||||
|
||||
fn try_origin(o: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
|
||||
let signer = o.clone().into_signer();
|
||||
if signer == Self::parent_relay_chain_origin().into_signer() {
|
||||
return Ok(ParentRelayChainLocation::get());
|
||||
} else if signer == Self::parent_relay_chain_universal_origin().into_signer() {
|
||||
return Ok(Location {
|
||||
parents: 2,
|
||||
interior: GlobalConsensus(RelayNetwork::get()).into(),
|
||||
});
|
||||
} else if signer == Self::sibling_teyrchain_universal_origin().into_signer() {
|
||||
return Ok(Location {
|
||||
parents: 2,
|
||||
interior: [GlobalConsensus(RelayNetwork::get()), Teyrchain(SIBLING_ASSET_HUB_ID)]
|
||||
.into(),
|
||||
});
|
||||
} else if signer == Self::origin_without_sovereign_account().into_signer() {
|
||||
return Ok(Location {
|
||||
parents: 1,
|
||||
interior: [Teyrchain(SIBLING_ASSET_HUB_ID), OnlyChild].into(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut sibling_account = [0u8; 32];
|
||||
sibling_account[..4].copy_from_slice(&SIBLING_ASSET_HUB_ID.encode()[..4]);
|
||||
if signer == Some(sibling_account.into()) {
|
||||
return Ok(Location { parents: 1, interior: Teyrchain(SIBLING_ASSET_HUB_ID).into() });
|
||||
}
|
||||
|
||||
Err(o)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn try_successful_origin() -> Result<RuntimeOrigin, ()> {
|
||||
Ok(Self::parent_relay_chain_origin())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type OpenBridgeOriginOf<T, I> =
|
||||
<T as pezpallet_xcm_bridge_hub::Config<I>>::OpenBridgeOrigin;
|
||||
|
||||
pub struct TestLocalXcmChannelManager;
|
||||
|
||||
impl TestLocalXcmChannelManager {
|
||||
pub fn make_congested() {
|
||||
pezframe_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Congested", &true);
|
||||
}
|
||||
|
||||
fn suspended_key(bridge: &BridgeId) -> Vec<u8> {
|
||||
[b"TestLocalXcmChannelManager.Suspended", bridge.encode().as_slice()].concat()
|
||||
}
|
||||
fn resumed_key(bridge: &BridgeId) -> Vec<u8> {
|
||||
[b"TestLocalXcmChannelManager.Resumed", bridge.encode().as_slice()].concat()
|
||||
}
|
||||
|
||||
pub fn is_bridge_suspended(bridge: &BridgeId) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(&Self::suspended_key(bridge))
|
||||
}
|
||||
|
||||
pub fn is_bridge_resumed(bridge: &BridgeId) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(&Self::resumed_key(bridge))
|
||||
}
|
||||
|
||||
fn build_congestion_message(bridge: &BridgeId, is_congested: bool) -> Vec<Instruction<()>> {
|
||||
use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall;
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, scale_info::TypeInfo)]
|
||||
enum Call {
|
||||
#[codec(index = 57)]
|
||||
XcmOverBridgeWrappedWithExportMessageRouter(XcmBridgeHubRouterCall),
|
||||
}
|
||||
|
||||
pezsp_std::vec![
|
||||
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
||||
Transact {
|
||||
origin_kind: OriginKind::Xcm,
|
||||
fallback_max_weight: None,
|
||||
call: Call::XcmOverBridgeWrappedWithExportMessageRouter(
|
||||
XcmBridgeHubRouterCall::report_bridge_status {
|
||||
bridge_id: bridge.inner(),
|
||||
is_congested,
|
||||
}
|
||||
)
|
||||
.encode()
|
||||
.into(),
|
||||
},
|
||||
ExpectTransactStatus(MaybeErrorCode::Success),
|
||||
]
|
||||
}
|
||||
|
||||
fn report_bridge_status(
|
||||
local_origin: &Location,
|
||||
bridge: &BridgeId,
|
||||
is_congested: bool,
|
||||
key: Vec<u8>,
|
||||
) -> Result<(), SendError> {
|
||||
// send as BridgeHub would send to sibling chain
|
||||
ExecuteXcmOverSendXcm::set_origin_for_execute(BridgeHubLocation::get());
|
||||
let result = send_xcm::<ExecuteXcmOverSendXcm>(
|
||||
local_origin.clone(),
|
||||
Self::build_congestion_message(&bridge, is_congested).into(),
|
||||
);
|
||||
|
||||
if result.is_ok() {
|
||||
pezframe_support::storage::unhashed::put(&key, &true);
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalXcmChannelManager for TestLocalXcmChannelManager {
|
||||
type Error = SendError;
|
||||
|
||||
fn is_congested(_with: &Location) -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Congested")
|
||||
}
|
||||
|
||||
fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
|
||||
Self::report_bridge_status(local_origin, &bridge, true, Self::suspended_key(&bridge))
|
||||
}
|
||||
|
||||
fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> {
|
||||
Self::report_bridge_status(local_origin, &bridge, false, Self::resumed_key(&bridge))
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_xcm_bridge_hub_router::XcmChannelStatusProvider for TestLocalXcmChannelManager {
|
||||
fn is_congested(with: &Location) -> bool {
|
||||
<Self as LocalXcmChannelManager>::is_congested(with)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestBlobDispatcher;
|
||||
|
||||
impl TestBlobDispatcher {
|
||||
pub fn is_dispatched() -> bool {
|
||||
pezframe_support::storage::unhashed::get_or_default(b"TestBlobDispatcher.Dispatched")
|
||||
}
|
||||
}
|
||||
|
||||
impl DispatchBlob for TestBlobDispatcher {
|
||||
fn dispatch_blob(_blob: Vec<u8>) -> Result<(), DispatchBlobError> {
|
||||
pezframe_support::storage::unhashed::put(b"TestBlobDispatcher.Dispatched", &true);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThisUnderlyingChain;
|
||||
|
||||
impl Chain for ThisUnderlyingChain {
|
||||
const ID: ChainId = *b"tuch";
|
||||
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BizinikiwiHeader;
|
||||
type AccountId = AccountId;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
u32::MAX
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for ThisUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithThisChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
pub type BridgedHeaderHash = H256;
|
||||
pub type BridgedChainHeader = BizinikiwiHeader;
|
||||
|
||||
pub struct BridgedUnderlyingChain;
|
||||
impl Chain for BridgedUnderlyingChain {
|
||||
const ID: ChainId = *b"bgdc";
|
||||
type BlockNumber = u64;
|
||||
type Hash = BridgedHeaderHash;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = BridgedChainHeader;
|
||||
type AccountId = AccountId;
|
||||
type Balance = Balance;
|
||||
type Nonce = u64;
|
||||
type Signature = pezsp_runtime::MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
4096
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithMessages for BridgedUnderlyingChain {
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "WithBridgedChainBridgeMessages";
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 128;
|
||||
}
|
||||
|
||||
pub struct BridgedHeaderChain;
|
||||
impl bp_header_pez_chain::HeaderChain<BridgedUnderlyingChain> for BridgedHeaderChain {
|
||||
fn finalized_header_state_root(
|
||||
_hash: HashOf<BridgedUnderlyingChain>,
|
||||
) -> Option<HashOf<BridgedUnderlyingChain>> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test message dispatcher.
|
||||
pub struct TestMessageDispatch;
|
||||
|
||||
impl TestMessageDispatch {
|
||||
pub fn deactivate(lane: TestLaneIdType) {
|
||||
pezframe_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false);
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageDispatch for TestMessageDispatch {
|
||||
type DispatchPayload = Vec<u8>;
|
||||
type DispatchLevelResult = ();
|
||||
type LaneId = TestLaneIdType;
|
||||
|
||||
fn is_active(lane: Self::LaneId) -> bool {
|
||||
pezframe_support::storage::unhashed::take::<bool>(&(b"inactive", lane).encode()[..]) !=
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
_message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
_: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
|
||||
}
|
||||
}
|
||||
|
||||
/// Run pezpallet test.
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
pezsp_io::TestExternalities::new(
|
||||
pezframe_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap(),
|
||||
)
|
||||
.execute_with(test)
|
||||
}
|
||||
Reference in New Issue
Block a user