Squashed 'bridges/' content from commit 062554430

git-subtree-dir: bridges
git-subtree-split: 0625544309ff299307f7e110f252f04eac383102
This commit is contained in:
Branislav Kontur
2022-12-01 22:32:52 +01:00
commit d2b7ee2575
357 changed files with 79920 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
[package]
name = "pallet-bridge-beefy"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
log = { version = "0.4.14", default-features = false }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
serde = { version = "1.0", optional = true }
# Bridge Dependencies
bp-beefy = { path = "../../primitives/beefy", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
# Substrate Dependencies
beefy-merkle-tree = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
primitive-types = { version = "0.12.0", default-features = false, features = ["impl-codec"] }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[dev-dependencies]
beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master" }
mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.3.2" }
pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-mmr = { git = "https://github.com/paritytech/substrate", branch = "master" }
rand = "0.8"
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
bp-test-utils = { path = "../../primitives/test-utils" }
[features]
default = ["std"]
std = [
"beefy-merkle-tree/std",
"bp-beefy/std",
"bp-runtime/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"log/std",
"primitive-types/std",
"scale-info/std",
"serde",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
]
+644
View File
@@ -0,0 +1,644 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! BEEFY bridge pallet.
//!
//! This pallet is an on-chain BEEFY light client for Substrate-based chains that are using the
//! following pallets bundle: `pallet-mmr`, `pallet-beefy` and `pallet-beefy-mmr`.
//!
//! The pallet 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.).
#![cfg_attr(not(feature = "std"), no_std)]
use bp_beefy::{ChainWithBeefy, InitializationData};
use sp_std::{boxed::Box, prelude::*};
// Re-export in crate namespace for `construct_runtime!`
pub use pallet::*;
mod utils;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod mock_chain;
/// The target that will be used when publishing logs related to this pallet.
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> = bp_runtime::BlockNumberOf<BridgedChain<T, I>>;
/// Block hash, used by configured bridged chain.
pub type BridgedBlockHash<T, I> = bp_runtime::HashOf<BridgedChain<T, I>>;
/// Pallet 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 pallet
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 pallet.
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,
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use bp_runtime::{BasicOperatingMode, OwnedBridgeModule};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
/// The upper bound on the number of requests allowed by the pallet.
///
/// A request refers to an action which writes a header to storage.
///
/// Once this bound is reached the pallet will reject all commitments
/// until the request count has decreased.
#[pallet::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.
#[pallet::constant]
type CommitmentsToKeep: Get<u32>;
/// The chain we are bridging to here.
type BridgedChain: ChainWithBeefy;
}
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
fn on_initialize(_n: T::BlockNumber) -> frame_support::weights::Weight {
<RequestCount<T, I>>::mutate(|count| *count = count.saturating_sub(1));
Weight::from_ref_time(0)
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
}
impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pallet<T, I> {
const LOG_TARGET: &'static str = LOG_TARGET;
type OwnerStorage = PalletOwner<T, I>;
type OperatingMode = BasicOperatingMode;
type OperatingModeStorage = PalletOperatingMode<T, I>;
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I>
where
BridgedMmrHashing<T, I>: 'static + Send + Sync,
{
/// Initialize pallet with BEEFY authority set and best known finalized block number.
#[pallet::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);
log::info!(target: LOG_TARGET, "Initializing bridge BEEFY pallet: {:?}", init_data);
Ok(initialize::<T, I>(init_data)?)
}
/// Change `PalletOwner`.
///
/// May only be called either by root, or by `PalletOwner`.
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
<Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
}
/// Halt or resume all pallet operations.
///
/// May only be called either by root, or by `PalletOwner`.
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_operating_mode(
origin: OriginFor<T>,
operating_mode: 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 pallet 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.
#[pallet::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,
&current_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 {
log::debug!(
target: LOG_TARGET,
"Pruning commitment for old block: {:?}.",
old_block_number
);
ImportedCommitments::<T, I>::remove(old_block_number);
}
log::info!(
target: LOG_TARGET,
"Successfully imported commitment for block {:?}",
commitment.commitment.block_number,
);
Ok(())
}
}
/// The current number of requests which have written to storage.
///
/// If the `RequestCount` hits `MaxRequests`, no more calls will be allowed to the pallet until
/// the request capacity is increased.
///
/// The `RequestCount` is decreased by one at the beginning of every block. This is to ensure
/// that the pallet can always make progress.
#[pallet::storage]
#[pallet::getter(fn request_count)]
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
#[pallet::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.
#[pallet::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.
#[pallet::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.
#[pallet::storage]
pub type CurrentAuthoritySetInfo<T: Config<I>, I: 'static = ()> =
StorageValue<_, BridgedBeefyAuthoritySetInfo<T, I>, ValueQuery>;
/// Optional pallet owner.
///
/// Pallet owner has the right to halt all pallet operations and then resume it. If it is
/// `None`, then there are no direct ways to halt/resume pallet operations, but other
/// runtime methods may still be used to do that (i.e. `democracy::referendum` to update halt
/// flag directly or calling `halt_operations`).
#[pallet::storage]
pub type PalletOwner<T: Config<I>, I: 'static = ()> =
StorageValue<_, T::AccountId, OptionQuery>;
/// The current operating mode of the pallet.
///
/// Depending on the mode either all, or no transactions will be allowed.
#[pallet::storage]
pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
StorageValue<_, BasicOperatingMode, ValueQuery>;
#[pallet::genesis_config]
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>>,
}
#[cfg(feature = "std")]
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
fn default() -> Self {
Self { owner: None, init_data: None }
}
}
#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> GenesisBuild<T, I> 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 pallet");
} else {
// Since the bridge hasn't been initialized we shouldn't allow anyone to perform
// transactions.
<PalletOperatingMode<T, I>>::put(BasicOperatingMode::Halted);
}
}
}
#[pallet::error]
pub enum Error<T, I = ()> {
/// The pallet has not been initialized yet.
NotInitialized,
/// The pallet 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 pallet.
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(bp_runtime::OwnedBridgeModuleError),
}
/// Initialize pallet 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(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use beefy_primitives::mmr::BeefyAuthoritySet;
use bp_runtime::{BasicOperatingMode, OwnedBridgeModuleError};
use bp_test_utils::generate_owned_bridge_module_tests;
use frame_support::{assert_noop, assert_ok, traits::Get};
use mock::*;
use mock_chain::*;
use sp_runtime::DispatchError;
fn next_block() {
use frame_support::traits::OnInitialize;
let current_number = frame_system::Pallet::<TestRuntime>::block_number();
frame_system::Pallet::<TestRuntime>::set_block_number(current_number + 1);
let _ = Pallet::<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!(
Pallet::<TestRuntime>::initialize(
RuntimeOrigin::root(),
InitializationData {
operating_mode: BasicOperatingMode::Normal,
best_block_number: 0,
authority_set: BeefyAuthoritySet { id: 0, len: 1, root: [0u8; 32].into() }
}
),
Error::<TestRuntime, ()>::AlreadyInitialized,
);
});
}
#[test]
fn fails_to_initialize_if_authority_set_is_empty() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime>::initialize(
RuntimeOrigin::root(),
InitializationData {
operating_mode: BasicOperatingMode::Normal,
best_block_number: 0,
authority_set: BeefyAuthoritySet { id: 0, len: 0, root: [0u8; 32].into() }
}
),
Error::<TestRuntime, ()>::InvalidInitialAuthoritySet,
);
});
}
#[test]
fn fails_to_import_commitment_if_halted() {
run_test_with_initialize(1, || {
assert_ok!(Pallet::<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(index + 1).map(Into::into)
);
}
// 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(commitments_to_keep + 1).map(Into::into)
);
// 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(commitments_to_keep + 2).map(Into::into)
);
// 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);
}
+226
View File
@@ -0,0 +1,226 @@
// 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 bp_runtime::{BasicOperatingMode, Chain};
use codec::Encode;
use frame_support::{construct_runtime, parameter_types, weights::Weight};
use sp_core::{sr25519::Signature, Pair};
use sp_runtime::{
testing::{Header, H256},
traits::{BlakeTwo256, Hash, IdentityLookup},
Perbill,
};
pub use beefy_primitives::crypto::{AuthorityId as BeefyId, Pair as BeefyPair};
use sp_core::crypto::Wraps;
use sp_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 = beefy_primitives::mmr::MmrLeaf<
TestBridgedBlockNumber,
TestBridgedBlockHash,
TestBridgedMmrHash,
TestBridgedBeefyMmrLeafExtra,
>;
pub type TestBridgedMmrNode = MmrDataOrHash<Keccak256, TestBridgedRawMmrLeaf>;
type TestBlock = frame_system::mocking::MockBlock<TestRuntime>;
type TestUncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
construct_runtime! {
pub enum TestRuntime where
Block = TestBlock,
NodeBlock = TestBlock,
UncheckedExtrinsic = TestUncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Beefy: beefy::{Pallet},
}
}
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024);
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Config for TestRuntime {
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type RuntimeCall = RuntimeCall;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = TestAccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = ();
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type BaseCallFilter = frame_support::traits::Everything;
type SystemWeightInfo = ();
type DbWeight = ();
type BlockWeights = ();
type BlockLength = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
impl beefy::Config for TestRuntime {
type MaxRequests = frame_support::traits::ConstU32<16>;
type BridgedChain = TestBridgedChain;
type CommitmentsToKeep = frame_support::traits::ConstU32<16>;
}
#[derive(Debug)]
pub struct TestBridgedChain;
impl Chain for TestBridgedChain {
type BlockNumber = TestBridgedBlockNumber;
type Hash = H256;
type Hasher = BlakeTwo256;
type Header = <TestRuntime as frame_system::Config>::Header;
type AccountId = TestAccountId;
type Balance = u64;
type Index = u64;
type Signature = Signature;
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 Signature = beefy_primitives::crypto::AuthoritySignature;
type AuthorityIdToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum;
}
/// Run test within test runtime.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
sp_io::TestExternalities::new(Default::default()).execute_with(test)
}
/// Initialize pallet 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::Pallet::<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,
) -> sp_runtime::DispatchResult {
crate::Pallet::<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: &Vec<BeefyId>) -> TestBridgedAuthoritySetInfo {
let merkle_root = get_authorities_mmr_root::<TestRuntime, (), _>(validators.iter());
TestBridgedAuthoritySetInfo { id, len: validators.len() as u32, root: 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 }
}
+299
View File
@@ -0,0 +1,299 @@
// 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 beefy_primitives::mmr::{BeefyNextAuthoritySet, MmrLeafVersion};
use bp_beefy::{BeefyPayload, Commitment, ValidatorSetId, MMR_ROOT_PAYLOAD_ID};
use codec::Encode;
use pallet_mmr::NodeIndex;
use rand::Rng;
use sp_core::Pair;
use sp_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::MMRStore<TestBridgedMmrNode> for BridgedMmrStorage {
fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result<Option<TestBridgedMmrNode>> {
Ok(self.nodes.get(&pos).cloned())
}
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 = beefy_primitives::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,
root: 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_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,
},
&current_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) -> Self::Item {
let mut concat = left.hash().as_ref().to_vec();
concat.extend_from_slice(right.hash().as_ref());
TestBridgedMmrNode::Hash(TestBridgedMmrHashing::hash(&concat))
}
}
+363
View File
@@ -0,0 +1,363 @@
use crate::{
BridgedBeefyAuthorityId, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo,
BridgedBeefyCommitmentHasher, BridgedBeefyMmrLeaf, BridgedBeefySignedCommitment, BridgedChain,
BridgedMmrHash, BridgedMmrHashing, BridgedMmrProof, Config, Error, LOG_TARGET,
};
use bp_beefy::{merkle_root, verify_mmr_leaves_proof, BeefyVerify, MmrDataOrHash, MmrProof};
use codec::Encode;
use frame_support::ensure;
use sp_runtime::traits::{Convert, Hash};
use sp_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.root, 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<T: Config<I>, I: 'static>(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::<T, I>(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 BeefyVerify::<BridgedBeefyCommitmentHasher<T, I>>::verify(sig, &msg, authority) {
missing_signatures = missing_signatures.saturating_sub(1);
if missing_signatures == 0 {
break
}
} else {
log::debug!(
target: LOG_TARGET,
"Signed commitment contains incorrect signature of validator {} ({:?}): {:?}",
idx,
authority,
sig,
);
}
}
}
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_index = mmr_proof.leaf_index;
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)],
MmrProof::into_batch_proof(mmr_proof),
)
.map_err(|e| {
log::error!(
target: LOG_TARGET,
"MMR proof of leaf {:?} (root: {:?}, leaf: {}, leaf count: {}, len: {}) \
verification has failed with error: {:?}",
mmr_leaf_hash,
mmr_root,
mmr_proof_leaf_index,
mmr_proof_leaf_count,
mmr_proof_length,
e,
);
Error::<T, I>::MmrProofVerificationFailed
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{mock::*, mock_chain::*, *};
use beefy_primitives::ValidatorSet;
use bp_beefy::{BeefyPayload, MMR_ROOT_PAYLOAD_ID};
use frame_support::{assert_noop, assert_ok};
#[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_index += 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,
},
);
});
}
}
+59
View File
@@ -0,0 +1,59 @@
[package]
name = "pallet-bridge-grandpa"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false }
finality-grandpa = { version = "0.16.0", default-features = false }
log = { version = "0.4.17", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
# Bridge Dependencies
bp-runtime = { path = "../../primitives/runtime", default-features = false }
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
# Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
# Optional Benchmarking Dependencies
bp-test-utils = { path = "../../primitives/test-utils", default-features = false, optional = true }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
[dev-dependencies]
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-runtime/std",
"bp-test-utils/std",
"codec/std",
"finality-grandpa/std",
"frame-support/std",
"frame-system/std",
"frame-benchmarking/std",
"log/std",
"scale-info/std",
"sp-finality-grandpa/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
]
runtime-benchmarks = [
"bp-test-utils",
"frame-benchmarking/runtime-benchmarks",
]
+132
View File
@@ -0,0 +1,132 @@
// 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/>.
//! Benchmarks for the GRANDPA Pallet.
//!
//! The main dispatchable for the GRANDPA pallet is `submit_finality_proof`, so these benchmarks are
//! based around that. 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_runtime::BasicOperatingMode;
use bp_test_utils::{
accounts, make_justification_for_header, JustificationGeneratorParams, TEST_GRANDPA_ROUND,
TEST_GRANDPA_SET_ID,
};
use frame_benchmarking::{benchmarks_instance_pallet, whitelisted_caller};
use frame_support::traits::Get;
use frame_system::RawOrigin;
use sp_finality_grandpa::AuthorityId;
use sp_runtime::traits::Zero;
use sp_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;
// The maximum number of pre-commits to include in a justification. In practice this scales with the
// number of validators.
pub const MAX_VALIDATOR_SET_SIZE: u32 = 1024;
// `1..MAX_VALIDATOR_SET_SIZE` and `1..MAX_VOTE_ANCESTRIES` are 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 VALIDATOR_SET_SIZE_RANGE_BEGIN: u32 = MAX_VALIDATOR_SET_SIZE / 20;
const VALIDATOR_SET_SIZE_RANGE_END: u32 =
VALIDATOR_SET_SIZE_RANGE_BEGIN + VALIDATOR_SET_SIZE_RANGE_BEGIN;
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;
/// Returns number of first header to be imported.
///
/// Since we bootstrap the pallet with `HeadersToKeep` already imported headers,
/// this function computes the next expected header number to import.
fn header_number<T: Config<I>, I: 'static, N: From<u32>>() -> N {
(T::HeadersToKeep::get() + 1).into()
}
/// 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>>) {
let authority_list = accounts(precommits as u16)
.iter()
.map(|id| (AuthorityId::from(*id), 1))
.collect::<Vec<_>>();
let init_data = InitializationData {
header: Box::new(bp_test_utils::test_header(Zero::zero())),
authority_list,
set_id: TEST_GRANDPA_SET_ID,
operating_mode: BasicOperatingMode::Normal,
};
bootstrap_bridge::<T, I>(init_data);
let header: BridgedHeader<T, I> = bp_test_utils::test_header(header_number::<T, I, _>());
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 pallet.
submit_finality_proof {
let p in VALIDATOR_SET_SIZE_RANGE_BEGIN..VALIDATOR_SET_SIZE_RANGE_END;
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 header: BridgedHeader<T, I> = bp_test_utils::test_header(header_number::<T, I, _>());
let expected_hash = header.hash();
assert_eq!(<BestFinalized<T, I>>::get().unwrap().1, expected_hash);
assert!(<ImportedHeaders<T, I>>::contains_key(expected_hash));
}
}
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{Config, Pallet};
use bp_runtime::FilterCall;
use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
use sp_runtime::{
traits::Header,
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
};
/// 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.
impl<
Call: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
T: frame_system::Config<RuntimeCall = Call> + Config<I>,
I: 'static,
> FilterCall<Call> for Pallet<T, I>
{
fn validate(call: &<T as frame_system::Config>::RuntimeCall) -> TransactionValidity {
let bundled_block_number = match call.is_sub_type() {
Some(crate::Call::<T, I>::submit_finality_proof { ref finality_target, .. }) =>
*finality_target.number(),
_ => return Ok(ValidTransaction::default()),
};
let best_finalized = crate::BestFinalized::<T, I>::get();
let best_finalized_number = match best_finalized {
Some((best_finalized_number, _)) => best_finalized_number,
None => return InvalidTransaction::Call.into(),
};
if best_finalized_number >= bundled_block_number {
log::trace!(
target: crate::LOG_TARGET,
"Rejecting obsolete bridged header: bundled {:?}, best {:?}",
bundled_block_number,
best_finalized_number,
);
return InvalidTransaction::Stale.into()
}
Ok(ValidTransaction::default())
}
}
#[cfg(test)]
mod tests {
use super::FilterCall;
use crate::{
mock::{run_test, test_header, RuntimeCall, TestNumber, TestRuntime},
BestFinalized,
};
use bp_test_utils::make_default_justification;
fn validate_block_submit(num: TestNumber) -> bool {
crate::Pallet::<TestRuntime>::validate(&RuntimeCall::Grandpa(crate::Call::<
TestRuntime,
(),
>::submit_finality_proof {
finality_target: Box::new(test_header(num)),
justification: make_default_justification(&test_header(num)),
}))
.is_ok()
}
fn sync_to_header_10() {
let header10_hash = sp_core::H256::default();
BestFinalized::<TestRuntime, ()>::put((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_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));
});
}
}
File diff suppressed because it is too large Load Diff
+131
View File
@@ -0,0 +1,131 @@
// 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/>.
// From construct_runtime macro
#![allow(clippy::from_over_into)]
use bp_runtime::Chain;
use frame_support::{construct_runtime, parameter_types, weights::Weight};
use sp_core::sr25519::Signature;
use sp_runtime::{
testing::{Header, H256},
traits::{BlakeTwo256, IdentityLookup},
Perbill,
};
pub type AccountId = u64;
pub type TestHeader = crate::BridgedHeader<TestRuntime, ()>;
pub type TestNumber = crate::BridgedBlockNumber<TestRuntime, ()>;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
pub const MAX_BRIDGED_AUTHORITIES: u32 = 2048;
pub const MAX_HEADER_SIZE: u32 = 65536;
use crate as grandpa;
construct_runtime! {
pub enum TestRuntime where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Grandpa: grandpa::{Pallet, Call},
}
}
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024);
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Config for TestRuntime {
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type RuntimeCall = RuntimeCall;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = ();
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type BaseCallFilter = frame_support::traits::Everything;
type SystemWeightInfo = ();
type DbWeight = ();
type BlockWeights = ();
type BlockLength = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
parameter_types! {
pub const MaxRequests: u32 = 2;
pub const HeadersToKeep: u32 = 5;
pub const SessionLength: u64 = 5;
pub const NumValidators: u32 = 5;
}
impl grandpa::Config for TestRuntime {
type BridgedChain = TestBridgedChain;
type MaxRequests = MaxRequests;
type HeadersToKeep = HeadersToKeep;
type MaxBridgedAuthorities = frame_support::traits::ConstU32<MAX_BRIDGED_AUTHORITIES>;
type MaxBridgedHeaderSize = frame_support::traits::ConstU32<MAX_HEADER_SIZE>;
type WeightInfo = ();
}
#[derive(Debug)]
pub struct TestBridgedChain;
impl Chain for TestBridgedChain {
type BlockNumber = <TestRuntime as frame_system::Config>::BlockNumber;
type Hash = <TestRuntime as frame_system::Config>::Hash;
type Hasher = <TestRuntime as frame_system::Config>::Hashing;
type Header = <TestRuntime as frame_system::Config>::Header;
type AccountId = AccountId;
type Balance = u64;
type Index = u64;
type Signature = Signature;
fn max_extrinsic_size() -> u32 {
unreachable!()
}
fn max_extrinsic_weight() -> Weight {
unreachable!()
}
}
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
sp_io::TestExternalities::new(Default::default()).execute_with(test)
}
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)
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Wrappers for public types that are implementing `MaxEncodedLen`
use crate::Config;
use bp_header_chain::AuthoritySet;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{BoundedVec, RuntimeDebugNoBound};
use scale_info::TypeInfo;
use sp_finality_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};
/// A bounded list of Grandpa authorities with associated weights.
pub type StoredAuthorityList<MaxBridgedAuthorities> =
BoundedVec<(AuthorityId, AuthorityWeight), MaxBridgedAuthorities>;
/// A bounded GRANDPA Authority List and ID.
#[derive(Clone, 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<<T as Config<I>>::MaxBridgedAuthorities>,
/// 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, ()> {
Ok(Self { authorities: TryFrom::try_from(authorities).map_err(drop)?, set_id })
}
}
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 }
}
}
+79
View File
@@ -0,0 +1,79 @@
// 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/>.
//! Autogenerated weights for `pallet_bridge_grandpa`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-11-17, STEPS: 50, REPEAT: 20
//! LOW RANGE: [], HIGH RANGE: []
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled
//! CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// target/release/millau-bridge-node
// benchmark
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_bridge_grandpa
// --extrinsic=*
// --execution=wasm
// --wasm-execution=Compiled
// --heap-pages=4096
// --output=./modules/grandpa/src/weights.rs
// --template=./.maintain/millau-weight-template.hbs
#![allow(clippy::all)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for `pallet_bridge_grandpa`.
pub trait WeightInfo {
fn submit_finality_proof(p: u32, v: u32) -> Weight;
}
/// Weights for `pallet_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: frame_system::Config> WeightInfo for BridgeWeight<T> {
fn submit_finality_proof(p: u32, v: u32) -> Weight {
Weight::from_ref_time(198_274_668 as u64)
.saturating_add(Weight::from_ref_time(39_830_948 as u64).saturating_mul(p as u64))
.saturating_add(Weight::from_ref_time(1_535_681 as u64).saturating_mul(v as u64))
.saturating_add(T::DbWeight::get().reads(7 as u64))
.saturating_add(T::DbWeight::get().writes(6 as u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
fn submit_finality_proof(p: u32, v: u32) -> Weight {
Weight::from_ref_time(198_274_668 as u64)
.saturating_add(Weight::from_ref_time(39_830_948 as u64).saturating_mul(p as u64))
.saturating_add(Weight::from_ref_time(1_535_681 as u64).saturating_mul(v as u64))
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(6 as u64))
}
}
+52
View File
@@ -0,0 +1,52 @@
[package]
name = "pallet-bridge-messages"
description = "Module that allows bridged chains to exchange messages using lane concept."
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false }
log = { version = "0.4.17", default-features = false }
num-traits = { version = "0.2", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
# Bridge dependencies
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
# Substrate Dependencies
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[dev-dependencies]
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
bp-test-utils = { path = "../../primitives/test-utils" }
[features]
default = ["std"]
std = [
"bp-messages/std",
"bp-runtime/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"frame-benchmarking/std",
"log/std",
"num-traits/std",
"scale-info/std",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
]
+424
View File
@@ -0,0 +1,424 @@
# Messages Module
The messages module 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 an 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.
## Message Workflow
The message "appears" when its submitter calls the `send_message()` function of the module. The
submitter specifies the lane that he's willing to use, the message itself and the fee that he's
willing to pay for the message delivery and dispatch. If a message passes all checks, 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 pallet 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
his 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 he 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, he must prove the fact
that he 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 Substrate-to-Substrate bridge we're using runtime storage proofs. Other bridges may use
transaction proofs, Substrate 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 interested in well-probed and relatively easy integration of two
Substrate-based chains, you may want to look at the
[bridge-runtime-common](../../bin/runtime-common/README.md) 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).
Message submitters may track message progress by inspecting module events. When Message is accepted,
the `MessageAccepted` event is emitted in the `send_message()` transaction. 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, inclusive range of delivered message nonces and their single-bit dispatch results.
Please note that the meaning of the 'dispatch result' is determined by the message dispatcher at
the target chain. For example, in case of immediate call dispatcher it will be the `true` if call
has been successfully dispatched and `false` if it has only been delivered. This simple mechanism
built into the messages module allows building basic bridge applications, which only care whether
their messages have been successfully dispatched or not. More sophisticated applications may use
their own dispatch result delivery mechanism to deliver something larger than single bit.
### How to plug-in Messages Module to Send Messages to the Bridged Chain?
The `pallet_bridge_messages::Config` trait has 3 main associated types that are used to work with
outbound messages. The `pallet_bridge_messages::Config::TargetHeaderChain` defines how we see the
bridged chain as the target for our outbound messages. It must be able to check that the bridged
chain may accept our message - like that the message has size below maximal possible transaction
size of the chain and so on. And when the relayer sends us a confirmation transaction, this
implementation must be able to parse and verify the proof of messages delivery. Normally, you would
reuse the same (configurable) type on all chains that are sending messages to the same bridged
chain.
The `pallet_bridge_messages::Config::LaneMessageVerifier` defines a single callback to verify outbound
messages. The simplest callback may just accept all messages. But in this case you'll need to answer
many questions first. Who will pay for the delivery and confirmation transaction? Are we sure that
someone will ever deliver this message to the bridged chain? Are we sure that we don't bloat our
runtime storage by accepting this message? What if the message is improperly encoded or has some
fields set to invalid values? Answering all those (and similar) questions would lead to correct
implementation.
There's another thing to consider when implementing type for use in
`pallet_bridge_messages::Config::LaneMessageVerifier`. It is whether we treat all message lanes
identically, or they'll have different sets of verification rules? For example, you may reserve
lane#1 for messages coming from some 'wrapped-token' pallet - then you may verify in your
implementation that the origin is associated with this pallet. Lane#2 may be reserved for 'system'
messages and you may charge zero fee for such messages. You may have some rate limiting for messages
sent over the lane#3. Or you may just verify the same rules set for all outbound messages - it is
all up to the `pallet_bridge_messages::Config::LaneMessageVerifier` implementation.
The last type is the `pallet_bridge_messages::Config::MessageDeliveryAndDispatchPayment`. When all
checks are made and we have decided to accept the message, we're calling the
`pay_delivery_and_dispatch_fee()` callback, passing the corresponding argument of the `send_message`
function. Later, when message delivery is confirmed, we're calling `pay_relayers_rewards()`
callback, passing accounts of relayers and messages that they have delivered. The simplest
implementation of this trait is in the [`instant_payments.rs`](./src/instant_payments.rs) module and
simply calls `Currency::transfer()` when those callbacks are called. So `Currency` units are
transferred between submitter, 'relayers fund' and relayers accounts. Other implementations may use
more or less sophisticated techniques - the whole relayers incentivization scheme is not a part of
the messages module.
### 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.
### How to plug-in Messages Module to 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::Config::SourceHeaderChain` defines how we see the
bridged chain as the source or our inbound messages. When relayer sends us a delivery transaction,
this implementation must be able to parse and verify the proof of messages wrapped in this
transaction. Normally, you would reuse the same (configurable) type on all chains that are sending
messages to the same bridged chain.
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.
### 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?
Message is being stored in the source chain storage until its delivery will be confirmed. After
that, we may safely remove the message from the storage. Lane messages are removed (pruned) when
someone sends a new message using the same lane. So the message submitter pays for that pruning. To
avoid pruning too many messages in a single transaction, there's
`pallet_bridge_messages::Config::MaxMessagesToPruneAtOnce` configuration parameter. We will never prune
more than this number of messages in the single transaction. That said, the value should not be too
big to avoid waste of resources when there are no messages to prune.
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::ConfigMaxUnrewardedRelayerEntriesAtInboundLane` 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::MaxUnconfirmedMessagesAtInboundLane` 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 Substrate-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
Apart from the message related calls, the module exposes a set of auxiliary calls. They fall in two
groups, described in the next two paragraphs.
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 all and
result all module operations without requiring runtime upgrade. The module may have no message
owner, but we suggest to use it at least for initial deployment. To calls that are related to this
account are:
- `fn set_owner()`: current module owner may call it to transfer "ownership" to another account;
- `fn halt_operations()`: the module owner (or sudo account) may call this function to stop all
module operations. After this call, all message-related transactions will be rejected until
further `resume_operations` call'. This call may be used when something extraordinary happens with
the bridge;
- `fn resume_operations()`: module owner may call this function to resume bridge operations. The
module will resume its regular operations after this call.
Apart from halting and resuming the bridge, the module owner may also tune module configuration
parameters without runtime upgrades. The set of parameters needs to be designed in advance, though.
The module configuration trait has associated `Parameter` type, which may be e.g. enum and represent
a set of parameters that may be updated by the module owner. For example, if your bridge needs to
convert sums between different tokens, you may define a 'conversion rate' parameter and let the
module owner update this parameter when there are significant changes in the rate. The corresponding
module call is `fn update_pallet_parameter()`.
## Weights of Module Extrinsics
The main assumptions behind weight formulas is:
- all possible costs are paid in advance by the message submitter;
- whenever possible, relayer tries to minimize cost of its transactions. So e.g. even though sender
always pays for delivering outbound lane state proof, relayer may not include it in the delivery
transaction (unless messages module on target chain requires that);
- weight formula should incentivize relayer to not to submit any redundant data in the extrinsics
arguments;
- the extrinsic shall never be executing slower (i.e. has larger actual weight) than defined by the
formula.
### Weight of `send_message` call
#### Related benchmarks
| Benchmark | Description |
|-----------------------------------|-----------------------------------------------------|
`send_minimal_message_worst_case` | Sends 0-size message with worst possible conditions |
`send_1_kb_message_worst_case` | Sends 1KB-size message with worst possible conditions |
`send_16_kb_message_worst_case` | Sends 16KB-size message with worst possible conditions |
#### Weight formula
The weight formula is:
```
Weight = BaseWeight + MessageSizeInKilobytes * MessageKiloByteSendWeight
```
Where:
| Component | How it is computed? | Description |
|-----------------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| `SendMessageOverhead` | `send_minimal_message_worst_case` | Weight of sending minimal (0 bytes) message |
| `MessageKiloByteSendWeight` | `(send_16_kb_message_worst_case - send_1_kb_message_worst_case)/15` | Weight of sending every additional kilobyte of the message |
### Weight of `receive_messages_proof` call
#### Related benchmarks
| Benchmark | Description* |
|---------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
| `receive_single_message_proof` | Receives proof of single `EXPECTED_DEFAULT_MESSAGE_LENGTH` message |
| `receive_two_messages_proof` | Receives proof of two identical `EXPECTED_DEFAULT_MESSAGE_LENGTH` messages |
| `receive_single_message_proof_with_outbound_lane_state` | Receives proof of single `EXPECTED_DEFAULT_MESSAGE_LENGTH` message and proof of outbound lane state at the source chain |
| `receive_single_message_proof_1_kb` | Receives proof of single message. The proof has size of approximately 1KB** |
| `receive_single_message_proof_16_kb` | Receives proof of single message. The proof has size of approximately 16KB** |
*\* - In all benchmarks all received messages are dispatched and their dispatch cost is near to zero*
*\*\* - Trie leafs are assumed to have minimal values. The proof is derived from the minimal proof
by including more trie nodes. That's because according to our additioal benchmarks, increasing proof
by including more nodes has slightly larger impact on performance than increasing values stored in leafs*.
#### Weight formula
The weight formula is:
```
Weight = BaseWeight + OutboundStateDeliveryWeight
+ MessagesCount * MessageDeliveryWeight
+ MessagesDispatchWeight
+ Max(0, ActualProofSize - ExpectedProofSize) * ProofByteDeliveryWeight
```
Where:
| Component | How it is computed? | Description |
|-------------------------------|------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `BaseWeight` | `2*receive_single_message_proof - receive_two_messages_proof` | Weight of receiving and parsing minimal proof |
| `OutboundStateDeliveryWeight` | `receive_single_message_proof_with_outbound_lane_state - receive_single_message_proof` | Additional weight when proof includes outbound lane state |
| `MessageDeliveryWeight` | `receive_two_messages_proof - receive_single_message_proof` | Weight of of parsing and dispatching (without actual dispatch cost) of every message |
| `MessagesCount` | | Provided by relayer |
| `MessagesDispatchWeight` | | Provided by relayer |
| `ActualProofSize` | | Provided by relayer |
| `ExpectedProofSize` | `EXPECTED_DEFAULT_MESSAGE_LENGTH * MessagesCount + EXTRA_STORAGE_PROOF_SIZE` | Size of proof that we are expecting. This only includes `EXTRA_STORAGE_PROOF_SIZE` once, because we assume that intermediate nodes likely to be included in the proof only once. This may be wrong, but since weight of processing proof with many nodes is almost equal to processing proof with large leafs, additional cost will be covered because we're charging for extra proof bytes anyway |
| `ProofByteDeliveryWeight` | `(receive_single_message_proof_16_kb - receive_single_message_proof_1_kb) / (15 * 1024)` | Weight of processing every additional proof byte over `ExpectedProofSize` limit |
#### Why for every message sent using `send_message` we will be able to craft `receive_messages_proof` transaction?
We have following checks in `send_message` transaction on the source chain:
- message size should be less than or equal to `2/3` of maximal extrinsic size on the target chain;
- message dispatch weight should be less than or equal to the `1/2` of maximal extrinsic dispatch
weight on the target chain.
Delivery transaction is an encoded delivery call and signed extensions. So we have `1/3` of maximal
extrinsic size reserved for:
- storage proof, excluding the message itself. Currently, on our test chains, the overhead is always
within `EXTRA_STORAGE_PROOF_SIZE` limits (1024 bytes);
- signed extras and other call arguments (`relayer_id: SourceChain::AccountId`, `messages_count:
u32`, `dispatch_weight: u64`).
On Millau chain, maximal extrinsic size is `0.75 * 2MB`, so `1/3` is `512KB` (`524_288` bytes). This
should be enough to cover these extra arguments and signed extensions.
Let's exclude message dispatch cost from single message delivery transaction weight formula:
```
Weight = BaseWeight + OutboundStateDeliveryWeight + MessageDeliveryWeight
+ Max(0, ActualProofSize - ExpectedProofSize) * ProofByteDeliveryWeight
```
So we have `1/2` of maximal extrinsic weight to cover these components. `BaseWeight`,
`OutboundStateDeliveryWeight` and `MessageDeliveryWeight` are determined using benchmarks and are
hardcoded into runtime. Adequate relayer would only include required trie nodes into the proof. So
if message size would be maximal (`2/3` of `MaximalExtrinsicSize`), then the extra proof size would
be `MaximalExtrinsicSize / 3 * 2 - EXPECTED_DEFAULT_MESSAGE_LENGTH`.
Both conditions are verified by `pallet_bridge_messages::ensure_weights_are_correct` and
`pallet_bridge_messages::ensure_able_to_receive_messages` functions, which must be called from every
runtime's tests.
#### Post-dispatch weight refunds of the `receive_messages_proof` call
Weight formula of the `receive_messages_proof` call assumes that the dispatch fee of every message is
paid at the target chain (where call is executed), that every message will be dispatched and that
dispatch weight of the message will be exactly the weight that is returned from the
`MessageDispatch::dispatch_weight` method call. This isn't true for all messages, so the call returns
actual weight used to dispatch messages.
This actual weight is the weight, returned by the weight formula, minus:
- the weight of undispatched messages, if we have failed to dispatch because of different issues;
- the unspent dispatch weight if the declared weight of some messages is less than their actual post-dispatch weight;
- the pay-dispatch-fee weight for every message that had dispatch fee paid at the source chain.
The last component is computed as a difference between two benchmarks results - the `receive_single_message_proof`
benchmark (that assumes that the fee is paid during dispatch) and the `receive_single_prepaid_message_proof`
(that assumes that the dispatch fee is already paid).
### Weight of `receive_messages_delivery_proof` call
#### Related benchmarks
| Benchmark | Description |
|-------------------------------------------------------------|------------------------------------------------------------------------------------------|
| `receive_delivery_proof_for_single_message` | Receives proof of single message delivery |
| `receive_delivery_proof_for_two_messages_by_single_relayer` | Receives proof of two messages delivery. Both messages are delivered by the same relayer |
| `receive_delivery_proof_for_two_messages_by_two_relayers` | Receives proof of two messages delivery. Messages are delivered by different relayers |
#### Weight formula
The weight formula is:
```
Weight = BaseWeight + MessagesCount * MessageConfirmationWeight
+ RelayersCount * RelayerRewardWeight
+ Max(0, ActualProofSize - ExpectedProofSize) * ProofByteDeliveryWeight
+ MessagesCount * (DbReadWeight + DbWriteWeight)
```
Where:
| Component | How it is computed? | Description |
|---------------------------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `BaseWeight` | `2*receive_delivery_proof_for_single_message - receive_delivery_proof_for_two_messages_by_single_relayer` | Weight of receiving and parsing minimal delivery proof |
| `MessageDeliveryWeight` | `receive_delivery_proof_for_two_messages_by_single_relayer - receive_delivery_proof_for_single_message` | Weight of confirming every additional message |
| `MessagesCount` | | Provided by relayer |
| `RelayerRewardWeight` | `receive_delivery_proof_for_two_messages_by_two_relayers - receive_delivery_proof_for_two_messages_by_single_relayer` | Weight of rewarding every additional relayer |
| `RelayersCount` | | Provided by relayer |
| `ActualProofSize` | | Provided by relayer |
| `ExpectedProofSize` | `EXTRA_STORAGE_PROOF_SIZE` | Size of proof that we are expecting |
| `ProofByteDeliveryWeight` | `(receive_single_message_proof_16_kb - receive_single_message_proof_1_kb) / (15 * 1024)` | Weight of processing every additional proof byte over `ExpectedProofSize` limit. We're using the same formula, as for message delivery, because proof mechanism is assumed to be the same in both cases |
#### Post-dispatch weight refunds of the `receive_messages_delivery_proof` call
Weight formula of the `receive_messages_delivery_proof` call assumes that all messages in the proof
are actually delivered (so there are no already confirmed messages) and every messages is processed
by the `OnDeliveryConfirmed` callback. This means that for every message, we're adding single db read
weight and single db write weight. If, by some reason, messages are not processed by the
`OnDeliveryConfirmed` callback, or their processing is faster than that additional weight, the
difference is refunded to the submitter.
#### Why we're always able to craft `receive_messages_delivery_proof` transaction?
There can be at most `<PeerRuntime as pallet_bridge_messages::Config>::MaxUnconfirmedMessagesAtInboundLane`
messages and at most
`<PeerRuntime as pallet_bridge_messages::Config>::MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded
relayers in the single delivery confirmation transaction.
We're checking that this transaction may be crafted in the
`pallet_bridge_messages::ensure_able_to_receive_confirmation` function, which must be called from every
runtime' tests.
+423
View File
@@ -0,0 +1,423 @@
// 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/>.
//! Messages pallet benchmarking.
use crate::{
inbound_lane::InboundLaneStorage, inbound_lane_storage, outbound_lane,
weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH, Call, OutboundLanes,
};
use bp_messages::{
source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, DeliveredMessages,
InboundLaneData, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer,
UnrewardedRelayersState,
};
use bp_runtime::StorageProofSize;
use frame_benchmarking::{account, benchmarks_instance_pallet};
use frame_support::weights::Weight;
use frame_system::RawOrigin;
use sp_std::{ops::RangeInclusive, prelude::*};
const SEED: u32 = 0;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config<I>, I: 'static>(crate::Pallet<T, I>);
/// Benchmark-specific message proof parameters.
#[derive(Debug)]
pub struct MessageProofParams {
/// 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>,
/// Proof size requirements.
pub size: StorageProofSize,
}
/// Benchmark-specific message delivery proof parameters.
#[derive(Debug)]
pub struct MessageDeliveryProofParams<ThisChainAccountId> {
/// 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 size: StorageProofSize,
}
/// 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() -> LaneId {
Default::default()
}
/// Return id of relayer account at the bridged chain.
fn bridged_relayer_id() -> Self::InboundRelayer;
/// Create given account and give it enough balance for test purposes.
fn endow_account(account: &Self::AccountId);
/// Prepare messages proof to receive by the module.
fn prepare_message_proof(
params: MessageProofParams,
) -> (<Self::SourceHeaderChain as SourceHeaderChain>::MessagesProof, Weight);
/// Prepare messages delivery proof to receive by the module.
fn prepare_message_delivery_proof(
params: MessageDeliveryProofParams<Self::AccountId>,
) -> <Self::TargetHeaderChain as TargetHeaderChain<Self::OutboundPayload, Self::AccountId>>::MessagesDeliveryProof;
/// Returns true if message has been dispatched (either successfully or not).
fn is_message_dispatched(nonce: MessageNonce) -> bool;
}
benchmarks_instance_pallet! {
//
// Benchmarks that are used directly by the runtime.
//
// 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 successfully dispatched;
// * message requires all heavy checks done by dispatcher;
// * message dispatch fee is paid at target (this) chain.
//
// This is base benchmark for all other message delivery benchmarks.
receive_single_message_proof {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: None,
size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
21,
);
assert!(T::is_message_dispatched(21));
}
// Benchmark `receive_messages_proof` extrinsic with two 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 successfully dispatched;
// * message requires all heavy checks done by dispatcher;
// * message dispatch fee is paid at target (this) chain.
//
// The weight of single message delivery could be approximated as
// `weight(receive_two_messages_proof) - 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.
receive_two_messages_proof {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=22,
outbound_lane_data: None,
size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 2, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
22,
);
assert!(T::is_message_dispatched(22));
}
// 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;
// * message requires all heavy checks done by dispatcher;
// * message dispatch fee is paid at target (this) chain.
//
// 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.
receive_single_message_proof_with_outbound_lane_state {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: Some(OutboundLaneData {
oldest_unpruned_nonce: 21,
latest_received_nonce: 20,
latest_generated_nonce: 21,
}),
size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
let lane_state = crate::InboundLanes::<T, I>::get(&T::bench_lane_id());
assert_eq!(lane_state.last_delivered_nonce(), 21);
assert_eq!(lane_state.last_confirmed_nonce, 20);
assert!(T::is_message_dispatched(21));
}
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions:
// * the proof has many redundand trie nodes with total size of approximately 1KB;
// * 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.
//
// With single KB of messages proof, the weight of the call is increased (roughly) by
// `(receive_single_message_proof_16KB - receive_single_message_proof_1_kb) / 15`.
receive_single_message_proof_1_kb {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: None,
size: StorageProofSize::HasExtraNodes(1024),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
21,
);
assert!(T::is_message_dispatched(21));
}
// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following conditions:
// * the proof has many redundand trie nodes with total size of approximately 16KB;
// * 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.
//
// Size of proof grows because it contains extra trie nodes in it.
//
// With single KB of messages proof, the weight of the call is increased (roughly) by
// `(receive_single_message_proof_16KB - receive_single_message_proof) / 15`.
receive_single_message_proof_16_kb {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: None,
size: StorageProofSize::HasExtraNodes(16 * 1024),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
21,
);
assert!(T::is_message_dispatched(21));
}
// 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 successfully dispatched;
// * message requires all heavy checks done by dispatcher;
// * message dispatch fee is paid at source (bridged) chain.
//
// This benchmark is used to compute extra weight spent at target chain when fee is paid there. Then we use
// this information in two places: (1) to reduce weight of delivery tx if sender pays fee at the source chain
// and (2) to refund relayer with this weight if fee has been paid at the source chain.
receive_single_prepaid_message_proof {
let relayer_id_on_source = T::bridged_relayer_id();
let relayer_id_on_target = account("relayer", 0, SEED);
T::endow_account(&relayer_id_on_target);
// mark messages 1..=20 as delivered
receive_messages::<T, I>(20);
let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
lane: T::bench_lane_id(),
message_nonces: 21..=21,
outbound_lane_data: None,
size: StorageProofSize::Minimal(EXPECTED_DEFAULT_MESSAGE_LENGTH),
});
}: receive_messages_proof(RawOrigin::Signed(relayer_id_on_target), relayer_id_on_source, proof, 1, dispatch_weight)
verify {
assert_eq!(
crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).last_delivered_nonce(),
21,
);
assert!(T::is_message_dispatched(21));
}
// 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.
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 {
relayers: vec![UnrewardedRelayer {
relayer: relayer_id.clone(),
messages: DeliveredMessages::new(1),
}].into_iter().collect(),
last_confirmed_nonce: 0,
},
size: StorageProofSize::Minimal(0),
});
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state)
verify {
assert_eq!(OutboundLanes::<T, I>::get(T::bench_lane_id()).latest_received_nonce, 1);
}
// 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)`.
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 {
relayers: vec![UnrewardedRelayer {
relayer: relayer_id.clone(),
messages: delivered_messages,
}].into_iter().collect(),
last_confirmed_nonce: 0,
},
size: StorageProofSize::Minimal(0),
});
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer_id.clone()), proof, relayers_state)
verify {
assert_eq!(OutboundLanes::<T, I>::get(T::bench_lane_id()).latest_received_nonce, 2);
}
// 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)`.
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 {
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,
},
size: StorageProofSize::Minimal(0),
});
}: receive_messages_delivery_proof(RawOrigin::Signed(relayer1_id.clone()), proof, relayers_state)
verify {
assert_eq!(OutboundLanes::<T, I>::get(T::bench_lane_id()).latest_received_nonce, 2);
}
}
fn send_regular_message<T: Config<I>, I: 'static>() {
let mut outbound_lane = outbound_lane::<T, I>(T::bench_lane_id());
outbound_lane.send_message(vec![]);
}
fn receive_messages<T: Config<I>, I: 'static>(nonce: MessageNonce) {
let mut inbound_lane_storage = inbound_lane_storage::<T, I>(T::bench_lane_id());
inbound_lane_storage.set_data(InboundLaneData {
relayers: vec![UnrewardedRelayer {
relayer: T::bridged_relayer_id(),
messages: DeliveredMessages::new(nonce),
}]
.into_iter()
.collect(),
last_confirmed_nonce: 0,
});
}
+545
View File
@@ -0,0 +1,545 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Everything about incoming messages receival.
use crate::Config;
use bp_messages::{
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
DeliveredMessages, InboundLaneData, LaneId, MessageKey, MessageNonce, OutboundLaneData,
ReceivalResult, UnrewardedRelayer,
};
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use frame_support::{traits::Get, RuntimeDebug};
use scale_info::{Type, TypeInfo};
use sp_std::prelude::PartialEq;
/// Inbound lane storage.
pub trait InboundLaneStorage {
/// Id of relayer on source chain.
type Relayer: Clone + PartialEq;
/// Lane id.
fn id(&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>);
}
/// 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<T::InboundRelayer>);
impl<T: Config<I>, I: 'static> sp_std::ops::Deref for StoredInboundLaneData<T, I> {
type Target = InboundLaneData<T::InboundRelayer>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Config<I>, I: 'static> sp_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<T::InboundRelayer>
{
fn from(data: StoredInboundLaneData<T, I>) -> Self {
data.0
}
}
impl<T: Config<I>, I: 'static> EncodeLike<StoredInboundLaneData<T, I>>
for InboundLaneData<T::InboundRelayer>
{
}
impl<T: Config<I>, I: 'static> TypeInfo for StoredInboundLaneData<T, I> {
type Identity = Self;
fn type_info() -> Type {
InboundLaneData::<T::InboundRelayer>::type_info()
}
}
impl<T: Config<I>, I: 'static> MaxEncodedLen for StoredInboundLaneData<T, I> {
fn max_encoded_len() -> usize {
InboundLaneData::<T::InboundRelayer>::encoded_size_hint(
T::MaxUnrewardedRelayerEntriesAtInboundLane::get() 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 }
}
/// 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<AccountId>, AccountId>(
&mut self,
relayer_at_bridged_chain: &S::Relayer,
relayer_at_this_chain: &AccountId,
nonce: MessageNonce,
message_data: DispatchMessageData<Dispatch::DispatchPayload>,
) -> ReceivalResult<Dispatch::DispatchLevelResult> {
let mut data = self.storage.data();
let is_correct_message = nonce == data.last_delivered_nonce() + 1;
if !is_correct_message {
return ReceivalResult::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 ReceivalResult::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 ReceivalResult::TooManyUnconfirmedMessages
}
// then, dispatch message
let dispatch_result = Dispatch::dispatch(
relayer_at_this_chain,
DispatchMessage {
key: MessageKey { lane_id: self.storage.id(), nonce },
data: message_data,
},
);
// now let's update inbound lane storage
let push_new = match data.relayers.back_mut() {
Some(entry) if entry.relayer == *relayer_at_bridged_chain => {
entry.messages.note_dispatched_message();
false
},
_ => true,
};
if push_new {
data.relayers.push_back(UnrewardedRelayer {
relayer: (*relayer_at_bridged_chain).clone(),
messages: DeliveredMessages::new(nonce),
});
}
self.storage.set_data(data);
ReceivalResult::Dispatched(dispatch_result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
inbound_lane,
mock::{
dispatch_result, inbound_message_data, run_test, unrewarded_relayer,
TestMessageDispatch, TestRuntime, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_RELAYER_A,
TEST_RELAYER_B, TEST_RELAYER_C,
},
RuntimeInboundLaneStorage,
};
fn receive_regular_message(
lane: &mut InboundLane<RuntimeInboundLaneStorage<TestRuntime, ()>>,
nonce: MessageNonce,
) {
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_A,
&TEST_RELAYER_A,
nonce,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::Dispatched(dispatch_result(0))
);
}
#[test]
fn receive_status_update_ignores_status_from_the_future() {
run_test(|| {
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_A,
&TEST_RELAYER_A,
10,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
let max_nonce =
<TestRuntime as Config>::MaxUnrewardedRelayerEntriesAtInboundLane::get();
for current_nonce in 1..max_nonce + 1 {
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&(TEST_RELAYER_A + current_nonce),
&(TEST_RELAYER_A + current_nonce),
current_nonce,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::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),
&(TEST_RELAYER_A + max_nonce + 1),
max_nonce + 1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::TooManyUnrewardedRelayers,
);
// Fails to dispatch new messages from latest relayer. Prevents griefing attacks.
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&(TEST_RELAYER_A + max_nonce),
&(TEST_RELAYER_A + max_nonce),
max_nonce + 1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::TooManyUnrewardedRelayers,
);
});
}
#[test]
fn fails_to_receive_messages_above_unconfirmed_messages_limit_per_lane() {
run_test(|| {
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
let max_nonce = <TestRuntime as Config>::MaxUnconfirmedMessagesAtInboundLane::get();
for current_nonce in 1..=max_nonce {
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_A,
&TEST_RELAYER_A,
current_nonce,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::Dispatched(dispatch_result(0))
);
}
// Fails to dispatch new message from different than latest relayer.
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_B,
&TEST_RELAYER_B,
max_nonce + 1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::TooManyUnconfirmedMessages,
);
// Fails to dispatch new messages from latest relayer.
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_A,
&TEST_RELAYER_A,
max_nonce + 1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::TooManyUnconfirmedMessages,
);
});
}
#[test]
fn correctly_receives_following_messages_from_two_relayers_alternately() {
run_test(|| {
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_A,
&TEST_RELAYER_A,
1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::Dispatched(dispatch_result(0))
);
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_B,
&TEST_RELAYER_B,
2,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::Dispatched(dispatch_result(0))
);
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_A,
&TEST_RELAYER_A,
3,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_A,
&TEST_RELAYER_A,
1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::Dispatched(dispatch_result(0))
);
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_B,
&TEST_RELAYER_B,
1,
inbound_message_data(REGULAR_PAYLOAD)
),
ReceivalResult::InvalidNonce,
);
});
}
#[test]
fn correct_message_is_processed_instantly() {
run_test(|| {
let mut lane = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = inbound_lane::<TestRuntime, _>(TEST_LANE_ID);
let mut payload = REGULAR_PAYLOAD;
*payload.dispatch_result.unspent_weight.ref_time_mut() = 1;
assert_eq!(
lane.receive_message::<TestMessageDispatch, _>(
&TEST_RELAYER_A,
&TEST_RELAYER_A,
1,
inbound_message_data(payload)
),
ReceivalResult::Dispatched(dispatch_result(1))
);
});
}
}
File diff suppressed because it is too large Load Diff
+423
View File
@@ -0,0 +1,423 @@
// 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/>.
// From construct_runtime macro
#![allow(clippy::from_over_into)]
use crate::{calc_relayers_rewards, Config};
use bp_messages::{
source_chain::{LaneMessageVerifier, MessageDeliveryAndDispatchPayment, TargetHeaderChain},
target_chain::{
DispatchMessage, DispatchMessageData, MessageDispatch, ProvedLaneMessages, ProvedMessages,
SourceHeaderChain,
},
DeliveredMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload,
OutboundLaneData, UnrewardedRelayer,
};
use bp_runtime::{messages::MessageDispatchResult, Size};
use codec::{Decode, Encode};
use frame_support::{
parameter_types,
weights::{RuntimeDbWeight, Weight},
};
use scale_info::TypeInfo;
use sp_core::H256;
use sp_runtime::{
testing::Header as SubstrateHeader,
traits::{BlakeTwo256, IdentityLookup},
Perbill,
};
use std::{
collections::{BTreeMap, VecDeque},
ops::RangeInclusive,
};
pub type AccountId = u64;
pub type Balance = u64;
#[derive(Decode, Encode, Clone, Debug, PartialEq, Eq, TypeInfo)]
pub struct TestPayload {
/// Field that may be used to identify messages.
pub id: u64,
/// Reject this message by lane verifier?
pub reject_by_lane_verifier: bool,
/// 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 = ();
type Block = frame_system::mocking::MockBlock<TestRuntime>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
use crate as pallet_bridge_messages;
frame_support::construct_runtime! {
pub enum TestRuntime where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Event<T>},
Messages: pallet_bridge_messages::{Pallet, Call, Event<T>},
}
}
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024);
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
}
impl frame_system::Config for TestRuntime {
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type RuntimeCall = RuntimeCall;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = SubstrateHeader;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = ();
type OnKilledAccount = ();
type BaseCallFilter = frame_support::traits::Everything;
type SystemWeightInfo = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = DbWeight;
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
}
impl pallet_balances::Config for TestRuntime {
type MaxLocks = ();
type Balance = Balance;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = frame_system::Pallet<TestRuntime>;
type WeightInfo = ();
type MaxReserves = ();
type ReserveIdentifier = ();
}
parameter_types! {
pub const MaxMessagesToPruneAtOnce: u64 = 10;
pub const MaxUnrewardedRelayerEntriesAtInboundLane: u64 = 16;
pub const MaxUnconfirmedMessagesAtInboundLane: u64 = 32;
pub const TestBridgedChainId: bp_runtime::ChainId = *b"test";
pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID, TEST_LANE_ID_2];
}
impl Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type ActiveOutboundLanes = ActiveOutboundLanes;
type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane;
type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane;
type MaximalOutboundPayloadSize = frame_support::traits::ConstU32<MAX_OUTBOUND_PAYLOAD_SIZE>;
type OutboundPayload = TestPayload;
type InboundPayload = TestPayload;
type InboundRelayer = TestRelayer;
type TargetHeaderChain = TestTargetHeaderChain;
type LaneMessageVerifier = TestLaneMessageVerifier;
type MessageDeliveryAndDispatchPayment = TestMessageDeliveryAndDispatchPayment;
type SourceHeaderChain = TestSourceHeaderChain;
type MessageDispatch = TestMessageDispatch;
type BridgedChainId = TestBridgedChainId;
}
impl Size for TestPayload {
fn size(&self) -> u32 {
16 + self.extra.len() as u32
}
}
/// Maximal outbound payload size.
pub const MAX_OUTBOUND_PAYLOAD_SIZE: u32 = 4096;
/// 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;
/// Error that is returned by all test implementations.
pub const TEST_ERROR: &str = "Test error";
/// Lane that we're using in tests.
pub const TEST_LANE_ID: LaneId = [0, 0, 0, 1];
/// Secondary lane that we're using in tests.
pub const TEST_LANE_ID_2: LaneId = [0, 0, 0, 2];
/// Inactive outbound lane.
pub const TEST_LANE_ID_3: LaneId = [0, 0, 0, 3];
/// Regular message payload.
pub const REGULAR_PAYLOAD: TestPayload = message_payload(0, 50);
/// Payload that is rejected by `TestTargetHeaderChain`.
pub const PAYLOAD_REJECTED_BY_TARGET_CHAIN: TestPayload = message_payload(1, 50);
/// Vec of proved messages, grouped by lane.
pub type MessagesByLaneVec = Vec<(LaneId, ProvedLaneMessages<Message>)>;
/// Test messages proof.
#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
pub struct TestMessagesProof {
pub result: Result<MessagesByLaneVec, ()>,
}
impl Size for TestMessagesProof {
fn size(&self) -> u32 {
0
}
}
impl From<Result<Vec<Message>, ()>> for TestMessagesProof {
fn from(result: Result<Vec<Message>, ()>) -> Self {
Self {
result: result.map(|messages| {
let mut messages_by_lane: BTreeMap<LaneId, ProvedLaneMessages<Message>> =
BTreeMap::new();
for message in messages {
messages_by_lane.entry(message.key.lane_id).or_default().messages.push(message);
}
messages_by_lane.into_iter().collect()
}),
}
}
}
/// Messages delivery proof used in tests.
#[derive(Debug, Encode, Decode, Eq, Clone, PartialEq, TypeInfo)]
pub struct TestMessagesDeliveryProof(pub Result<(LaneId, InboundLaneData<TestRelayer>), ()>);
impl Size for TestMessagesDeliveryProof {
fn size(&self) -> u32 {
0
}
}
/// Target header chain that is used in tests.
#[derive(Debug, Default)]
pub struct TestTargetHeaderChain;
impl TargetHeaderChain<TestPayload, TestRelayer> for TestTargetHeaderChain {
type Error = &'static str;
type MessagesDeliveryProof = TestMessagesDeliveryProof;
fn verify_message(payload: &TestPayload) -> Result<(), Self::Error> {
if *payload == PAYLOAD_REJECTED_BY_TARGET_CHAIN {
Err(TEST_ERROR)
} else {
Ok(())
}
}
fn verify_messages_delivery_proof(
proof: Self::MessagesDeliveryProof,
) -> Result<(LaneId, InboundLaneData<TestRelayer>), Self::Error> {
proof.0.map_err(|_| TEST_ERROR)
}
}
/// Lane message verifier that is used in tests.
#[derive(Debug, Default)]
pub struct TestLaneMessageVerifier;
impl LaneMessageVerifier<RuntimeOrigin, TestPayload> for TestLaneMessageVerifier {
type Error = &'static str;
fn verify_message(
_submitter: &RuntimeOrigin,
_lane: &LaneId,
_lane_outbound_data: &OutboundLaneData,
payload: &TestPayload,
) -> Result<(), Self::Error> {
if !payload.reject_by_lane_verifier {
Ok(())
} else {
Err(TEST_ERROR)
}
}
}
/// Message fee payment system that is used in tests.
#[derive(Debug, Default)]
pub struct TestMessageDeliveryAndDispatchPayment;
impl TestMessageDeliveryAndDispatchPayment {
/// 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();
frame_support::storage::unhashed::take::<bool>(&key).is_some()
}
}
impl MessageDeliveryAndDispatchPayment<RuntimeOrigin, AccountId>
for TestMessageDeliveryAndDispatchPayment
{
type Error = &'static str;
fn pay_relayers_rewards(
_lane_id: LaneId,
message_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
_confirmation_relayer: &AccountId,
received_range: &RangeInclusive<MessageNonce>,
) {
let relayers_rewards =
calc_relayers_rewards::<TestRuntime, ()>(message_relayers, received_range);
for (relayer, reward) in &relayers_rewards {
let key = (b":relayer-reward:", relayer, reward).encode();
frame_support::storage::unhashed::put(&key, &true);
}
}
}
/// Source header chain that is used in tests.
#[derive(Debug)]
pub struct TestSourceHeaderChain;
impl SourceHeaderChain for TestSourceHeaderChain {
type Error = &'static str;
type MessagesProof = TestMessagesProof;
fn verify_messages_proof(
proof: Self::MessagesProof,
_messages_count: u32,
) -> Result<ProvedMessages<Message>, Self::Error> {
proof.result.map(|proof| proof.into_iter().collect()).map_err(|_| TEST_ERROR)
}
}
/// Source header chain that is used in tests.
#[derive(Debug)]
pub struct TestMessageDispatch;
impl MessageDispatch<AccountId> for TestMessageDispatch {
type DispatchPayload = TestPayload;
type DispatchLevelResult = TestDispatchLevelResult;
fn dispatch_weight(message: &mut DispatchMessage<TestPayload>) -> Weight {
match message.data.payload.as_ref() {
Ok(payload) => payload.declared_weight,
Err(_) => Weight::from_ref_time(0),
}
}
fn dispatch(
_relayer_account: &AccountId,
message: DispatchMessage<TestPayload>,
) -> MessageDispatchResult<TestDispatchLevelResult> {
match message.data.payload.as_ref() {
Ok(payload) => payload.dispatch_result.clone(),
Err(_) => dispatch_result(0),
}
}
}
/// Return test lane message with given nonce and payload.
pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message {
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) -> MessagePayload {
payload.encode()
}
/// 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,
reject_by_lane_verifier: false,
declared_weight: Weight::from_ref_time(declared_weight),
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_ref_time(unspent_weight),
dispatch_fee_paid_during_dispatch: true,
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 } }
}
/// Run pallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
let mut t = frame_system::GenesisConfig::default().build_storage::<TestRuntime>().unwrap();
pallet_balances::GenesisConfig::<TestRuntime> { balances: vec![(ENDOWED_ACCOUNT, 1_000_000)] }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(test)
}
+436
View File
@@ -0,0 +1,436 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Everything about outgoing messages sending.
use crate::Config;
use bp_messages::{
DeliveredMessages, LaneId, MessageNonce, MessagePayload, OutboundLaneData, UnrewardedRelayer,
};
use frame_support::{
weights::{RuntimeDbWeight, Weight},
BoundedVec, RuntimeDebug,
};
use num_traits::Zero;
use sp_std::collections::vec_deque::VecDeque;
/// Outbound lane storage.
pub trait OutboundLaneStorage {
/// Lane id.
fn id(&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<MessagePayload>;
/// Save outbound message in the storage.
fn save_message(&mut self, nonce: MessageNonce, message_payload: MessagePayload);
/// Remove outbound message from the storage.
fn remove_message(&mut self, nonce: &MessageNonce);
}
/// Outbound message data wrapper that implements `MaxEncodedLen`.
pub type StoredMessagePayload<T, I> = BoundedVec<u8, <T as Config<I>>::MaximalOutboundPayloadSize>;
/// Result of messages receival confirmation.
#[derive(RuntimeDebug, PartialEq, Eq)]
pub enum ReceivalConfirmationResult {
/// New messages have been confirmed by the confirmation transaction.
ConfirmedMessages(DeliveredMessages),
/// Confirmation transaction brings no new confirmation. This may be a result of relayer
/// error or several relayers running.
NoNewConfirmations,
/// 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(MessageNonce),
}
/// Outbound messages lane.
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()
}
/// Send message over lane.
///
/// Returns new message nonce.
pub fn send_message(&mut self, message_payload: MessagePayload) -> 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>>,
) -> ReceivalConfirmationResult {
let mut data = self.storage.data();
if latest_delivered_nonce <= data.latest_received_nonce {
return ReceivalConfirmationResult::NoNewConfirmations
}
if latest_delivered_nonce > data.latest_generated_nonce {
return ReceivalConfirmationResult::FailedToConfirmFutureMessages
}
if latest_delivered_nonce - data.latest_received_nonce > 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.
return ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected(
latest_delivered_nonce - data.latest_received_nonce,
)
}
if let Err(e) = ensure_unrewarded_relayers_are_correct(latest_delivered_nonce, relayers) {
return e
}
let prev_latest_received_nonce = data.latest_received_nonce;
data.latest_received_nonce = latest_delivered_nonce;
self.storage.set_data(data);
ReceivalConfirmationResult::ConfirmedMessages(DeliveredMessages {
begin: prev_latest_received_nonce + 1,
end: latest_delivered_nonce,
})
}
/// Prune at most `max_messages_to_prune` already received messages.
///
/// Returns weight, consumed by messages pruning and lane state update.
pub fn prune_messages(
&mut self,
db_weight: RuntimeDbWeight,
mut remaining_weight: Weight,
) -> Weight {
let write_weight = db_weight.writes(1);
let two_writes_weight = write_weight + write_weight;
let mut spent_weight = Weight::zero();
let mut data = self.storage.data();
while remaining_weight.all_gte(two_writes_weight) &&
data.oldest_unpruned_nonce <= data.latest_received_nonce
{
self.storage.remove_message(&data.oldest_unpruned_nonce);
spent_weight += write_weight;
remaining_weight -= write_weight;
data.oldest_unpruned_nonce += 1;
}
if !spent_weight.is_zero() {
spent_weight += write_weight;
self.storage.set_data(data);
}
spent_weight
}
}
/// 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<(), ReceivalConfirmationResult> {
let mut last_entry_end: Option<MessageNonce> = None;
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(ReceivalConfirmationResult::EmptyUnrewardedRelayerEntry)
}
// every entry must confirm range of messages that follows previous entry range
// (guaranteed by the `InboundLane::receive_message()`)
if let Some(last_entry_end) = last_entry_end {
let expected_entry_begin = last_entry_end.checked_add(1);
if expected_entry_begin != Some(entry.messages.begin) {
return Err(ReceivalConfirmationResult::NonConsecutiveUnrewardedRelayerEntries)
}
}
last_entry_end = Some(entry.messages.end);
// 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 {
// technically this will be detected in the next loop iteration as
// `InvalidNumberOfDispatchResults` but to guarantee safety of loop operations below
// this is detected now
return Err(ReceivalConfirmationResult::FailedToConfirmFutureMessages)
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
mock::{
outbound_message_data, run_test, unrewarded_relayer, TestRelayer, TestRuntime,
REGULAR_PAYLOAD, TEST_LANE_ID,
},
outbound_lane,
};
use frame_support::weights::constants::RocksDbWeight;
use sp_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>>,
) -> ReceivalConfirmationResult {
run_test(|| {
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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 = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(1..=3)),
);
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 3);
});
}
#[test]
fn confirm_delivery_rejects_nonce_lesser_than_latest_received() {
run_test(|| {
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(1..=3)),
);
assert_eq!(
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
ReceivalConfirmationResult::NoNewConfirmations,
);
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 3);
assert_eq!(
lane.confirm_delivery(1, 2, &unrewarded_relayers(1..=1)),
ReceivalConfirmationResult::NoNewConfirmations,
);
assert_eq!(lane.storage.data().latest_generated_nonce, 3);
assert_eq!(lane.storage.data().latest_received_nonce, 3);
});
}
#[test]
fn confirm_delivery_rejects_nonce_larger_than_last_generated() {
assert_eq!(
assert_3_messages_confirmation_fails(10, &unrewarded_relayers(1..=10),),
ReceivalConfirmationResult::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).into_iter())
.chain(unrewarded_relayers(3..=3).into_iter())
.collect(),
),
ReceivalConfirmationResult::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).into_iter())
.chain(unrewarded_relayers(2..=3).into_iter())
.collect(),
),
ReceivalConfirmationResult::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).into_iter())
.chain(unrewarded_relayers(2..=2).into_iter())
.collect(),
),
ReceivalConfirmationResult::NonConsecutiveUnrewardedRelayerEntries,
);
}
#[test]
fn prune_messages_works() {
run_test(|| {
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
// when lane is empty, nothing is pruned
assert_eq!(
lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)),
Weight::zero()
);
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
// when nothing is confirmed, nothing is pruned
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!(lane.storage.message(&1).is_some());
assert!(lane.storage.message(&2).is_some());
assert!(lane.storage.message(&3).is_some());
assert_eq!(
lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)),
Weight::zero()
);
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1);
// after confirmation, some messages are received
assert_eq!(
lane.confirm_delivery(2, 2, &unrewarded_relayers(1..=2)),
ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(1..=2)),
);
assert_eq!(
lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)),
RocksDbWeight::get().writes(3),
);
assert!(lane.storage.message(&1).is_none());
assert!(lane.storage.message(&2).is_none());
assert!(lane.storage.message(&3).is_some());
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3);
// after last message is confirmed, everything is pruned
assert_eq!(
lane.confirm_delivery(1, 3, &unrewarded_relayers(3..=3)),
ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(3..=3)),
);
assert_eq!(
lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)),
RocksDbWeight::get().writes(2),
);
assert!(lane.storage.message(&1).is_none());
assert!(lane.storage.message(&2).is_none());
assert!(lane.storage.message(&3).is_none());
assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4);
});
}
#[test]
fn confirm_delivery_detects_when_more_than_expected_messages_are_confirmed() {
run_test(|| {
let mut lane = outbound_lane::<TestRuntime, _>(TEST_LANE_ID);
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)),
ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected(3),
);
assert_eq!(
lane.confirm_delivery(2, 3, &unrewarded_relayers(1..=3)),
ReceivalConfirmationResult::TryingToConfirmMoreMessagesThanExpected(3),
);
assert_eq!(
lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)),
ReceivalConfirmationResult::ConfirmedMessages(delivered_messages(1..=3)),
);
});
}
}
+163
View File
@@ -0,0 +1,163 @@
// 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/>.
//! Autogenerated weights for `pallet_bridge_messages`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-11-17, STEPS: 50, REPEAT: 20
//! LOW RANGE: [], HIGH RANGE: []
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled
//! CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// target/release/millau-bridge-node
// benchmark
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_bridge_messages
// --extrinsic=*
// --execution=wasm
// --wasm-execution=Compiled
// --heap-pages=4096
// --output=./modules/messages/src/weights.rs
// --template=./.maintain/millau-weight-template.hbs
#![allow(clippy::all)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for `pallet_bridge_messages`.
pub trait WeightInfo {
fn receive_single_message_proof() -> Weight;
fn receive_two_messages_proof() -> Weight;
fn receive_single_message_proof_with_outbound_lane_state() -> Weight;
fn receive_single_message_proof_1_kb() -> Weight;
fn receive_single_message_proof_16_kb() -> Weight;
fn receive_single_prepaid_message_proof() -> 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;
}
/// Weights for `pallet_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: frame_system::Config> WeightInfo for BridgeWeight<T> {
fn receive_single_message_proof() -> Weight {
Weight::from_ref_time(50_596_000 as u64)
.saturating_add(T::DbWeight::get().reads(4 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
fn receive_two_messages_proof() -> Weight {
Weight::from_ref_time(77_041_000 as u64)
.saturating_add(T::DbWeight::get().reads(4 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
Weight::from_ref_time(58_331_000 as u64)
.saturating_add(T::DbWeight::get().reads(4 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
fn receive_single_message_proof_1_kb() -> Weight {
Weight::from_ref_time(48_061_000 as u64)
.saturating_add(T::DbWeight::get().reads(3 as u64))
.saturating_add(T::DbWeight::get().writes(1 as u64))
}
fn receive_single_message_proof_16_kb() -> Weight {
Weight::from_ref_time(101_601_000 as u64)
.saturating_add(T::DbWeight::get().reads(3 as u64))
.saturating_add(T::DbWeight::get().writes(1 as u64))
}
fn receive_single_prepaid_message_proof() -> Weight {
Weight::from_ref_time(49_646_000 as u64)
.saturating_add(T::DbWeight::get().reads(4 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
fn receive_delivery_proof_for_single_message() -> Weight {
Weight::from_ref_time(55_108_000 as u64)
.saturating_add(T::DbWeight::get().reads(4 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
Weight::from_ref_time(53_917_000 as u64)
.saturating_add(T::DbWeight::get().reads(4 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
Weight::from_ref_time(57_335_000 as u64)
.saturating_add(T::DbWeight::get().reads(5 as u64))
.saturating_add(T::DbWeight::get().writes(3 as u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
fn receive_single_message_proof() -> Weight {
Weight::from_ref_time(50_596_000 as u64)
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
fn receive_two_messages_proof() -> Weight {
Weight::from_ref_time(77_041_000 as u64)
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
Weight::from_ref_time(58_331_000 as u64)
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
fn receive_single_message_proof_1_kb() -> Weight {
Weight::from_ref_time(48_061_000 as u64)
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().writes(1 as u64))
}
fn receive_single_message_proof_16_kb() -> Weight {
Weight::from_ref_time(101_601_000 as u64)
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().writes(1 as u64))
}
fn receive_single_prepaid_message_proof() -> Weight {
Weight::from_ref_time(49_646_000 as u64)
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
fn receive_delivery_proof_for_single_message() -> Weight {
Weight::from_ref_time(55_108_000 as u64)
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
Weight::from_ref_time(53_917_000 as u64)
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
Weight::from_ref_time(57_335_000 as u64)
.saturating_add(RocksDbWeight::get().reads(5 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
}
+289
View File
@@ -0,0 +1,289 @@
// 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/>.
//! Weight-related utilities.
use crate::weights::WeightInfo;
use bp_messages::{MessageNonce, UnrewardedRelayersState};
use bp_runtime::{PreComputedSize, Size};
use frame_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, built at
/// Rialto chain. 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>() {
// verify `receive_messages_proof` weight components
assert_ne!(W::receive_messages_proof_overhead(), Weight::zero());
assert_ne!(W::receive_messages_proof_messages_overhead(1), Weight::zero());
assert_ne!(W::receive_messages_proof_outbound_lane_state_overhead(), Weight::zero());
assert_ne!(W::storage_proof_size_overhead(1), Weight::zero());
// verify `receive_messages_delivery_proof` weight components
assert_ne!(W::receive_messages_delivery_proof_overhead(), Weight::zero());
assert_ne!(W::storage_proof_size_overhead(1), Weight::zero());
}
/// 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 {} + {} is larger than maximal possible transaction size {}",
max_incoming_message_proof_size,
SIGNED_EXTENSIONS_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 + {} is larger than maximal possible transaction weight {}",
max_delivery_transaction_dispatch_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 {} + {} is larger than maximal possible transaction size {}",
max_inbound_lane_data_proof_size_from_peer_chain,
SIGNED_EXTENSIONS_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 {} is larger than maximal possible transaction weight {}",
max_confirmation_transaction_dispatch_weight,
max_extrinsic_weight,
);
}
/// 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;
// 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 transaction_overhead = Self::receive_messages_proof_overhead();
let outbound_state_delivery_weight =
Self::receive_messages_proof_outbound_lane_state_overhead();
let messages_delivery_weight =
Self::receive_messages_proof_messages_overhead(MessageNonce::from(messages_count));
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),
);
transaction_overhead
.saturating_add(outbound_state_delivery_weight)
.saturating_add(messages_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 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(messages_overhead)
.saturating_add(relayers_overhead)
.saturating_add(proof_size_overhead)
}
// Functions that are used by extrinsics weights formulas.
/// Returns weight overhead of message delivery transaction (`receive_messages_proof`).
fn receive_messages_proof_overhead() -> Weight {
let weight_of_two_messages_and_two_tx_overheads =
Self::receive_single_message_proof().saturating_mul(2);
let weight_of_two_messages_and_single_tx_overhead = Self::receive_two_messages_proof();
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 given a number of messages with
/// message delivery transaction (`receive_messages_proof`).
fn receive_messages_proof_messages_overhead(messages: MessageNonce) -> Weight {
let weight_of_two_messages_and_single_tx_overhead = Self::receive_two_messages_proof();
let weight_of_single_message_and_single_tx_overhead = Self::receive_single_message_proof();
weight_of_two_messages_and_single_tx_overhead
.saturating_sub(weight_of_single_message_and_single_tx_overhead)
.saturating_mul(messages as _)
}
/// 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_message_proof_16_kb() -
Self::receive_single_message_proof_1_kb()) /
(15 * 1024);
proof_size_in_bytes * byte_weight
}
/// Returns weight of the pay-dispatch-fee operation for inbound messages.
///
/// This function may return zero if runtime doesn't support pay-dispatch-fee-at-target-chain
/// option.
fn pay_inbound_dispatch_fee_overhead() -> Weight {
Self::receive_single_message_proof()
.saturating_sub(Self::receive_single_prepaid_message_proof())
}
}
impl WeightInfoExt for () {
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
}
impl<T: frame_system::Config> WeightInfoExt for crate::weights::BridgeWeight<T> {
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
}
+55
View File
@@ -0,0 +1,55 @@
[package]
name = "pallet-bridge-parachains"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false }
log = { version = "0.4.17", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
# Bridge Dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-parachains = { path = "../../primitives/parachains", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
pallet-bridge-grandpa = { path = "../grandpa", default-features = false }
# Substrate Dependencies
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[dev-dependencies]
bp-header-chain = { path = "../../primitives/header-chain" }
bp-test-utils = { path = "../../primitives/test-utils" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-parachains/std",
"bp-polkadot-core/std",
"bp-runtime/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"frame-benchmarking/std",
"log/std",
"pallet-bridge-grandpa/std",
"scale-info/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
]
+106
View File
@@ -0,0 +1,106 @@
// 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/>.
//! Parachains finality pallet benchmarking.
use crate::{
weights_ext::DEFAULT_PARACHAIN_HEAD_SIZE, Call, RelayBlockHash, RelayBlockHasher,
RelayBlockNumber,
};
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
use bp_runtime::StorageProofSize;
use frame_benchmarking::{account, benchmarks_instance_pallet};
use frame_system::RawOrigin;
use sp_std::prelude::*;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config<I>, I: 'static>(crate::Pallet<T, I>);
/// Trait that must be implemented by runtime to benchmark the parachains finality pallet.
pub trait Config<I: 'static>: crate::Config<I> {
/// Generate parachain heads proof and prepare environment for verifying this proof.
fn prepare_parachain_heads_proof(
parachains: &[ParaId],
parachain_head_size: u32,
proof_size: StorageProofSize,
) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof, Vec<(ParaId, ParaHash)>);
}
benchmarks_instance_pallet! {
where_clause {
where
<T as pallet_bridge_grandpa::Config<T::BridgesGrandpaPalletInstance>>::BridgedChain:
bp_runtime::Chain<
BlockNumber = RelayBlockNumber,
Hash = RelayBlockHash,
Hasher = RelayBlockHasher,
>,
}
// Benchmark `submit_parachain_heads` extrinsic with different number of parachains.
submit_parachain_heads_with_n_parachains {
let p in 1..1024;
let sender = account("sender", 0, 0);
let parachains = (1..=p).map(ParaId).collect::<Vec<_>>();
let (relay_block_number, relay_block_hash, parachain_heads_proof, parachains_heads) = T::prepare_parachain_heads_proof(
&parachains,
DEFAULT_PARACHAIN_HEAD_SIZE,
StorageProofSize::Minimal(0),
);
let at_relay_block = (relay_block_number, relay_block_hash);
}: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains_heads, parachain_heads_proof)
verify {
for parachain in parachains {
assert!(crate::Pallet::<T, I>::best_parachain_head(parachain).is_some());
}
}
// Benchmark `submit_parachain_heads` extrinsic with 1kb proof size.
submit_parachain_heads_with_1kb_proof {
let sender = account("sender", 0, 0);
let parachains = vec![ParaId(1)];
let (relay_block_number, relay_block_hash, parachain_heads_proof, parachains_heads) = T::prepare_parachain_heads_proof(
&parachains,
DEFAULT_PARACHAIN_HEAD_SIZE,
StorageProofSize::HasExtraNodes(1024),
);
let at_relay_block = (relay_block_number, relay_block_hash);
}: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains_heads, parachain_heads_proof)
verify {
for parachain in parachains {
assert!(crate::Pallet::<T, I>::best_parachain_head(parachain).is_some());
}
}
// Benchmark `submit_parachain_heads` extrinsic with 16kb proof size.
submit_parachain_heads_with_16kb_proof {
let sender = account("sender", 0, 0);
let parachains = vec![ParaId(1)];
let (relay_block_number, relay_block_hash, parachain_heads_proof, parachains_heads) = T::prepare_parachain_heads_proof(
&parachains,
DEFAULT_PARACHAIN_HEAD_SIZE,
StorageProofSize::HasExtraNodes(16 * 1024),
);
let at_relay_block = (relay_block_number, relay_block_hash);
}: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains_heads, parachain_heads_proof)
verify {
for parachain in parachains {
assert!(crate::Pallet::<T, I>::best_parachain_head(parachain).is_some());
}
}
}
+166
View File
@@ -0,0 +1,166 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{Config, Pallet, RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
use bp_runtime::FilterCall;
use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction};
/// Validate parachain heads in order to avoid "mining" transactions that provide
/// outdated bridged parachain 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 parachain
/// 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-parachain-head-per-transaction is how the
/// pallet will be used in our environment.
impl<
Call: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
T: frame_system::Config<RuntimeCall = Call> + Config<I>,
I: 'static,
> FilterCall<Call> for Pallet<T, I>
where
<T as pallet_bridge_grandpa::Config<T::BridgesGrandpaPalletInstance>>::BridgedChain:
bp_runtime::Chain<
BlockNumber = RelayBlockNumber,
Hash = RelayBlockHash,
Hasher = RelayBlockHasher,
>,
{
fn validate(call: &Call) -> TransactionValidity {
let (updated_at_relay_block_number, parachains) = match call.is_sub_type() {
Some(crate::Call::<T, I>::submit_parachain_heads {
ref at_relay_block,
ref parachains,
..
}) => (at_relay_block.0, parachains),
_ => return Ok(ValidTransaction::default()),
};
let (parachain, parachain_head_hash) = match parachains.as_slice() {
&[(parachain, parachain_head_hash)] => (parachain, parachain_head_hash),
_ => return Ok(ValidTransaction::default()),
};
let maybe_stored_best_head = crate::ParasInfo::<T, I>::get(parachain);
let is_valid = Self::validate_updated_parachain_head(
parachain,
&maybe_stored_best_head,
updated_at_relay_block_number,
parachain_head_hash,
"Rejecting obsolete parachain-head transaction",
);
if is_valid {
Ok(ValidTransaction::default())
} else {
InvalidTransaction::Stale.into()
}
}
}
#[cfg(test)]
mod tests {
use crate::{
extension::FilterCall,
mock::{run_test, RuntimeCall, TestRuntime},
ParaInfo, ParasInfo, RelayBlockNumber,
};
use bp_parachains::BestParaHeadHash;
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
fn validate_submit_parachain_heads(
num: RelayBlockNumber,
parachains: Vec<(ParaId, ParaHash)>,
) -> bool {
crate::Pallet::<TestRuntime>::validate(&RuntimeCall::Parachains(crate::Call::<
TestRuntime,
(),
>::submit_parachain_heads {
at_relay_block: (num, Default::default()),
parachains,
parachain_heads_proof: ParaHeadsProof(Vec::new()),
}))
.is_ok()
}
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_parachain_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_parachain_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_parachain_heads(20, vec![(ParaId(1), [1u8; 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();
assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
});
}
#[test]
fn extension_accepts_if_more_than_one_parachain_is_submitted() {
run_test(|| {
// when current best finalized is #10 and we're trying to import header#5, but another
// parachain head is also supplied => tx is accepted
sync_to_relay_header_10();
assert!(validate_submit_parachain_heads(
5,
vec![(ParaId(1), [1u8; 32].into()), (ParaId(2), [1u8; 32].into())]
));
});
}
}
File diff suppressed because it is too large Load Diff
+194
View File
@@ -0,0 +1,194 @@
// 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 bp_polkadot_core::parachains::ParaId;
use bp_runtime::Chain;
use frame_support::{construct_runtime, parameter_types, traits::IsInVec, weights::Weight};
use sp_runtime::{
testing::{Header, H256},
traits::{BlakeTwo256, Header as HeaderT, IdentityLookup},
Perbill,
};
use crate as pallet_bridge_parachains;
pub type AccountId = u64;
pub type TestNumber = u64;
pub type RelayBlockHeader =
sp_runtime::generic::Header<crate::RelayBlockNumber, crate::RelayBlockHasher>;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
pub const PARAS_PALLET_NAME: &str = "Paras";
pub const UNTRACKED_PARACHAIN_ID: u32 = 10;
pub const MAXIMAL_PARACHAIN_HEAD_SIZE: u32 = 512;
construct_runtime! {
pub enum TestRuntime where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Grandpa1: pallet_bridge_grandpa::<Instance1>::{Pallet},
Grandpa2: pallet_bridge_grandpa::<Instance2>::{Pallet},
Parachains: pallet_bridge_parachains::{Call, Pallet, Event<T>},
}
}
parameter_types! {
pub const BlockHashCount: TestNumber = 250;
pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024);
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Config for TestRuntime {
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type RuntimeCall = RuntimeCall;
type BlockNumber = TestNumber;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type BaseCallFilter = frame_support::traits::Everything;
type SystemWeightInfo = ();
type DbWeight = ();
type BlockWeights = ();
type BlockLength = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
parameter_types! {
pub const MaxRequests: u32 = 2;
pub const HeadersToKeep: u32 = 5;
pub const SessionLength: u64 = 5;
pub const NumValidators: u32 = 5;
}
impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance1> for TestRuntime {
type BridgedChain = TestBridgedChain;
type MaxRequests = MaxRequests;
type HeadersToKeep = HeadersToKeep;
type MaxBridgedAuthorities = frame_support::traits::ConstU32<5>;
type MaxBridgedHeaderSize = frame_support::traits::ConstU32<512>;
type WeightInfo = ();
}
impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance2> for TestRuntime {
type BridgedChain = TestBridgedChain;
type MaxRequests = MaxRequests;
type HeadersToKeep = HeadersToKeep;
type MaxBridgedAuthorities = frame_support::traits::ConstU32<5>;
type MaxBridgedHeaderSize = frame_support::traits::ConstU32<512>;
type WeightInfo = ();
}
parameter_types! {
pub const HeadsToKeep: u32 = 4;
pub const ParasPalletName: &'static str = PARAS_PALLET_NAME;
pub GetTenFirstParachains: Vec<ParaId> = (0..10).map(ParaId).collect();
}
impl pallet_bridge_parachains::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1;
type ParasPalletName = ParasPalletName;
type TrackedParachains = IsInVec<GetTenFirstParachains>;
type HeadsToKeep = HeadsToKeep;
type MaxParaHeadSize = frame_support::traits::ConstU32<MAXIMAL_PARACHAIN_HEAD_SIZE>;
}
#[derive(Debug)]
pub struct TestBridgedChain;
impl Chain for TestBridgedChain {
type BlockNumber = crate::RelayBlockNumber;
type Hash = crate::RelayBlockHash;
type Hasher = crate::RelayBlockHasher;
type Header = RelayBlockHeader;
type AccountId = AccountId;
type Balance = u32;
type Index = u32;
type Signature = sp_runtime::testing::TestSignature;
fn max_extrinsic_size() -> u32 {
unreachable!()
}
fn max_extrinsic_weight() -> Weight {
unreachable!()
}
}
#[derive(Debug)]
pub struct OtherBridgedChain;
impl Chain for OtherBridgedChain {
type BlockNumber = u64;
type Hash = crate::RelayBlockHash;
type Hasher = crate::RelayBlockHasher;
type Header = sp_runtime::generic::Header<u64, crate::RelayBlockHasher>;
type AccountId = AccountId;
type Balance = u32;
type Index = u32;
type Signature = sp_runtime::testing::TestSignature;
fn max_extrinsic_size() -> u32 {
unreachable!()
}
fn max_extrinsic_weight() -> Weight {
unreachable!()
}
}
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
System::set_block_number(1);
System::reset_events();
test()
})
}
pub fn test_relay_header(
num: crate::RelayBlockNumber,
state_root: crate::RelayBlockHash,
) -> RelayBlockHeader {
RelayBlockHeader::new(
num,
Default::default(),
state_root,
Default::default(),
Default::default(),
)
}
+101
View File
@@ -0,0 +1,101 @@
// 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/>.
//! Autogenerated weights for `pallet_bridge_parachains`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-11-17, STEPS: 50, REPEAT: 20
//! LOW RANGE: [], HIGH RANGE: []
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled
//! CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// target/release/millau-bridge-node
// benchmark
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_bridge_parachains
// --extrinsic=*
// --execution=wasm
// --wasm-execution=Compiled
// --heap-pages=4096
// --output=./modules/parachains/src/weights.rs
// --template=./.maintain/millau-weight-template.hbs
#![allow(clippy::all)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for `pallet_bridge_parachains`.
pub trait WeightInfo {
fn submit_parachain_heads_with_n_parachains(p: u32) -> Weight;
fn submit_parachain_heads_with_1kb_proof() -> Weight;
fn submit_parachain_heads_with_16kb_proof() -> Weight;
}
/// Weights for `pallet_bridge_parachains` 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: frame_system::Config> WeightInfo for BridgeWeight<T> {
fn submit_parachain_heads_with_n_parachains(p: u32) -> Weight {
Weight::from_ref_time(51_173_000 as u64)
.saturating_add(Weight::from_ref_time(24_495_968 as u64).saturating_mul(p as u64))
.saturating_add(T::DbWeight::get().reads(2 as u64))
.saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(p as u64)))
.saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(p as u64)))
}
fn submit_parachain_heads_with_1kb_proof() -> Weight {
Weight::from_ref_time(58_175_000 as u64)
.saturating_add(T::DbWeight::get().reads(4 as u64))
.saturating_add(T::DbWeight::get().writes(3 as u64))
}
fn submit_parachain_heads_with_16kb_proof() -> Weight {
Weight::from_ref_time(101_796_000 as u64)
.saturating_add(T::DbWeight::get().reads(4 as u64))
.saturating_add(T::DbWeight::get().writes(3 as u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
fn submit_parachain_heads_with_n_parachains(p: u32) -> Weight {
Weight::from_ref_time(51_173_000 as u64)
.saturating_add(Weight::from_ref_time(24_495_968 as u64).saturating_mul(p as u64))
.saturating_add(RocksDbWeight::get().reads(2 as u64))
.saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(p as u64)))
.saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(p as u64)))
}
fn submit_parachain_heads_with_1kb_proof() -> Weight {
Weight::from_ref_time(58_175_000 as u64)
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
fn submit_parachain_heads_with_16kb_proof() -> Weight {
Weight::from_ref_time(101_796_000 as u64)
.saturating_add(RocksDbWeight::get().reads(4 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
}
+107
View File
@@ -0,0 +1,107 @@
// 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/>.
//! Weight-related utilities.
use crate::weights::{BridgeWeight, WeightInfo};
use bp_runtime::Size;
use frame_support::weights::{RuntimeDbWeight, Weight};
/// Size of the regular parachain head.
///
/// It's not that we are expecting all parachain 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 Polkadot block.
pub const DEFAULT_PARACHAIN_HEAD_SIZE: u32 = 384;
/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at
/// the Rialto chain.
pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
/// Extended weight info.
pub trait WeightInfoExt: WeightInfo {
/// 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 parachain heads delivery extrinsic.
fn submit_parachain_heads_weight(
db_weight: RuntimeDbWeight,
proof: &impl Size,
parachains_count: u32,
) -> Weight {
// weight of the `submit_parachain_heads` with exactly `parachains_count` parachain
// heads of the default size (`DEFAULT_PARACHAIN_HEAD_SIZE`)
let base_weight = Self::submit_parachain_heads_with_n_parachains(parachains_count);
// overhead because of extra storage proof bytes
let expected_proof_size = parachains_count
.saturating_mul(DEFAULT_PARACHAIN_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::parachain_head_pruning_weight(db_weight).saturating_mul(parachains_count as u64);
base_weight.saturating_add(proof_size_overhead).saturating_add(pruning_weight)
}
/// Returns weight of single parachain head storage update.
///
/// This weight only includes db write operations that happens if parachain head is actually
/// updated. All extra weights (weight of storage proof validation, additional checks, ...) is
/// not included.
fn parachain_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 parachain head pruning.
fn parachain_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_parachain_heads_with_16kb_proof() -
Self::submit_parachain_heads_with_1kb_proof()) /
(15 * 1024);
extra_byte_weight.saturating_mul(extra_proof_bytes as u64)
}
}
impl WeightInfoExt for () {
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
}
impl<T: frame_system::Config> WeightInfoExt for BridgeWeight<T> {
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
}
+51
View File
@@ -0,0 +1,51 @@
[package]
name = "pallet-bridge-relayers"
description = "Module used to store relayer rewards and coordinate relayers set."
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false }
log = { version = "0.4.17", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
# Bridge dependencies
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-relayers = { path = "../../primitives/relayers", default-features = false }
pallet-bridge-messages = { path = "../messages", default-features = false }
# Substrate Dependencies
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[dev-dependencies]
bp-runtime = { path = "../../primitives/runtime" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
[features]
default = ["std"]
std = [
"bp-messages/std",
"bp-relayers/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-bridge-messages/std",
"scale-info/std",
"sp-arithmetic/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
]
+41
View File
@@ -0,0 +1,41 @@
// 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/>.
//! Benchmarks for the relayers Pallet.
#![cfg(feature = "runtime-benchmarks")]
use crate::*;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_system::RawOrigin;
/// Reward amount that is (hopefully) is larger than existential deposit across all chains.
const REWARD_AMOUNT: u32 = u32::MAX;
benchmarks! {
// Benchmark `claim_rewards` call.
claim_rewards {
let lane = [0, 0, 0, 0];
let relayer: T::AccountId = whitelisted_caller();
RelayerRewards::<T>::insert(&relayer, lane, T::Reward::from(REWARD_AMOUNT));
}: _(RawOrigin::Signed(relayer), lane)
verify {
// 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
}
}
+247
View File
@@ -0,0 +1,247 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Runtime module that is used to store relayer rewards and (in the future) to
//! coordinate relations between relayers.
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
use bp_messages::LaneId;
use bp_relayers::PaymentProcedure;
use frame_support::sp_runtime::Saturating;
use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero};
use sp_std::marker::PhantomData;
use weights::WeightInfo;
pub use pallet::*;
pub use payment_adapter::MessageDeliveryAndDispatchPaymentAdapter;
mod benchmarking;
mod mock;
mod payment_adapter;
pub mod weights;
/// The target that will be used when publishing logs related to this pallet.
pub const LOG_TARGET: &str = "runtime::bridge-relayers";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type of relayer reward.
type Reward: AtLeast32BitUnsigned + Copy + Parameter + MaxEncodedLen;
/// Pay rewards adapter.
type PaymentProcedure: PaymentProcedure<Self::AccountId, Self::Reward>;
/// Pallet call weights.
type WeightInfo: WeightInfo;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Claim accumulated rewards.
#[pallet::weight(T::WeightInfo::claim_rewards())]
pub fn claim_rewards(origin: OriginFor<T>, lane_id: LaneId) -> DispatchResult {
let relayer = ensure_signed(origin)?;
RelayerRewards::<T>::try_mutate_exists(
&relayer,
lane_id,
|maybe_reward| -> DispatchResult {
let reward = maybe_reward.take().ok_or(Error::<T>::NoRewardForRelayer)?;
T::PaymentProcedure::pay_reward(&relayer, lane_id, reward).map_err(|e| {
log::trace!(
target: LOG_TARGET,
"Failed to pay {:?} rewards to {:?}: {:?}",
lane_id,
relayer,
e,
);
Error::<T>::FailedToPayReward
})?;
Self::deposit_event(Event::<T>::RewardPaid {
relayer: relayer.clone(),
lane_id,
reward,
});
Ok(())
},
)
}
}
impl<T: Config> Pallet<T> {
/// Register reward for given relayer.
pub fn register_relayer_reward(lane_id: LaneId, relayer: &T::AccountId, reward: T::Reward) {
if reward.is_zero() {
return
}
RelayerRewards::<T>::mutate(relayer, lane_id, |old_reward: &mut Option<T::Reward>| {
let new_reward = old_reward.unwrap_or_else(Zero::zero).saturating_add(reward);
*old_reward = Some(new_reward);
log::trace!(
target: crate::LOG_TARGET,
"Relayer {:?} can now claim reward: {:?}",
relayer,
new_reward,
);
});
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Reward has been paid to the relayer.
RewardPaid {
/// Relayer account that has been rewarded.
relayer: T::AccountId,
/// Relayer has received reward for serving this lane.
lane_id: LaneId,
/// Reward amount.
reward: T::Reward,
},
}
#[pallet::error]
pub enum Error<T> {
/// No reward can be claimed by given relayer.
NoRewardForRelayer,
/// Reward payment procedure has failed.
FailedToPayReward,
}
/// Map of the relayer => accumulated reward.
#[pallet::storage]
pub type RelayerRewards<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Identity,
LaneId,
T::Reward,
OptionQuery,
>;
}
#[cfg(test)]
mod tests {
use super::*;
use mock::{RuntimeEvent as TestEvent, *};
use crate::Event::RewardPaid;
use frame_support::{assert_noop, assert_ok, traits::fungible::Inspect};
use frame_system::{EventRecord, Pallet as System, Phase};
use sp_runtime::DispatchError;
fn get_ready_for_events() {
System::<TestRuntime>::set_block_number(1);
System::<TestRuntime>::reset_events();
}
#[test]
fn root_cant_claim_anything() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime>::claim_rewards(RuntimeOrigin::root(), TEST_LANE_ID),
DispatchError::BadOrigin,
);
});
}
#[test]
fn relayer_cant_claim_if_no_reward_exists() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(REGULAR_RELAYER),
TEST_LANE_ID
),
Error::<TestRuntime>::NoRewardForRelayer,
);
});
}
#[test]
fn relayer_cant_claim_if_payment_procedure_fails() {
run_test(|| {
RelayerRewards::<TestRuntime>::insert(FAILING_RELAYER, TEST_LANE_ID, 100);
assert_noop!(
Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(FAILING_RELAYER),
TEST_LANE_ID
),
Error::<TestRuntime>::FailedToPayReward,
);
});
}
#[test]
fn relayer_can_claim_reward() {
run_test(|| {
get_ready_for_events();
RelayerRewards::<TestRuntime>::insert(REGULAR_RELAYER, TEST_LANE_ID, 100);
assert_ok!(Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(REGULAR_RELAYER),
TEST_LANE_ID
));
assert_eq!(RelayerRewards::<TestRuntime>::get(REGULAR_RELAYER, TEST_LANE_ID), None);
//Check if the `RewardPaid` event was emitted.
assert_eq!(
System::<TestRuntime>::events(),
vec![EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(RewardPaid {
relayer: REGULAR_RELAYER,
lane_id: TEST_LANE_ID,
reward: 100
}),
topics: vec![],
}],
);
});
}
#[test]
fn mint_reward_payment_procedure_actually_mints_tokens() {
type Balances = pallet_balances::Pallet<TestRuntime>;
run_test(|| {
assert_eq!(Balances::balance(&1), 0);
assert_eq!(Balances::total_issuance(), 0);
bp_relayers::MintReward::<Balances, AccountId>::pay_reward(&1, TEST_LANE_ID, 100)
.unwrap();
assert_eq!(Balances::balance(&1), 100);
assert_eq!(Balances::total_issuance(), 100);
});
}
}
+161
View File
@@ -0,0 +1,161 @@
// 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 pallet_bridge_relayers;
use bp_messages::{
source_chain::ForbidOutboundMessages, target_chain::ForbidInboundMessages, LaneId,
};
use bp_relayers::PaymentProcedure;
use frame_support::{parameter_types, weights::RuntimeDbWeight};
use sp_core::H256;
use sp_runtime::{
testing::Header as SubstrateHeader,
traits::{BlakeTwo256, IdentityLookup},
};
pub type AccountId = u64;
pub type Balance = u64;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
frame_support::construct_runtime! {
pub enum TestRuntime where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Event<T>},
Messages: pallet_bridge_messages::{Pallet, Event<T>},
Relayers: pallet_bridge_relayers::{Pallet, Call, Event<T>},
}
}
parameter_types! {
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
}
impl frame_system::Config for TestRuntime {
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type RuntimeCall = RuntimeCall;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = SubstrateHeader;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = frame_support::traits::ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = ();
type OnKilledAccount = ();
type BaseCallFilter = frame_support::traits::Everything;
type SystemWeightInfo = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = DbWeight;
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
impl pallet_balances::Config for TestRuntime {
type MaxLocks = ();
type Balance = Balance;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = frame_support::traits::ConstU64<1>;
type AccountStore = frame_system::Pallet<TestRuntime>;
type WeightInfo = ();
type MaxReserves = ();
type ReserveIdentifier = ();
}
parameter_types! {
pub const TestBridgedChainId: bp_runtime::ChainId = *b"test";
pub ActiveOutboundLanes: &'static [bp_messages::LaneId] = &[[0, 0, 0, 0]];
}
// we're not testing messages pallet here, so values in this config might be crazy
impl pallet_bridge_messages::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type ActiveOutboundLanes = ActiveOutboundLanes;
type MaxUnrewardedRelayerEntriesAtInboundLane = frame_support::traits::ConstU64<8>;
type MaxUnconfirmedMessagesAtInboundLane = frame_support::traits::ConstU64<8>;
type MaximalOutboundPayloadSize = frame_support::traits::ConstU32<1024>;
type OutboundPayload = ();
type InboundPayload = ();
type InboundRelayer = AccountId;
type TargetHeaderChain = ForbidOutboundMessages;
type LaneMessageVerifier = ForbidOutboundMessages;
type MessageDeliveryAndDispatchPayment = ();
type SourceHeaderChain = ForbidInboundMessages;
type MessageDispatch = ForbidInboundMessages;
type BridgedChainId = TestBridgedChainId;
}
impl pallet_bridge_relayers::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type Reward = Balance;
type PaymentProcedure = TestPaymentProcedure;
type WeightInfo = ();
}
/// Message lane that we're using in tests.
pub const TEST_LANE_ID: LaneId = [0, 0, 0, 0];
/// Regular relayer that may receive rewards.
pub const REGULAR_RELAYER: AccountId = 1;
/// Relayer that can't receive rewards.
pub const FAILING_RELAYER: AccountId = 2;
/// Payment procedure that rejects payments to the `FAILING_RELAYER`.
pub struct TestPaymentProcedure;
impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
type Error = ();
fn pay_reward(
relayer: &AccountId,
_lane_id: LaneId,
_reward: Balance,
) -> Result<(), Self::Error> {
match *relayer {
FAILING_RELAYER => Err(()),
_ => Ok(()),
}
}
}
/// Run pallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
let t = frame_system::GenesisConfig::default().build_storage::<TestRuntime>().unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(test)
}
+171
View File
@@ -0,0 +1,171 @@
// 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/>.
//! Code that allows relayers pallet to be used as a delivery+dispatch payment mechanism
//! for the messages pallet.
use crate::{Config, Pallet};
use bp_messages::source_chain::{MessageDeliveryAndDispatchPayment, RelayersRewards};
use frame_support::sp_runtime::SaturatedConversion;
use sp_arithmetic::traits::{Saturating, UniqueSaturatedFrom, Zero};
use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive};
/// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism
/// for the messages pallet.
pub struct MessageDeliveryAndDispatchPaymentAdapter<T, MessagesInstance>(
PhantomData<(T, MessagesInstance)>,
);
impl<T, MessagesInstance> MessageDeliveryAndDispatchPayment<T::RuntimeOrigin, T::AccountId>
for MessageDeliveryAndDispatchPaymentAdapter<T, MessagesInstance>
where
T: Config + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
{
type Error = &'static str;
fn pay_relayers_rewards(
lane_id: bp_messages::LaneId,
messages_relayers: VecDeque<bp_messages::UnrewardedRelayer<T::AccountId>>,
confirmation_relayer: &T::AccountId,
received_range: &RangeInclusive<bp_messages::MessageNonce>,
) {
let relayers_rewards = pallet_bridge_messages::calc_relayers_rewards::<T, MessagesInstance>(
messages_relayers,
received_range,
);
register_relayers_rewards::<T>(
confirmation_relayer,
relayers_rewards,
lane_id,
// TODO (https://github.com/paritytech/parity-bridges-common/issues/1318): this shall be fixed
// in some way. ATM the future of the `register_relayer_reward` is not yet known
100_000_u32.into(),
10_000_u32.into(),
);
}
}
// Update rewards to given relayers, optionally rewarding confirmation relayer.
fn register_relayers_rewards<T: Config>(
confirmation_relayer: &T::AccountId,
relayers_rewards: RelayersRewards<T::AccountId>,
lane_id: bp_messages::LaneId,
delivery_fee: T::Reward,
confirmation_fee: T::Reward,
) {
// reward every relayer except `confirmation_relayer`
let mut confirmation_relayer_reward = T::Reward::zero();
for (relayer, messages) in relayers_rewards {
// sane runtime configurations guarantee that the number of messages will be below
// `u32::MAX`
let mut relayer_reward =
T::Reward::unique_saturated_from(messages).saturating_mul(delivery_fee);
if relayer != *confirmation_relayer {
// If delivery confirmation is submitted by other relayer, let's deduct confirmation fee
// from relayer reward.
//
// If confirmation fee has been increased (or if it was the only component of message
// fee), then messages relayer may receive zero reward.
let mut confirmation_reward =
T::Reward::saturated_from(messages).saturating_mul(confirmation_fee);
confirmation_reward = sp_std::cmp::min(confirmation_reward, relayer_reward);
relayer_reward = relayer_reward.saturating_sub(confirmation_reward);
confirmation_relayer_reward =
confirmation_relayer_reward.saturating_add(confirmation_reward);
Pallet::<T>::register_relayer_reward(lane_id, &relayer, relayer_reward);
} else {
// If delivery confirmation is submitted by this relayer, let's add confirmation fee
// from other relayers to this relayer reward.
confirmation_relayer_reward =
confirmation_relayer_reward.saturating_add(relayer_reward);
}
}
// finally - pay reward to confirmation relayer
Pallet::<T>::register_relayer_reward(
lane_id,
confirmation_relayer,
confirmation_relayer_reward,
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{mock::*, RelayerRewards};
const RELAYER_1: AccountId = 1;
const RELAYER_2: AccountId = 2;
const RELAYER_3: AccountId = 3;
fn relayers_rewards() -> RelayersRewards<AccountId> {
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_LANE_ID,
50,
10,
);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_LANE_ID), Some(80));
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_LANE_ID), Some(170));
});
}
#[test]
fn confirmation_relayer_is_rewarded_if_it_has_not_delivered_any_delivered_messages() {
run_test(|| {
register_relayers_rewards::<TestRuntime>(
&RELAYER_3,
relayers_rewards(),
TEST_LANE_ID,
50,
10,
);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_LANE_ID), Some(80));
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_LANE_ID), Some(120));
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_3, TEST_LANE_ID), Some(50));
});
}
#[test]
fn only_confirmation_relayer_is_rewarded_if_confirmation_fee_has_significantly_increased() {
run_test(|| {
register_relayers_rewards::<TestRuntime>(
&RELAYER_3,
relayers_rewards(),
TEST_LANE_ID,
50,
1000,
);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_LANE_ID), None);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_LANE_ID), None);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_3, TEST_LANE_ID), Some(250));
});
}
}
+75
View File
@@ -0,0 +1,75 @@
// 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/>.
//! Autogenerated weights for `pallet_bridge_relayers`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-11-17, STEPS: 50, REPEAT: 20
//! LOW RANGE: [], HIGH RANGE: []
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled
//! CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// target/release/millau-bridge-node
// benchmark
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_bridge_relayers
// --extrinsic=*
// --execution=wasm
// --wasm-execution=Compiled
// --heap-pages=4096
// --output=./modules/relayers/src/weights.rs
// --template=./.maintain/millau-weight-template.hbs
#![allow(clippy::all)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for `pallet_bridge_relayers`.
pub trait WeightInfo {
fn claim_rewards() -> Weight;
}
/// Weights for `pallet_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: frame_system::Config> WeightInfo for BridgeWeight<T> {
fn claim_rewards() -> Weight {
Weight::from_ref_time(59_334_000 as u64)
.saturating_add(T::DbWeight::get().reads(2 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
fn claim_rewards() -> Weight {
Weight::from_ref_time(59_334_000 as u64)
.saturating_add(RocksDbWeight::get().reads(2 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
}
+35
View File
@@ -0,0 +1,35 @@
[package]
name = "pallet-shift-session-manager"
description = "A Substrate Runtime module that selects 2/3 of initial validators for every session"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
# Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-staking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[dev-dependencies]
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"pallet-session/std",
"scale-info/std",
"sp-staking/std",
"sp-std/std",
]
+268
View File
@@ -0,0 +1,268 @@
// 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/>.
//! Substrate session manager that selects 2/3 validators from initial set,
//! starting from session 2.
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::traits::{ValidatorSet, ValidatorSetWithIdentification};
use sp_std::prelude::*;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
#[pallet::disable_frame_system_supertrait_check]
pub trait Config: pallet_session::Config {}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {}
/// Validators of first two sessions.
#[pallet::storage]
pub(super) type InitialValidators<T: Config> = StorageValue<_, Vec<T::ValidatorId>>;
}
impl<T: pallet_session::Config + Config> ValidatorSet<T::AccountId> for Pallet<T> {
type ValidatorId = T::ValidatorId;
type ValidatorIdOf = T::ValidatorIdOf;
fn session_index() -> sp_staking::SessionIndex {
pallet_session::Pallet::<T>::current_index()
}
fn validators() -> Vec<Self::ValidatorId> {
pallet_session::Pallet::<T>::validators()
}
}
impl<T: Config> ValidatorSetWithIdentification<T::AccountId> for Pallet<T> {
type Identification = ();
type IdentificationOf = ();
}
impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> {
fn end_session(_: sp_staking::SessionIndex) {}
fn start_session(_: sp_staking::SessionIndex) {}
fn new_session(session_index: sp_staking::SessionIndex) -> Option<Vec<T::ValidatorId>> {
// we don't want to add even more fields to genesis config => just return None
if session_index == 0 || session_index == 1 {
return None
}
// the idea that on first call (i.e. when session 1 ends) we're reading current
// set of validators from session module (they are initial validators) and save
// in our 'local storage'.
// then for every session we select (deterministically) 2/3 of these initial
// validators to serve validators of new session
let available_validators = InitialValidators::<T>::get().unwrap_or_else(|| {
let validators = <pallet_session::Pallet<T>>::validators();
InitialValidators::<T>::put(validators.clone());
validators
});
Some(Self::select_validators(session_index, &available_validators))
}
}
impl<T: Config> Pallet<T> {
/// Select validators for session.
fn select_validators(
session_index: sp_staking::SessionIndex,
available_validators: &[T::ValidatorId],
) -> Vec<T::ValidatorId> {
let available_validators_count = available_validators.len();
let count = sp_std::cmp::max(1, 2 * available_validators_count / 3);
let offset = session_index as usize % available_validators_count;
let end = offset + count;
let session_validators = match end.overflowing_sub(available_validators_count) {
(wrapped_end, false) if wrapped_end != 0 => available_validators[offset..]
.iter()
.chain(available_validators[..wrapped_end].iter())
.cloned()
.collect(),
_ => available_validators[offset..end].to_vec(),
};
session_validators
}
}
#[cfg(test)]
mod tests {
// From construct_runtime macro
#![allow(clippy::from_over_into)]
use super::*;
use frame_support::{
parameter_types,
sp_io::TestExternalities,
sp_runtime::{
testing::{Header, UintAuthorityId},
traits::{BlakeTwo256, ConvertInto, IdentityLookup},
Perbill, RuntimeAppPublic,
},
traits::GenesisBuild,
weights::Weight,
BasicExternalities,
};
use sp_core::H256;
type AccountId = u64;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
frame_support::construct_runtime! {
pub enum TestRuntime where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Session: pallet_session::{Pallet},
}
}
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024);
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Config for TestRuntime {
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type RuntimeCall = RuntimeCall;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = ();
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type BaseCallFilter = frame_support::traits::Everything;
type SystemWeightInfo = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
parameter_types! {
pub const Period: u64 = 1;
pub const Offset: u64 = 0;
}
impl pallet_session::Config for TestRuntime {
type RuntimeEvent = ();
type ValidatorId = <Self as frame_system::Config>::AccountId;
type ValidatorIdOf = ConvertInto;
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
type SessionManager = ();
type SessionHandler = TestSessionHandler;
type Keys = UintAuthorityId;
type WeightInfo = ();
}
impl Config for TestRuntime {}
pub struct TestSessionHandler;
impl pallet_session::SessionHandler<AccountId> for TestSessionHandler {
const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[UintAuthorityId::ID];
fn on_genesis_session<Ks: sp_runtime::traits::OpaqueKeys>(_validators: &[(AccountId, Ks)]) {
}
fn on_new_session<Ks: sp_runtime::traits::OpaqueKeys>(
_: bool,
_: &[(AccountId, Ks)],
_: &[(AccountId, Ks)],
) {
}
fn on_disabled(_: u32) {}
}
fn new_test_ext() -> TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<TestRuntime>().unwrap();
let keys = vec![
(1, 1, UintAuthorityId(1)),
(2, 2, UintAuthorityId(2)),
(3, 3, UintAuthorityId(3)),
(4, 4, UintAuthorityId(4)),
(5, 5, UintAuthorityId(5)),
];
BasicExternalities::execute_with_storage(&mut t, || {
for (ref k, ..) in &keys {
frame_system::Pallet::<TestRuntime>::inc_providers(k);
}
});
pallet_session::GenesisConfig::<TestRuntime> { keys }
.assimilate_storage(&mut t)
.unwrap();
TestExternalities::new(t)
}
#[test]
fn shift_session_manager_works() {
new_test_ext().execute_with(|| {
let all_accs = vec![1, 2, 3, 4, 5];
// at least 1 validator is selected
assert_eq!(Pallet::<TestRuntime>::select_validators(0, &[1]), vec![1],);
// at session#0, shift is also 0
assert_eq!(Pallet::<TestRuntime>::select_validators(0, &all_accs), vec![1, 2, 3],);
// at session#1, shift is also 1
assert_eq!(Pallet::<TestRuntime>::select_validators(1, &all_accs), vec![2, 3, 4],);
// at session#3, we're wrapping
assert_eq!(Pallet::<TestRuntime>::select_validators(3, &all_accs), vec![4, 5, 1],);
// at session#5, we're starting from the beginning again
assert_eq!(Pallet::<TestRuntime>::select_validators(5, &all_accs), vec![1, 2, 3],);
});
}
}