mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 21:01:02 +00:00
Unify Substrate Bridge Pallet with Finality Verifier (#783)
* Add relevant storage items from Substrate pallet * Add function for importing finalized headers to storage * Remove unused getter functions * Add GenesisConfig to pallet * Add initialization extrinsic * Add operational extrinsic * Get existing finality verifier tests compiling again * Add tests for pallet initialization * Add tests related to pallet's operational status * Update tests which were using `pallet-substrate-bridge` * Add tests related to header imports * Use wrapper function when init-ing some tests * Add prefix to tests related to rate limiter * Fix failed compilation related to GenesisConfig * Add some documentation * Change some extrinsics to be Operational * Add public interface to pallet * Implement runtime APIs for finality-verifier pallet * Justify use of `expect` when importing headers * Reject headers with forced changes * Add weight to initialize extrinsic * Remove TODO which will be addressed later * Move succesful import log to correct location * Expand proof for when `best_finalized` is fetched * Move check for newer finalized blocks earlier in pipeline * Rename `ConflictingFork` error to be more generic * Only compute finality_target's hash once * Add missing documentation to Runtime APIs * Add TODO about using `set_id` from `ScheduledChange` digest
This commit is contained in:
committed by
Bastian Köcher
parent
95f5d2248e
commit
6cfd87783e
@@ -559,6 +559,17 @@ impl_runtime_apis! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl bp_rialto::RialtoFinalityApi<Block> for Runtime {
|
||||||
|
fn best_finalized() -> (bp_rialto::BlockNumber, bp_rialto::Hash) {
|
||||||
|
let header = BridgeFinalityVerifier::best_finalized();
|
||||||
|
(header.number, header.hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_known_header(hash: bp_rialto::Hash) -> bool {
|
||||||
|
BridgeFinalityVerifier::is_known_header(hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl bp_rialto::ToRialtoOutboundLaneApi<Block, Balance, ToRialtoMessagePayload> for Runtime {
|
impl bp_rialto::ToRialtoOutboundLaneApi<Block, Balance, ToRialtoMessagePayload> for Runtime {
|
||||||
fn estimate_message_delivery_and_dispatch_fee(
|
fn estimate_message_delivery_and_dispatch_fee(
|
||||||
_lane_id: bp_message_lane::LaneId,
|
_lane_id: bp_message_lane::LaneId,
|
||||||
|
|||||||
@@ -642,6 +642,17 @@ impl_runtime_apis! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl bp_millau::MillauFinalityApi<Block> for Runtime {
|
||||||
|
fn best_finalized() -> (bp_millau::BlockNumber, bp_millau::Hash) {
|
||||||
|
let header = BridgeFinalityVerifier::best_finalized();
|
||||||
|
(header.number, header.hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_known_header(hash: bp_millau::Hash) -> bool {
|
||||||
|
BridgeFinalityVerifier::is_known_header(hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl bp_currency_exchange::RialtoCurrencyExchangeApi<Block, exchange::EthereumTransactionInclusionProof> for Runtime {
|
impl bp_currency_exchange::RialtoCurrencyExchangeApi<Block, exchange::EthereumTransactionInclusionProof> for Runtime {
|
||||||
fn filter_transaction_proof(proof: exchange::EthereumTransactionInclusionProof) -> bool {
|
fn filter_transaction_proof(proof: exchange::EthereumTransactionInclusionProof) -> bool {
|
||||||
BridgeRialtoCurrencyExchange::filter_transaction_proof(&proof)
|
BridgeRialtoCurrencyExchange::filter_transaction_proof(&proof)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ bp-header-chain = { path = "../../primitives/header-chain", default-features = f
|
|||||||
|
|
||||||
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||||
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||||
|
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||||
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||||
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ std = [
|
|||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sp-finality-grandpa/std",
|
||||||
"sp-runtime/std",
|
"sp-runtime/std",
|
||||||
"sp-std/std",
|
"sp-std/std",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -33,11 +33,16 @@
|
|||||||
#![allow(clippy::large_enum_variant)]
|
#![allow(clippy::large_enum_variant)]
|
||||||
|
|
||||||
use bp_header_chain::{justification::verify_justification, AncestryChecker, HeaderChain};
|
use bp_header_chain::{justification::verify_justification, AncestryChecker, HeaderChain};
|
||||||
use bp_runtime::{Chain, HeaderOf};
|
use bp_runtime::{BlockNumberOf, Chain, HashOf, HasherOf, HeaderOf};
|
||||||
|
use codec::{Decode, Encode};
|
||||||
use finality_grandpa::voter_set::VoterSet;
|
use finality_grandpa::voter_set::VoterSet;
|
||||||
use frame_support::{dispatch::DispatchError, ensure};
|
use frame_support::{dispatch::DispatchError, ensure};
|
||||||
use frame_system::ensure_signed;
|
use frame_system::{ensure_signed, RawOrigin};
|
||||||
use sp_runtime::traits::Header as HeaderT;
|
#[cfg(feature = "std")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
|
||||||
|
use sp_runtime::traits::{BadOrigin, Header as HeaderT, Zero};
|
||||||
|
use sp_runtime::RuntimeDebug;
|
||||||
use sp_std::vec::Vec;
|
use sp_std::vec::Vec;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -46,15 +51,21 @@ mod mock;
|
|||||||
// Re-export in crate namespace for `construct_runtime!`
|
// Re-export in crate namespace for `construct_runtime!`
|
||||||
pub use pallet::*;
|
pub use pallet::*;
|
||||||
|
|
||||||
|
/// Block number of the bridged chain.
|
||||||
|
pub type BridgedBlockNumber<T> = BlockNumberOf<<T as Config>::BridgedChain>;
|
||||||
|
/// Block hash of the bridged chain.
|
||||||
|
pub type BridgedBlockHash<T> = HashOf<<T as Config>::BridgedChain>;
|
||||||
|
/// Hasher of the bridged chain.
|
||||||
|
pub type _BridgedBlockHasher<T> = HasherOf<<T as Config>::BridgedChain>;
|
||||||
|
/// Header of the bridged chain.
|
||||||
|
pub type BridgedHeader<T> = HeaderOf<<T as Config>::BridgedChain>;
|
||||||
|
|
||||||
#[frame_support::pallet]
|
#[frame_support::pallet]
|
||||||
pub mod pallet {
|
pub mod pallet {
|
||||||
use super::*;
|
use super::*;
|
||||||
use frame_support::pallet_prelude::*;
|
use frame_support::pallet_prelude::*;
|
||||||
use frame_system::pallet_prelude::*;
|
use frame_system::pallet_prelude::*;
|
||||||
|
|
||||||
/// Header of the bridged chain.
|
|
||||||
pub(crate) type BridgedHeader<T> = HeaderOf<<T as Config>::BridgedChain>;
|
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
pub trait Config: frame_system::Config {
|
pub trait Config: frame_system::Config {
|
||||||
/// The chain we are bridging to here.
|
/// The chain we are bridging to here.
|
||||||
@@ -113,6 +124,7 @@ pub mod pallet {
|
|||||||
justification: Vec<u8>,
|
justification: Vec<u8>,
|
||||||
ancestry_proof: T::AncestryProof,
|
ancestry_proof: T::AncestryProof,
|
||||||
) -> DispatchResultWithPostInfo {
|
) -> DispatchResultWithPostInfo {
|
||||||
|
ensure_operational::<T>()?;
|
||||||
let _ = ensure_signed(origin)?;
|
let _ = ensure_signed(origin)?;
|
||||||
|
|
||||||
ensure!(
|
ensure!(
|
||||||
@@ -120,14 +132,24 @@ pub mod pallet {
|
|||||||
<Error<T>>::TooManyRequests
|
<Error<T>>::TooManyRequests
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let (hash, number) = (finality_target.hash(), finality_target.number());
|
||||||
frame_support::debug::trace!("Going to try and finalize header {:?}", finality_target);
|
frame_support::debug::trace!("Going to try and finalize header {:?}", finality_target);
|
||||||
|
|
||||||
let authority_set = T::HeaderChain::authority_set();
|
let best_finalized = <ImportedHeaders<T>>::get(<BestFinalized<T>>::get()).expect(
|
||||||
|
"In order to reach this point the bridge must have been initialized. Afterwards,
|
||||||
|
every time `BestFinalized` is updated `ImportedHeaders` is also updated. Therefore
|
||||||
|
`ImportedHeaders` must contain an entry for `BestFinalized`.",
|
||||||
|
);
|
||||||
|
|
||||||
|
// We do a quick check here to ensure that our header chain is making progress and isn't
|
||||||
|
// "travelling back in time" (which could be indicative of something bad, e.g a hard-fork).
|
||||||
|
ensure!(best_finalized.number() < number, <Error<T>>::OldHeader);
|
||||||
|
|
||||||
|
let authority_set = <CurrentAuthoritySet<T>>::get();
|
||||||
let voter_set = VoterSet::new(authority_set.authorities).ok_or(<Error<T>>::InvalidAuthoritySet)?;
|
let voter_set = VoterSet::new(authority_set.authorities).ok_or(<Error<T>>::InvalidAuthoritySet)?;
|
||||||
let set_id = authority_set.set_id;
|
let set_id = authority_set.set_id;
|
||||||
|
|
||||||
let (hash, number) = (finality_target.hash(), *finality_target.number());
|
verify_justification::<BridgedHeader<T>>((hash, *number), set_id, voter_set, &justification).map_err(
|
||||||
verify_justification::<BridgedHeader<T>>((hash, number), set_id, voter_set, &justification).map_err(
|
|
||||||
|e| {
|
|e| {
|
||||||
frame_support::debug::error!("Received invalid justification for {:?}: {:?}", finality_target, e);
|
frame_support::debug::error!("Received invalid justification for {:?}: {:?}", finality_target, e);
|
||||||
<Error<T>>::InvalidJustification
|
<Error<T>>::InvalidJustification
|
||||||
@@ -142,10 +164,84 @@ pub mod pallet {
|
|||||||
<Error<T>>::InvalidAncestryProof
|
<Error<T>>::InvalidAncestryProof
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = T::HeaderChain::append_header(finality_target)?;
|
let _ = T::HeaderChain::append_header(finality_target.clone())?;
|
||||||
|
|
||||||
|
import_header::<T>(hash, finality_target)?;
|
||||||
|
<RequestCount<T>>::mutate(|count| *count += 1);
|
||||||
|
|
||||||
frame_support::debug::info!("Succesfully imported finalized header with hash {:?}!", hash);
|
frame_support::debug::info!("Succesfully imported finalized header with hash {:?}!", hash);
|
||||||
|
|
||||||
<RequestCount<T>>::mutate(|count| *count += 1);
|
Ok(().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bootstrap the bridge pallet with an initial header and authority set from which to sync.
|
||||||
|
///
|
||||||
|
/// The initial configuration provided does not need to be the genesis header of the bridged
|
||||||
|
/// chain, it can be any arbirary header. You can also provide the next scheduled set change
|
||||||
|
/// if it is already know.
|
||||||
|
///
|
||||||
|
/// This function is only allowed to be called from a trusted origin and writes to storage
|
||||||
|
/// with practically no checks in terms of the validity of the data. It is important that
|
||||||
|
/// you ensure that valid data is being passed in.
|
||||||
|
#[pallet::weight((T::DbWeight::get().reads_writes(2, 5), DispatchClass::Operational))]
|
||||||
|
pub fn initialize(
|
||||||
|
origin: OriginFor<T>,
|
||||||
|
init_data: super::InitializationData<BridgedHeader<T>>,
|
||||||
|
) -> DispatchResultWithPostInfo {
|
||||||
|
ensure_owner_or_root::<T>(origin)?;
|
||||||
|
|
||||||
|
let init_allowed = !<BestFinalized<T>>::exists();
|
||||||
|
ensure!(init_allowed, <Error<T>>::AlreadyInitialized);
|
||||||
|
initialize_bridge::<T>(init_data.clone());
|
||||||
|
|
||||||
|
frame_support::debug::info!(
|
||||||
|
"Pallet has been initialized with the following parameters: {:?}",
|
||||||
|
init_data
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change `ModuleOwner`.
|
||||||
|
///
|
||||||
|
/// May only be called either by root, or by `ModuleOwner`.
|
||||||
|
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||||
|
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResultWithPostInfo {
|
||||||
|
ensure_owner_or_root::<T>(origin)?;
|
||||||
|
match new_owner {
|
||||||
|
Some(new_owner) => {
|
||||||
|
ModuleOwner::<T>::put(&new_owner);
|
||||||
|
frame_support::debug::info!("Setting pallet Owner to: {:?}", new_owner);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
ModuleOwner::<T>::kill();
|
||||||
|
frame_support::debug::info!("Removed Owner of pallet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Halt all pallet operations. Operations may be resumed using `resume_operations` call.
|
||||||
|
///
|
||||||
|
/// May only be called either by root, or by `ModuleOwner`.
|
||||||
|
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||||
|
pub fn halt_operations(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
|
||||||
|
ensure_owner_or_root::<T>(origin)?;
|
||||||
|
<IsHalted<T>>::put(true);
|
||||||
|
frame_support::debug::warn!("Stopping pallet operations.");
|
||||||
|
|
||||||
|
Ok(().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume all pallet operations. May be called even if pallet is halted.
|
||||||
|
///
|
||||||
|
/// May only be called either by root, or by `ModuleOwner`.
|
||||||
|
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||||
|
pub fn resume_operations(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
|
||||||
|
ensure_owner_or_root::<T>(origin)?;
|
||||||
|
<IsHalted<T>>::put(false);
|
||||||
|
frame_support::debug::info!("Resuming pallet operations.");
|
||||||
|
|
||||||
Ok(().into())
|
Ok(().into())
|
||||||
}
|
}
|
||||||
@@ -162,6 +258,68 @@ pub mod pallet {
|
|||||||
#[pallet::getter(fn request_count)]
|
#[pallet::getter(fn request_count)]
|
||||||
pub(super) type RequestCount<T: Config> = StorageValue<_, u32, ValueQuery>;
|
pub(super) type RequestCount<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||||
|
|
||||||
|
/// Hash of the header used to bootstrap the pallet.
|
||||||
|
#[pallet::storage]
|
||||||
|
pub(super) type InitialHash<T: Config> = StorageValue<_, BridgedBlockHash<T>, ValueQuery>;
|
||||||
|
|
||||||
|
/// Hash of the best finalized header.
|
||||||
|
#[pallet::storage]
|
||||||
|
pub(super) type BestFinalized<T: Config> = StorageValue<_, BridgedBlockHash<T>, ValueQuery>;
|
||||||
|
|
||||||
|
/// Headers which have been imported into the pallet.
|
||||||
|
#[pallet::storage]
|
||||||
|
pub(super) type ImportedHeaders<T: Config> = StorageMap<_, Identity, BridgedBlockHash<T>, BridgedHeader<T>>;
|
||||||
|
|
||||||
|
/// The current GRANDPA Authority set.
|
||||||
|
#[pallet::storage]
|
||||||
|
pub(super) type CurrentAuthoritySet<T: Config> = StorageValue<_, bp_header_chain::AuthoritySet, ValueQuery>;
|
||||||
|
|
||||||
|
/// Optional pallet owner.
|
||||||
|
///
|
||||||
|
/// Pallet owner has a 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 call the `halt_operations`).
|
||||||
|
#[pallet::storage]
|
||||||
|
pub(super) type ModuleOwner<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
|
||||||
|
|
||||||
|
/// If true, all pallet transactions are failed immediately.
|
||||||
|
#[pallet::storage]
|
||||||
|
pub(super) type IsHalted<T: Config> = StorageValue<_, bool, ValueQuery>;
|
||||||
|
|
||||||
|
#[pallet::genesis_config]
|
||||||
|
pub struct GenesisConfig<T: Config> {
|
||||||
|
owner: Option<T::AccountId>,
|
||||||
|
init_data: Option<super::InitializationData<BridgedHeader<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl<T: Config> Default for GenesisConfig<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
owner: None,
|
||||||
|
init_data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::genesis_build]
|
||||||
|
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
|
||||||
|
fn build(&self) {
|
||||||
|
if let Some(ref owner) = self.owner {
|
||||||
|
<ModuleOwner<T>>::put(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(init_data) = self.init_data.clone() {
|
||||||
|
initialize_bridge::<T>(init_data);
|
||||||
|
} else {
|
||||||
|
// Since the bridge hasn't been initialized we shouldn't allow anyone to perform
|
||||||
|
// transactions.
|
||||||
|
<IsHalted<T>>::put(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pallet::error]
|
#[pallet::error]
|
||||||
pub enum Error<T> {
|
pub enum Error<T> {
|
||||||
/// The given justification is invalid for the given header.
|
/// The given justification is invalid for the given header.
|
||||||
@@ -175,32 +333,199 @@ pub mod pallet {
|
|||||||
FailedToWriteHeader,
|
FailedToWriteHeader,
|
||||||
/// There are too many requests for the current window to handle.
|
/// There are too many requests for the current window to handle.
|
||||||
TooManyRequests,
|
TooManyRequests,
|
||||||
|
/// The header being imported is older than the best finalized header known to the pallet.
|
||||||
|
OldHeader,
|
||||||
|
/// The scheduled authority set change found in the header is unsupported by the pallet.
|
||||||
|
///
|
||||||
|
/// This is the case for non-standard (e.g forced) authority set changes.
|
||||||
|
UnsupportedScheduledChange,
|
||||||
|
/// The pallet has already been initialized.
|
||||||
|
AlreadyInitialized,
|
||||||
|
/// All pallet operations are halted.
|
||||||
|
Halted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Import the given header to the pallet's storage.
|
||||||
|
///
|
||||||
|
/// This function will also check if the header schedules and enacts authority set changes,
|
||||||
|
/// updating the current authority set accordingly.
|
||||||
|
///
|
||||||
|
/// Note: This function assumes that the given header has already been proven to be valid and
|
||||||
|
/// finalized. Using this assumption it will write them to storage with minimal checks. That
|
||||||
|
/// means it's of great importance that this function *not* called with any headers whose
|
||||||
|
/// finality has not been checked, otherwise you risk bricking your bridge.
|
||||||
|
pub(crate) fn import_header<T: Config>(
|
||||||
|
hash: BridgedBlockHash<T>,
|
||||||
|
header: BridgedHeader<T>,
|
||||||
|
) -> Result<(), sp_runtime::DispatchError> {
|
||||||
|
// We don't support forced changes - at that point governance intervention is required.
|
||||||
|
ensure!(
|
||||||
|
super::find_forced_change(&header).is_none(),
|
||||||
|
<Error<T>>::UnsupportedScheduledChange
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(change) = super::find_scheduled_change(&header) {
|
||||||
|
// GRANDPA only includes a `delay` for forced changes, so this isn't valid.
|
||||||
|
ensure!(change.delay == Zero::zero(), <Error<T>>::UnsupportedScheduledChange);
|
||||||
|
|
||||||
|
// TODO [#788]: Stop manually increasing the `set_id` here.
|
||||||
|
let next_authorities = bp_header_chain::AuthoritySet {
|
||||||
|
authorities: change.next_authorities,
|
||||||
|
set_id: <CurrentAuthoritySet<T>>::get().set_id + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Since our header schedules a change and we know the delay is 0, it must also enact
|
||||||
|
// the change.
|
||||||
|
<CurrentAuthoritySet<T>>::put(next_authorities);
|
||||||
|
};
|
||||||
|
|
||||||
|
<BestFinalized<T>>::put(hash);
|
||||||
|
<ImportedHeaders<T>>::insert(hash, header);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Since this writes to storage with no real checks this should only be used in functions that
|
||||||
|
/// were called by a trusted origin.
|
||||||
|
fn initialize_bridge<T: Config>(init_params: super::InitializationData<BridgedHeader<T>>) {
|
||||||
|
let super::InitializationData {
|
||||||
|
header,
|
||||||
|
authority_list,
|
||||||
|
set_id,
|
||||||
|
is_halted,
|
||||||
|
} = init_params;
|
||||||
|
|
||||||
|
let initial_hash = header.hash();
|
||||||
|
<InitialHash<T>>::put(initial_hash);
|
||||||
|
<BestFinalized<T>>::put(initial_hash);
|
||||||
|
<ImportedHeaders<T>>::insert(initial_hash, header);
|
||||||
|
|
||||||
|
let authority_set = bp_header_chain::AuthoritySet::new(authority_list, set_id);
|
||||||
|
<CurrentAuthoritySet<T>>::put(authority_set);
|
||||||
|
|
||||||
|
<IsHalted<T>>::put(is_halted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that the origin is either root, or `ModuleOwner`.
|
||||||
|
fn ensure_owner_or_root<T: Config>(origin: T::Origin) -> Result<(), BadOrigin> {
|
||||||
|
match origin.into() {
|
||||||
|
Ok(RawOrigin::Root) => Ok(()),
|
||||||
|
Ok(RawOrigin::Signed(ref signer)) if Some(signer) == <ModuleOwner<T>>::get().as_ref() => Ok(()),
|
||||||
|
_ => Err(BadOrigin),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that the pallet is in operational mode (not halted).
|
||||||
|
fn ensure_operational<T: Config>() -> Result<(), Error<T>> {
|
||||||
|
if <IsHalted<T>>::get() {
|
||||||
|
Err(<Error<T>>::Halted)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config> Pallet<T> {
|
||||||
|
/// Get the best finalized header the pallet knows of.
|
||||||
|
///
|
||||||
|
/// Returns a dummy header if there is no best header. This can only happen
|
||||||
|
/// if the pallet has not been initialized yet.
|
||||||
|
pub fn best_finalized() -> BridgedHeader<T> {
|
||||||
|
let hash = <BestFinalized<T>>::get();
|
||||||
|
<ImportedHeaders<T>>::get(hash).unwrap_or_else(|| {
|
||||||
|
<BridgedHeader<T>>::new(
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a particular header is known to the bridge pallet.
|
||||||
|
pub fn is_known_header(hash: BridgedBlockHash<T>) -> bool {
|
||||||
|
<ImportedHeaders<T>>::contains_key(hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data required for initializing the bridge pallet.
|
||||||
|
///
|
||||||
|
/// The bridge needs to know where to start its sync from, and this provides that initial context.
|
||||||
|
#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||||
|
pub struct InitializationData<H: HeaderT> {
|
||||||
|
/// The header from which we should start syncing.
|
||||||
|
pub header: H,
|
||||||
|
/// The initial authorities of the pallet.
|
||||||
|
pub authority_list: sp_finality_grandpa::AuthorityList,
|
||||||
|
/// The ID of the initial authority set.
|
||||||
|
pub set_id: sp_finality_grandpa::SetId,
|
||||||
|
/// Should the pallet block transaction immediately after initialization.
|
||||||
|
pub is_halted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn find_scheduled_change<H: HeaderT>(header: &H) -> Option<sp_finality_grandpa::ScheduledChange<H::Number>> {
|
||||||
|
use sp_runtime::generic::OpaqueDigestItemId;
|
||||||
|
|
||||||
|
let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
|
||||||
|
|
||||||
|
let filter_log = |log: ConsensusLog<H::Number>| match log {
|
||||||
|
ConsensusLog::ScheduledChange(change) => Some(change),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// find the first consensus digest with the right ID which converts to
|
||||||
|
// the right kind of consensus log.
|
||||||
|
header.digest().convert_first(|l| l.try_to(id).and_then(filter_log))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the given header for a consensus digest signalling a **forced** scheduled change and
|
||||||
|
/// extracts it.
|
||||||
|
pub(crate) fn find_forced_change<H: HeaderT>(
|
||||||
|
header: &H,
|
||||||
|
) -> Option<(H::Number, sp_finality_grandpa::ScheduledChange<H::Number>)> {
|
||||||
|
use sp_runtime::generic::OpaqueDigestItemId;
|
||||||
|
|
||||||
|
let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
|
||||||
|
|
||||||
|
let filter_log = |log: ConsensusLog<H::Number>| match log {
|
||||||
|
ConsensusLog::ForcedChange(delay, change) => Some((delay, change)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// find the first consensus digest with the right ID which converts to
|
||||||
|
// the right kind of consensus log.
|
||||||
|
header.digest().convert_first(|l| l.try_to(id).and_then(filter_log))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::mock::{run_test, test_header, Origin, TestRuntime};
|
use crate::mock::{run_test, test_header, Origin, TestHash, TestHeader, TestNumber, TestRuntime};
|
||||||
use bp_test_utils::{authority_list, make_justification_for_header};
|
use bp_test_utils::{alice, authority_list, bob, make_justification_for_header};
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use frame_support::{assert_err, assert_ok};
|
use frame_support::weights::PostDispatchInfo;
|
||||||
|
use frame_support::{assert_err, assert_noop, assert_ok};
|
||||||
|
use sp_runtime::{Digest, DigestItem, DispatchError};
|
||||||
|
|
||||||
fn initialize_substrate_bridge() {
|
fn initialize_substrate_bridge() {
|
||||||
|
assert_ok!(init_with_origin(Origin::root()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_with_origin(
|
||||||
|
origin: Origin,
|
||||||
|
) -> Result<InitializationData<TestHeader>, sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>> {
|
||||||
let genesis = test_header(0);
|
let genesis = test_header(0);
|
||||||
|
|
||||||
let init_data = pallet_substrate_bridge::InitializationData {
|
let init_data = InitializationData {
|
||||||
header: genesis,
|
header: genesis,
|
||||||
authority_list: authority_list(),
|
authority_list: authority_list(),
|
||||||
set_id: 1,
|
set_id: 1,
|
||||||
scheduled_change: None,
|
|
||||||
is_halted: false,
|
is_halted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_ok!(pallet_substrate_bridge::Module::<TestRuntime>::initialize(
|
Module::<TestRuntime>::initialize(origin, init_data.clone()).map(|_| init_data)
|
||||||
Origin::root(),
|
|
||||||
init_data
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_finality_proof(child: u8, header: u8) -> frame_support::dispatch::DispatchResultWithPostInfo {
|
fn submit_finality_proof(child: u8, header: u8) -> frame_support::dispatch::DispatchResultWithPostInfo {
|
||||||
@@ -223,6 +548,146 @@ mod tests {
|
|||||||
let _ = Module::<TestRuntime>::on_initialize(current_number);
|
let _ = Module::<TestRuntime>::on_initialize(current_number);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn change_log(delay: u64) -> Digest<TestHash> {
|
||||||
|
let consensus_log = ConsensusLog::<TestNumber>::ScheduledChange(sp_finality_grandpa::ScheduledChange {
|
||||||
|
next_authorities: vec![(alice(), 1), (bob(), 1)],
|
||||||
|
delay,
|
||||||
|
});
|
||||||
|
|
||||||
|
Digest::<TestHash> {
|
||||||
|
logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forced_change_log(delay: u64) -> Digest<TestHash> {
|
||||||
|
let consensus_log = ConsensusLog::<TestNumber>::ForcedChange(
|
||||||
|
delay,
|
||||||
|
sp_finality_grandpa::ScheduledChange {
|
||||||
|
next_authorities: vec![(alice(), 1), (bob(), 1)],
|
||||||
|
delay,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Digest::<TestHash> {
|
||||||
|
logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_root_or_owner_origin_can_initialize_pallet() {
|
||||||
|
run_test(|| {
|
||||||
|
assert_noop!(init_with_origin(Origin::signed(1)), DispatchError::BadOrigin);
|
||||||
|
assert_ok!(init_with_origin(Origin::root()));
|
||||||
|
|
||||||
|
// Reset storage so we can initialize the pallet again
|
||||||
|
BestFinalized::<TestRuntime>::kill();
|
||||||
|
ModuleOwner::<TestRuntime>::put(2);
|
||||||
|
assert_ok!(init_with_origin(Origin::signed(2)));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_storage_entries_are_correctly_initialized() {
|
||||||
|
run_test(|| {
|
||||||
|
assert_eq!(
|
||||||
|
BestFinalized::<TestRuntime>::get(),
|
||||||
|
BridgedBlockHash::<TestRuntime>::default()
|
||||||
|
);
|
||||||
|
assert_eq!(Module::<TestRuntime>::best_finalized(), test_header(0));
|
||||||
|
|
||||||
|
let init_data = init_with_origin(Origin::root()).unwrap();
|
||||||
|
|
||||||
|
assert!(<ImportedHeaders<TestRuntime>>::contains_key(init_data.header.hash()));
|
||||||
|
assert_eq!(BestFinalized::<TestRuntime>::get(), init_data.header.hash());
|
||||||
|
assert_eq!(
|
||||||
|
CurrentAuthoritySet::<TestRuntime>::get().authorities,
|
||||||
|
init_data.authority_list
|
||||||
|
);
|
||||||
|
assert_eq!(IsHalted::<TestRuntime>::get(), false);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_can_only_initialize_pallet_once() {
|
||||||
|
run_test(|| {
|
||||||
|
initialize_substrate_bridge();
|
||||||
|
assert_noop!(
|
||||||
|
init_with_origin(Origin::root()),
|
||||||
|
<Error<TestRuntime>>::AlreadyInitialized
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pallet_owner_may_change_owner() {
|
||||||
|
run_test(|| {
|
||||||
|
ModuleOwner::<TestRuntime>::put(2);
|
||||||
|
|
||||||
|
assert_ok!(Module::<TestRuntime>::set_owner(Origin::root(), Some(1)));
|
||||||
|
assert_noop!(
|
||||||
|
Module::<TestRuntime>::halt_operations(Origin::signed(2)),
|
||||||
|
DispatchError::BadOrigin,
|
||||||
|
);
|
||||||
|
assert_ok!(Module::<TestRuntime>::halt_operations(Origin::root()));
|
||||||
|
|
||||||
|
assert_ok!(Module::<TestRuntime>::set_owner(Origin::signed(1), None));
|
||||||
|
assert_noop!(
|
||||||
|
Module::<TestRuntime>::resume_operations(Origin::signed(1)),
|
||||||
|
DispatchError::BadOrigin,
|
||||||
|
);
|
||||||
|
assert_noop!(
|
||||||
|
Module::<TestRuntime>::resume_operations(Origin::signed(2)),
|
||||||
|
DispatchError::BadOrigin,
|
||||||
|
);
|
||||||
|
assert_ok!(Module::<TestRuntime>::resume_operations(Origin::root()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pallet_may_be_halted_by_root() {
|
||||||
|
run_test(|| {
|
||||||
|
assert_ok!(Module::<TestRuntime>::halt_operations(Origin::root()));
|
||||||
|
assert_ok!(Module::<TestRuntime>::resume_operations(Origin::root()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pallet_may_be_halted_by_owner() {
|
||||||
|
run_test(|| {
|
||||||
|
ModuleOwner::<TestRuntime>::put(2);
|
||||||
|
|
||||||
|
assert_ok!(Module::<TestRuntime>::halt_operations(Origin::signed(2)));
|
||||||
|
assert_ok!(Module::<TestRuntime>::resume_operations(Origin::signed(2)));
|
||||||
|
|
||||||
|
assert_noop!(
|
||||||
|
Module::<TestRuntime>::halt_operations(Origin::signed(1)),
|
||||||
|
DispatchError::BadOrigin,
|
||||||
|
);
|
||||||
|
assert_noop!(
|
||||||
|
Module::<TestRuntime>::resume_operations(Origin::signed(1)),
|
||||||
|
DispatchError::BadOrigin,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_ok!(Module::<TestRuntime>::halt_operations(Origin::signed(2)));
|
||||||
|
assert_noop!(
|
||||||
|
Module::<TestRuntime>::resume_operations(Origin::signed(1)),
|
||||||
|
DispatchError::BadOrigin,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pallet_rejects_transactions_if_halted() {
|
||||||
|
run_test(|| {
|
||||||
|
<IsHalted<TestRuntime>>::put(true);
|
||||||
|
|
||||||
|
assert_noop!(
|
||||||
|
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), test_header(1), vec![], vec![]),
|
||||||
|
Error::<TestRuntime>::Halted,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn succesfully_imports_header_with_valid_finality_and_ancestry_proofs() {
|
fn succesfully_imports_header_with_valid_finality_and_ancestry_proofs() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
@@ -231,12 +696,8 @@ mod tests {
|
|||||||
assert_ok!(submit_finality_proof(1, 2));
|
assert_ok!(submit_finality_proof(1, 2));
|
||||||
|
|
||||||
let header = test_header(2);
|
let header = test_header(2);
|
||||||
assert_eq!(
|
assert_eq!(<BestFinalized<TestRuntime>>::get(), header.hash());
|
||||||
pallet_substrate_bridge::Module::<TestRuntime>::best_headers(),
|
assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
|
||||||
vec![(*header.number(), header.hash())]
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(pallet_substrate_bridge::Module::<TestRuntime>::best_finalized(), header);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,18 +770,14 @@ mod tests {
|
|||||||
let genesis = test_header(0);
|
let genesis = test_header(0);
|
||||||
|
|
||||||
let invalid_authority_list = vec![(alice(), u64::MAX), (bob(), u64::MAX)];
|
let invalid_authority_list = vec![(alice(), u64::MAX), (bob(), u64::MAX)];
|
||||||
let init_data = pallet_substrate_bridge::InitializationData {
|
let init_data = InitializationData {
|
||||||
header: genesis,
|
header: genesis,
|
||||||
authority_list: invalid_authority_list,
|
authority_list: invalid_authority_list,
|
||||||
set_id: 1,
|
set_id: 1,
|
||||||
scheduled_change: None,
|
|
||||||
is_halted: false,
|
is_halted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_ok!(pallet_substrate_bridge::Module::<TestRuntime>::initialize(
|
assert_ok!(Module::<TestRuntime>::initialize(Origin::root(), init_data));
|
||||||
Origin::root(),
|
|
||||||
init_data
|
|
||||||
));
|
|
||||||
|
|
||||||
let header = test_header(1);
|
let header = test_header(1);
|
||||||
let justification = [1u8; 32].encode();
|
let justification = [1u8; 32].encode();
|
||||||
@@ -334,7 +791,81 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn disallows_imports_once_limit_is_hit_in_single_block() {
|
fn importing_header_ensures_that_chain_is_extended() {
|
||||||
|
run_test(|| {
|
||||||
|
initialize_substrate_bridge();
|
||||||
|
|
||||||
|
assert_ok!(submit_finality_proof(5, 6));
|
||||||
|
assert_err!(submit_finality_proof(3, 4), Error::<TestRuntime>::OldHeader);
|
||||||
|
assert_ok!(submit_finality_proof(7, 8));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn importing_header_enacts_new_authority_set() {
|
||||||
|
run_test(|| {
|
||||||
|
initialize_substrate_bridge();
|
||||||
|
|
||||||
|
let next_set_id = 2;
|
||||||
|
let next_authorities = vec![(alice(), 1), (bob(), 1)];
|
||||||
|
|
||||||
|
// Need to update the header digest to indicate that our header signals an authority set
|
||||||
|
// change. The change will be enacted when we import our header.
|
||||||
|
let mut header = test_header(2);
|
||||||
|
header.digest = change_log(0);
|
||||||
|
|
||||||
|
// Let's import our test header
|
||||||
|
assert_ok!(pallet::import_header::<TestRuntime>(header.hash(), header.clone()));
|
||||||
|
|
||||||
|
// Make sure that our header is the best finalized
|
||||||
|
assert_eq!(<BestFinalized<TestRuntime>>::get(), header.hash());
|
||||||
|
assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
|
||||||
|
|
||||||
|
// Make sure that the authority set actually changed upon importing our header
|
||||||
|
assert_eq!(
|
||||||
|
<CurrentAuthoritySet<TestRuntime>>::get(),
|
||||||
|
bp_header_chain::AuthoritySet::new(next_authorities, next_set_id),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn importing_header_rejects_header_with_scheduled_change_delay() {
|
||||||
|
run_test(|| {
|
||||||
|
initialize_substrate_bridge();
|
||||||
|
|
||||||
|
// Need to update the header digest to indicate that our header signals an authority set
|
||||||
|
// change. However, the change doesn't happen until the next block.
|
||||||
|
let mut header = test_header(2);
|
||||||
|
header.digest = change_log(1);
|
||||||
|
|
||||||
|
// Should not be allowed to import this header
|
||||||
|
assert_err!(
|
||||||
|
pallet::import_header::<TestRuntime>(header.hash(), header),
|
||||||
|
<Error<TestRuntime>>::UnsupportedScheduledChange
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn importing_header_rejects_header_with_forced_changes() {
|
||||||
|
run_test(|| {
|
||||||
|
initialize_substrate_bridge();
|
||||||
|
|
||||||
|
// Need to update the header digest to indicate that it signals a forced authority set
|
||||||
|
// change.
|
||||||
|
let mut header = test_header(2);
|
||||||
|
header.digest = forced_change_log(0);
|
||||||
|
|
||||||
|
// Should not be allowed to import this header
|
||||||
|
assert_err!(
|
||||||
|
pallet::import_header::<TestRuntime>(header.hash(), header),
|
||||||
|
<Error<TestRuntime>>::UnsupportedScheduledChange
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn rate_limiter_disallows_imports_once_limit_is_hit_in_single_block() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_substrate_bridge();
|
initialize_substrate_bridge();
|
||||||
assert_ok!(submit_finality_proof(1, 2));
|
assert_ok!(submit_finality_proof(1, 2));
|
||||||
@@ -344,7 +875,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_requests_do_not_count_towards_request_count() {
|
fn rate_limiter_invalid_requests_do_not_count_towards_request_count() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
let submit_invalid_request = || {
|
let submit_invalid_request = || {
|
||||||
let child = test_header(1);
|
let child = test_header(1);
|
||||||
@@ -376,7 +907,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allows_request_after_new_block_has_started() {
|
fn rate_limiter_allows_request_after_new_block_has_started() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_substrate_bridge();
|
initialize_substrate_bridge();
|
||||||
assert_ok!(submit_finality_proof(1, 2));
|
assert_ok!(submit_finality_proof(1, 2));
|
||||||
@@ -388,7 +919,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn disallows_imports_once_limit_is_hit_across_different_blocks() {
|
fn rate_limiter_disallows_imports_once_limit_is_hit_across_different_blocks() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_substrate_bridge();
|
initialize_substrate_bridge();
|
||||||
assert_ok!(submit_finality_proof(1, 2));
|
assert_ok!(submit_finality_proof(1, 2));
|
||||||
@@ -401,7 +932,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allows_max_requests_after_long_time_with_no_activity() {
|
fn rate_limiter_allows_max_requests_after_long_time_with_no_activity() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_substrate_bridge();
|
initialize_substrate_bridge();
|
||||||
assert_ok!(submit_finality_proof(1, 2));
|
assert_ok!(submit_finality_proof(1, 2));
|
||||||
|
|||||||
@@ -17,8 +17,7 @@
|
|||||||
// From construct_runtime macro
|
// From construct_runtime macro
|
||||||
#![allow(clippy::from_over_into)]
|
#![allow(clippy::from_over_into)]
|
||||||
|
|
||||||
use crate::pallet::{BridgedHeader, Config};
|
use bp_runtime::Chain;
|
||||||
use bp_runtime::{BlockNumberOf, Chain};
|
|
||||||
use frame_support::{construct_runtime, parameter_types, weights::Weight};
|
use frame_support::{construct_runtime, parameter_types, weights::Weight};
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
testing::{Header, H256},
|
testing::{Header, H256},
|
||||||
@@ -27,8 +26,9 @@ use sp_runtime::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub type AccountId = u64;
|
pub type AccountId = u64;
|
||||||
pub type TestHeader = BridgedHeader<TestRuntime>;
|
pub type TestHeader = crate::BridgedHeader<TestRuntime>;
|
||||||
pub type TestNumber = BlockNumberOf<<TestRuntime as Config>::BridgedChain>;
|
pub type TestNumber = crate::BridgedBlockNumber<TestRuntime>;
|
||||||
|
pub type TestHash = crate::BridgedBlockHash<TestRuntime>;
|
||||||
|
|
||||||
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
||||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
||||||
|
|||||||
@@ -238,6 +238,11 @@ pub const IS_KNOWN_MILLAU_BLOCK_METHOD: &str = "MillauHeaderApi_is_known_block";
|
|||||||
/// Name of the `MillauHeaderApi::incomplete_headers` runtime method.
|
/// Name of the `MillauHeaderApi::incomplete_headers` runtime method.
|
||||||
pub const INCOMPLETE_MILLAU_HEADERS_METHOD: &str = "MillauHeaderApi_incomplete_headers";
|
pub const INCOMPLETE_MILLAU_HEADERS_METHOD: &str = "MillauHeaderApi_incomplete_headers";
|
||||||
|
|
||||||
|
/// Name of the `RialtoFinalityApi::best_finalized` runtime method.
|
||||||
|
pub const BEST_FINALIZED_MILLAU_HEADER_METHOD: &str = "MillauFinalityApi_best_finalized";
|
||||||
|
/// Name of the `RialtoFinalityApi::is_known_header` runtime method.
|
||||||
|
pub const IS_KNOW_MILLAU_HEADER_METHOD: &str = "MillauFinalityApi_is_known_header";
|
||||||
|
|
||||||
/// Name of the `ToMillauOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
/// Name of the `ToMillauOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
||||||
pub const TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
pub const TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
||||||
"ToMillauOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
"ToMillauOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
||||||
@@ -258,7 +263,7 @@ pub const FROM_MILLAU_UNREWARDED_RELAYERS_STATE: &str = "FromMillauInboundLaneAp
|
|||||||
sp_api::decl_runtime_apis! {
|
sp_api::decl_runtime_apis! {
|
||||||
/// API for querying information about Millau headers from the Bridge Pallet instance.
|
/// API for querying information about Millau headers from the Bridge Pallet instance.
|
||||||
///
|
///
|
||||||
/// This API is implemented by runtimes that are bridging with Millau chain, not the
|
/// This API is implemented by runtimes that are bridging with the Millau chain, not the
|
||||||
/// Millau runtime itself.
|
/// Millau runtime itself.
|
||||||
pub trait MillauHeaderApi {
|
pub trait MillauHeaderApi {
|
||||||
/// Returns number and hash of the best blocks known to the bridge module.
|
/// Returns number and hash of the best blocks known to the bridge module.
|
||||||
@@ -281,6 +286,17 @@ sp_api::decl_runtime_apis! {
|
|||||||
fn is_finalized_block(hash: Hash) -> bool;
|
fn is_finalized_block(hash: Hash) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// API for querying information about the finalized Millau headers.
|
||||||
|
///
|
||||||
|
/// This API is implemented by runtimes that are bridging with the Millau chain, not the
|
||||||
|
/// Millau runtime itself.
|
||||||
|
pub trait MillauFinalityApi {
|
||||||
|
/// Returns number and hash of the best finalized header known to the bridge module.
|
||||||
|
fn best_finalized() -> (BlockNumber, Hash);
|
||||||
|
/// Returns true if the header is known to the runtime.
|
||||||
|
fn is_known_header(hash: Hash) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
/// Outbound message lane API for messages that are sent to Millau chain.
|
/// Outbound message lane API for messages that are sent to Millau chain.
|
||||||
///
|
///
|
||||||
/// This API is implemented by runtimes that are sending messages to Millau chain, not the
|
/// This API is implemented by runtimes that are sending messages to Millau chain, not the
|
||||||
|
|||||||
@@ -199,6 +199,11 @@ pub const IS_KNOWN_RIALTO_BLOCK_METHOD: &str = "RialtoHeaderApi_is_known_block";
|
|||||||
/// Name of the `RialtoHeaderApi::incomplete_headers` runtime method.
|
/// Name of the `RialtoHeaderApi::incomplete_headers` runtime method.
|
||||||
pub const INCOMPLETE_RIALTO_HEADERS_METHOD: &str = "RialtoHeaderApi_incomplete_headers";
|
pub const INCOMPLETE_RIALTO_HEADERS_METHOD: &str = "RialtoHeaderApi_incomplete_headers";
|
||||||
|
|
||||||
|
/// Name of the `RialtoFinalityApi::best_finalized` runtime method.
|
||||||
|
pub const BEST_FINALIZED_RIALTO_HEADER_METHOD: &str = "RialtoFinalityApi_best_finalized";
|
||||||
|
/// Name of the `RialtoFinalityApi::is_known_header` runtime method.
|
||||||
|
pub const IS_KNOW_RIALTO_HEADER_METHOD: &str = "RialtoFinalityApi_is_known_header";
|
||||||
|
|
||||||
/// Name of the `ToRialtoOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
/// Name of the `ToRialtoOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
||||||
pub const TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
pub const TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
||||||
"ToRialtoOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
"ToRialtoOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
||||||
@@ -219,7 +224,7 @@ pub const FROM_RIALTO_UNREWARDED_RELAYERS_STATE: &str = "FromRialtoInboundLaneAp
|
|||||||
sp_api::decl_runtime_apis! {
|
sp_api::decl_runtime_apis! {
|
||||||
/// API for querying information about Rialto headers from the Bridge Pallet instance.
|
/// API for querying information about Rialto headers from the Bridge Pallet instance.
|
||||||
///
|
///
|
||||||
/// This API is implemented by runtimes that are bridging with Rialto chain, not the
|
/// This API is implemented by runtimes that are bridging with the Rialto chain, not the
|
||||||
/// Rialto runtime itself.
|
/// Rialto runtime itself.
|
||||||
pub trait RialtoHeaderApi {
|
pub trait RialtoHeaderApi {
|
||||||
/// Returns number and hash of the best blocks known to the bridge module.
|
/// Returns number and hash of the best blocks known to the bridge module.
|
||||||
@@ -242,6 +247,17 @@ sp_api::decl_runtime_apis! {
|
|||||||
fn is_finalized_block(hash: Hash) -> bool;
|
fn is_finalized_block(hash: Hash) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// API for querying information about the finalized Rialto headers.
|
||||||
|
///
|
||||||
|
/// This API is implemented by runtimes that are bridging with the Rialto chain, not the
|
||||||
|
/// Millau runtime itself.
|
||||||
|
pub trait RialtoFinalityApi {
|
||||||
|
/// Returns number and hash of the best finalized header known to the bridge module.
|
||||||
|
fn best_finalized() -> (BlockNumber, Hash);
|
||||||
|
/// Returns true if the header is known to the runtime.
|
||||||
|
fn is_known_header(hash: Hash) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
/// Outbound message lane API for messages that are sent to Rialto chain.
|
/// Outbound message lane API for messages that are sent to Rialto chain.
|
||||||
///
|
///
|
||||||
/// This API is implemented by runtimes that are sending messages to Rialto chain, not the
|
/// This API is implemented by runtimes that are sending messages to Rialto chain, not the
|
||||||
|
|||||||
Reference in New Issue
Block a user