mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 05:11:02 +00:00
Add BEEFY finality pallet with basic functionality (#1606)
* pallet-bridge-beefy: initial commit
This commit is contained in:
committed by
Bastian Köcher
parent
b3ab4a1b6a
commit
1f9110a065
@@ -19,6 +19,7 @@ bp-relayers = { path = "../../../primitives/relayers", default-features = false
|
|||||||
bp-rialto = { path = "../../../primitives/chain-rialto", default-features = false }
|
bp-rialto = { path = "../../../primitives/chain-rialto", default-features = false }
|
||||||
bp-runtime = { path = "../../../primitives/runtime", default-features = false }
|
bp-runtime = { path = "../../../primitives/runtime", default-features = false }
|
||||||
bridge-runtime-common = { path = "../../runtime-common", default-features = false }
|
bridge-runtime-common = { path = "../../runtime-common", default-features = false }
|
||||||
|
pallet-bridge-beefy = { path = "../../../modules/beefy", default-features = false }
|
||||||
pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
|
pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
|
||||||
pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
|
pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
|
||||||
pallet-bridge-relayers = { path = "../../../modules/relayers", default-features = false }
|
pallet-bridge-relayers = { path = "../../../modules/relayers", default-features = false }
|
||||||
@@ -98,6 +99,7 @@ std = [
|
|||||||
"pallet-balances/std",
|
"pallet-balances/std",
|
||||||
"pallet-beefy/std",
|
"pallet-beefy/std",
|
||||||
"pallet-beefy-mmr/std",
|
"pallet-beefy-mmr/std",
|
||||||
|
"pallet-bridge-beefy/std",
|
||||||
"pallet-bridge-grandpa/std",
|
"pallet-bridge-grandpa/std",
|
||||||
"pallet-bridge-messages/std",
|
"pallet-bridge-messages/std",
|
||||||
"pallet-bridge-relayers/std",
|
"pallet-bridge-relayers/std",
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ pub use frame_support::{
|
|||||||
|
|
||||||
pub use frame_system::Call as SystemCall;
|
pub use frame_system::Call as SystemCall;
|
||||||
pub use pallet_balances::Call as BalancesCall;
|
pub use pallet_balances::Call as BalancesCall;
|
||||||
|
pub use pallet_bridge_beefy::Call as BridgeBeefyCall;
|
||||||
pub use pallet_bridge_grandpa::Call as BridgeGrandpaCall;
|
pub use pallet_bridge_grandpa::Call as BridgeGrandpaCall;
|
||||||
pub use pallet_bridge_messages::Call as MessagesCall;
|
pub use pallet_bridge_messages::Call as MessagesCall;
|
||||||
pub use pallet_sudo::Call as SudoCall;
|
pub use pallet_sudo::Call as SudoCall;
|
||||||
@@ -474,6 +475,13 @@ impl pallet_bridge_messages::Config<WithMillauMessagesInstance> for Runtime {
|
|||||||
type BridgedChainId = BridgedChainId;
|
type BridgedChainId = BridgedChainId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type MillauBeefyInstance = ();
|
||||||
|
impl pallet_bridge_beefy::Config<MillauBeefyInstance> for Runtime {
|
||||||
|
type MaxRequests = frame_support::traits::ConstU32<16>;
|
||||||
|
type CommitmentsToKeep = frame_support::traits::ConstU32<8>;
|
||||||
|
type BridgedChain = bp_millau::Millau;
|
||||||
|
}
|
||||||
|
|
||||||
construct_runtime!(
|
construct_runtime!(
|
||||||
pub enum Runtime where
|
pub enum Runtime where
|
||||||
Block = Block,
|
Block = Block,
|
||||||
@@ -506,6 +514,9 @@ construct_runtime!(
|
|||||||
BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
|
BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
|
||||||
BridgeMillauMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
|
BridgeMillauMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
|
||||||
|
|
||||||
|
// Millau bridge modules (BEEFY based).
|
||||||
|
BridgeMillauBeefy: pallet_bridge_beefy::{Pallet, Call, Storage},
|
||||||
|
|
||||||
// Parachain modules.
|
// Parachain modules.
|
||||||
ParachainsOrigin: polkadot_runtime_parachains::origin::{Pallet, Origin},
|
ParachainsOrigin: polkadot_runtime_parachains::origin::{Pallet, Origin},
|
||||||
Configuration: polkadot_runtime_parachains::configuration::{Pallet, Call, Storage, Config<T>},
|
Configuration: polkadot_runtime_parachains::configuration::{Pallet, Call, Storage, Config<T>},
|
||||||
|
|||||||
@@ -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",
|
||||||
|
]
|
||||||
@@ -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,
|
||||||
|
¤t_authority_set_info,
|
||||||
|
&validator_set,
|
||||||
|
)?;
|
||||||
|
utils::verify_beefy_mmr_leaf::<T, I>(&mmr_leaf, mmr_proof, mmr_root)?;
|
||||||
|
|
||||||
|
// Update request count.
|
||||||
|
RequestCount::<T, I>::mutate(|count| *count += 1);
|
||||||
|
// Update authority set if needed.
|
||||||
|
if mmr_leaf.beefy_next_authority_set.id > current_authority_set_info.id {
|
||||||
|
CurrentAuthoritySetInfo::<T, I>::put(mmr_leaf.beefy_next_authority_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import commitment.
|
||||||
|
let block_number_index = commitments_info.next_block_number_index;
|
||||||
|
let to_prune = ImportedBlockNumbers::<T, I>::try_get(block_number_index);
|
||||||
|
ImportedCommitments::<T, I>::insert(
|
||||||
|
commitment.commitment.block_number,
|
||||||
|
ImportedCommitment::<T, I> {
|
||||||
|
parent_number_and_hash: mmr_leaf.parent_number_and_hash,
|
||||||
|
mmr_root,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ImportedBlockNumbers::<T, I>::insert(
|
||||||
|
block_number_index,
|
||||||
|
commitment.commitment.block_number,
|
||||||
|
);
|
||||||
|
ImportedCommitmentsInfo::<T, I>::put(ImportedCommitmentsInfoData {
|
||||||
|
best_block_number: commitment.commitment.block_number,
|
||||||
|
next_block_number_index: (block_number_index + 1) % T::CommitmentsToKeep::get(),
|
||||||
|
});
|
||||||
|
if let Ok(old_block_number) = to_prune {
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -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 }
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
¤t_validator_set_keys,
|
||||||
|
validator_count * 2 / 3 + 1,
|
||||||
|
));
|
||||||
|
|
||||||
|
chain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default Merging & Hashing behavior for MMR.
|
||||||
|
pub struct BridgedMmrHashMerge;
|
||||||
|
|
||||||
|
impl mmr_lib::Merge for BridgedMmrHashMerge {
|
||||||
|
type Item = TestBridgedMmrNode;
|
||||||
|
|
||||||
|
fn merge(left: &Self::Item, right: &Self::Item) -> Self::Item {
|
||||||
|
let mut concat = left.hash().as_ref().to_vec();
|
||||||
|
concat.extend_from_slice(right.hash().as_ref());
|
||||||
|
|
||||||
|
TestBridgedMmrNode::Hash(TestBridgedMmrHashing::hash(&concat))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
[package]
|
||||||
|
name = "bp-beefy"
|
||||||
|
description = "Primitives of pallet-bridge-beefy module."
|
||||||
|
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, features = ["derive", "bit-vec"] }
|
||||||
|
scale-info = { version = "2.0.1", default-features = false, features = ["bit-vec", "derive"] }
|
||||||
|
serde = { version = "1.0", optional = true }
|
||||||
|
static_assertions = "1.1"
|
||||||
|
|
||||||
|
# Bridge Dependencies
|
||||||
|
|
||||||
|
bp-runtime = { path = "../runtime", default-features = false }
|
||||||
|
|
||||||
|
# Substrate Dependencies
|
||||||
|
|
||||||
|
beefy-merkle-tree = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
pallet-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = [
|
||||||
|
"beefy-primitives/std",
|
||||||
|
"bp-runtime/std",
|
||||||
|
"codec/std",
|
||||||
|
"frame-support/std",
|
||||||
|
"pallet-beefy-mmr/std",
|
||||||
|
"pallet-mmr/std",
|
||||||
|
"scale-info/std",
|
||||||
|
"serde",
|
||||||
|
"sp-application-crypto/std",
|
||||||
|
"sp-core/std",
|
||||||
|
"sp-io/std",
|
||||||
|
"sp-runtime/std",
|
||||||
|
"sp-std/std"
|
||||||
|
]
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//! Primitives that are used to interact with BEEFY bridge pallet.
|
||||||
|
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
pub use beefy_merkle_tree::{merkle_root, Keccak256 as BeefyKeccak256};
|
||||||
|
pub use beefy_primitives::{
|
||||||
|
crypto::{AuthorityId as EcdsaValidatorId, AuthoritySignature as EcdsaValidatorSignature},
|
||||||
|
known_payloads::MMR_ROOT_ID as MMR_ROOT_PAYLOAD_ID,
|
||||||
|
mmr::{BeefyAuthoritySet, MmrLeafVersion},
|
||||||
|
BeefyAuthorityId, BeefyVerify, Commitment, Payload as BeefyPayload, SignedCommitment,
|
||||||
|
ValidatorSet, ValidatorSetId, BEEFY_ENGINE_ID,
|
||||||
|
};
|
||||||
|
pub use pallet_beefy_mmr::BeefyEcdsaToEthereum;
|
||||||
|
pub use pallet_mmr::{
|
||||||
|
primitives::{DataOrHash as MmrDataOrHash, Proof as MmrProof},
|
||||||
|
verify_leaves_proof as verify_mmr_leaves_proof,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bp_runtime::{BasicOperatingMode, BlockNumberOf, Chain, HashOf};
|
||||||
|
use codec::{Decode, Encode};
|
||||||
|
use frame_support::Parameter;
|
||||||
|
use scale_info::TypeInfo;
|
||||||
|
use sp_runtime::{
|
||||||
|
traits::{Convert, MaybeSerializeDeserialize},
|
||||||
|
RuntimeDebug,
|
||||||
|
};
|
||||||
|
use sp_std::prelude::*;
|
||||||
|
|
||||||
|
/// Substrate-based chain with BEEFY && MMR pallets deployed.
|
||||||
|
///
|
||||||
|
/// Both BEEFY and MMR pallets and their clients may be configured to use different
|
||||||
|
/// primitives. Some of types can be configured in low-level pallets, but are constrained
|
||||||
|
/// when BEEFY+MMR bundle is used.
|
||||||
|
pub trait ChainWithBeefy: Chain {
|
||||||
|
/// The hashing algorithm used to compute the digest of the BEEFY commitment.
|
||||||
|
///
|
||||||
|
/// Corresponds to the hashing algorithm, used by `beefy_gadget::BeefyKeystore`.
|
||||||
|
type CommitmentHasher: sp_runtime::traits::Hash;
|
||||||
|
|
||||||
|
/// The hashing algorithm used to build the MMR.
|
||||||
|
///
|
||||||
|
/// The same algorithm is also used to compute merkle roots in BEEFY
|
||||||
|
/// (e.g. validator addresses root in leaf data).
|
||||||
|
///
|
||||||
|
/// Corresponds to the `Hashing` field of the `pallet-mmr` configuration.
|
||||||
|
type MmrHashing: sp_runtime::traits::Hash<Output = Self::MmrHash>;
|
||||||
|
|
||||||
|
/// The output type of the hashing algorithm used to build the MMR.
|
||||||
|
///
|
||||||
|
/// This type is actually stored in the MMR.
|
||||||
|
|
||||||
|
/// Corresponds to the `Hash` field of the `pallet-mmr` configuration.
|
||||||
|
type MmrHash: sp_std::hash::Hash
|
||||||
|
+ Parameter
|
||||||
|
+ Copy
|
||||||
|
+ AsRef<[u8]>
|
||||||
|
+ Default
|
||||||
|
+ MaybeSerializeDeserialize;
|
||||||
|
|
||||||
|
/// The type expected for the MMR leaf extra data.
|
||||||
|
type BeefyMmrLeafExtra: Parameter;
|
||||||
|
|
||||||
|
/// A way to identify a BEEFY validator.
|
||||||
|
///
|
||||||
|
/// Corresponds to the `BeefyId` field of the `pallet-beefy` configuration.
|
||||||
|
type AuthorityId: BeefyAuthorityId + Parameter;
|
||||||
|
|
||||||
|
/// The signature type used by BEEFY.
|
||||||
|
///
|
||||||
|
/// Corresponds to the `BeefyId` field of the `pallet-beefy` configuration.
|
||||||
|
type Signature: BeefyVerify<Self::CommitmentHasher, Signer = Self::AuthorityId> + Parameter;
|
||||||
|
|
||||||
|
/// A way to convert validator id to its raw representation in the BEEFY merkle tree.
|
||||||
|
///
|
||||||
|
/// Corresponds to the `BeefyAuthorityToMerkleLeaf` field of the `pallet-beefy-mmr`
|
||||||
|
/// configuration.
|
||||||
|
type AuthorityIdToMerkleLeaf: Convert<Self::AuthorityId, Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BEEFY validator id used by given Substrate chain.
|
||||||
|
pub type BeefyAuthorityIdOf<C> = <C as ChainWithBeefy>::AuthorityId;
|
||||||
|
/// BEEFY validator set, containing both validator identifiers and the numeric set id.
|
||||||
|
pub type BeefyAuthoritySetOf<C> = ValidatorSet<BeefyAuthorityIdOf<C>>;
|
||||||
|
/// BEEFY authority set, containing both validator identifiers and the numeric set id.
|
||||||
|
pub type BeefyAuthoritySetInfoOf<C> = beefy_primitives::mmr::BeefyAuthoritySet<MmrHashOf<C>>;
|
||||||
|
/// BEEFY validator signature used by given Substrate chain.
|
||||||
|
pub type BeefyValidatorSignatureOf<C> = <C as ChainWithBeefy>::Signature;
|
||||||
|
/// Signed BEEFY commitment used by given Substrate chain.
|
||||||
|
pub type BeefySignedCommitmentOf<C> =
|
||||||
|
SignedCommitment<BlockNumberOf<C>, BeefyValidatorSignatureOf<C>>;
|
||||||
|
/// Hash algorithm, used to compute the digest of the BEEFY commitment before signing it.
|
||||||
|
pub type BeefyCommitmentHasher<C> = <C as ChainWithBeefy>::CommitmentHasher;
|
||||||
|
/// Hash algorithm used in Beefy MMR construction by given Substrate chain.
|
||||||
|
pub type MmrHashingOf<C> = <C as ChainWithBeefy>::MmrHashing;
|
||||||
|
/// Hash type, used in MMR construction by given Substrate chain.
|
||||||
|
pub type MmrHashOf<C> = <C as ChainWithBeefy>::MmrHash;
|
||||||
|
/// BEEFY MMR proof type used by the given Substrate chain.
|
||||||
|
pub type MmrProofOf<C> = MmrProof<MmrHashOf<C>>;
|
||||||
|
/// The type of the MMR leaf extra data used by the given Substrate chain.
|
||||||
|
pub type BeefyMmrLeafExtraOf<C> = <C as ChainWithBeefy>::BeefyMmrLeafExtra;
|
||||||
|
/// A way to convert a validator id to its raw representation in the BEEFY merkle tree, used by
|
||||||
|
/// the given Substrate chain.
|
||||||
|
pub type BeefyAuthorityIdToMerkleLeafOf<C> = <C as ChainWithBeefy>::AuthorityIdToMerkleLeaf;
|
||||||
|
/// Actual type of leafs in the BEEFY MMR.
|
||||||
|
pub type BeefyMmrLeafOf<C> = beefy_primitives::mmr::MmrLeaf<
|
||||||
|
BlockNumberOf<C>,
|
||||||
|
HashOf<C>,
|
||||||
|
MmrHashOf<C>,
|
||||||
|
BeefyMmrLeafExtraOf<C>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Data required for initializing the BEEFY pallet.
|
||||||
|
///
|
||||||
|
/// Provides the initial context that the bridge needs in order to know
|
||||||
|
/// where to start the sync process from.
|
||||||
|
#[derive(Encode, Decode, RuntimeDebug, PartialEq, Clone, TypeInfo)]
|
||||||
|
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct InitializationData<BlockNumber, Hash> {
|
||||||
|
/// Pallet operating mode.
|
||||||
|
pub operating_mode: BasicOperatingMode,
|
||||||
|
/// Number of the best block, finalized by BEEFY.
|
||||||
|
pub best_block_number: BlockNumber,
|
||||||
|
/// BEEFY authority set that will be finalizing descendants of the `best_beefy_block_number`
|
||||||
|
/// block.
|
||||||
|
pub authority_set: BeefyAuthoritySet<Hash>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Basic data, stored by the pallet for every imported commitment.
|
||||||
|
#[derive(Encode, Decode, RuntimeDebug, PartialEq, TypeInfo)]
|
||||||
|
pub struct ImportedCommitment<BlockNumber, BlockHash, MmrHash> {
|
||||||
|
/// Block number and hash of the finalized block parent.
|
||||||
|
pub parent_number_and_hash: (BlockNumber, BlockHash),
|
||||||
|
/// MMR root at the imported block.
|
||||||
|
pub mmr_root: MmrHash,
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
|||||||
|
|
||||||
# Bridge Dependencies
|
# Bridge Dependencies
|
||||||
|
|
||||||
|
bp-beefy = { path = "../beefy", default-features = false }
|
||||||
bp-messages = { path = "../messages", default-features = false }
|
bp-messages = { path = "../messages", default-features = false }
|
||||||
bp-runtime = { path = "../runtime", default-features = false }
|
bp-runtime = { path = "../runtime", default-features = false }
|
||||||
fixed-hash = { version = "0.7.0", default-features = false }
|
fixed-hash = { version = "0.7.0", default-features = false }
|
||||||
@@ -34,6 +35,7 @@ sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master",
|
|||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
|
"bp-beefy/std",
|
||||||
"bp-messages/std",
|
"bp-messages/std",
|
||||||
"bp-runtime/std",
|
"bp-runtime/std",
|
||||||
"fixed-hash/std",
|
"fixed-hash/std",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
mod millau_hash;
|
mod millau_hash;
|
||||||
|
|
||||||
|
use bp_beefy::ChainWithBeefy;
|
||||||
use bp_messages::{
|
use bp_messages::{
|
||||||
InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails,
|
InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails,
|
||||||
};
|
};
|
||||||
@@ -41,6 +42,7 @@ use sp_trie::{LayoutV0, LayoutV1, TrieConfiguration};
|
|||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sp_runtime::traits::Keccak256;
|
||||||
|
|
||||||
pub use millau_hash::MillauHash;
|
pub use millau_hash::MillauHash;
|
||||||
|
|
||||||
@@ -155,6 +157,16 @@ impl Chain for Millau {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ChainWithBeefy for Millau {
|
||||||
|
type CommitmentHasher = Keccak256;
|
||||||
|
type MmrHashing = Keccak256;
|
||||||
|
type MmrHash = <Keccak256 as sp_runtime::traits::Hash>::Output;
|
||||||
|
type BeefyMmrLeafExtra = ();
|
||||||
|
type AuthorityId = bp_beefy::EcdsaValidatorId;
|
||||||
|
type Signature = bp_beefy::EcdsaValidatorSignature;
|
||||||
|
type AuthorityIdToMerkleLeaf = bp_beefy::BeefyEcdsaToEthereum;
|
||||||
|
}
|
||||||
|
|
||||||
/// Millau Hasher (Blake2-256 ++ Keccak-256) implementation.
|
/// Millau Hasher (Blake2-256 ++ Keccak-256) implementation.
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, RuntimeDebug, TypeInfo)]
|
#[derive(PartialEq, Eq, Clone, Copy, RuntimeDebug, TypeInfo)]
|
||||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||||
|
|||||||
Reference in New Issue
Block a user