fix: Complete snowbridge pezpallet rebrand and critical bug fixes
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
[package]
|
||||
name = "bp-beefy"
|
||||
description = "Primitives of pezpallet-bridge-beefy module."
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
publish = false
|
||||
documentation = "https://docs.rs/bp-beefy"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["bit-vec", "derive"], workspace = true }
|
||||
scale-info = { features = ["bit-vec", "derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
binary-merkle-tree = { workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezpallet-beefy-mmr = { workspace = true }
|
||||
pezpallet-mmr = { workspace = true }
|
||||
pezsp-consensus-beefy = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"binary-merkle-tree/std",
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezpallet-beefy-mmr/std",
|
||||
"pezpallet-mmr/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-consensus-beefy/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"binary-merkle-tree/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezpallet-beefy-mmr/runtime-benchmarks",
|
||||
"pezpallet-mmr/runtime-benchmarks",
|
||||
"pezsp-consensus-beefy/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,151 @@
|
||||
// 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 pezpallet.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub use binary_merkle_tree::merkle_root;
|
||||
pub use pezpallet_beefy_mmr::BeefyEcdsaToEthereum;
|
||||
pub use pezpallet_mmr::{
|
||||
primitives::{DataOrHash as MmrDataOrHash, LeafProof as MmrProof},
|
||||
verify_leaves_proof as verify_mmr_leaves_proof,
|
||||
};
|
||||
pub use pezsp_consensus_beefy::{
|
||||
ecdsa_crypto::{
|
||||
AuthorityId as EcdsaValidatorId, AuthoritySignature as EcdsaValidatorSignature,
|
||||
},
|
||||
known_payloads::MMR_ROOT_ID as MMR_ROOT_PAYLOAD_ID,
|
||||
mmr::{BeefyAuthoritySet, MmrLeafVersion},
|
||||
BeefyAuthorityId, Commitment, Payload as BeefyPayload, SignedCommitment, ValidatorSet,
|
||||
ValidatorSetId, BEEFY_ENGINE_ID,
|
||||
};
|
||||
|
||||
use pezbp_runtime::{BasicOperatingMode, BlockNumberOf, Chain, HashOf};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::Parameter;
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pezsp_runtime::{
|
||||
traits::{Convert, MaybeSerializeDeserialize},
|
||||
RuntimeAppPublic, RuntimeDebug,
|
||||
};
|
||||
use pezsp_std::prelude::*;
|
||||
|
||||
/// Bizinikiwi-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 `pezsc_consensus_beefy::BeefyKeystore`.
|
||||
type CommitmentHasher: pezsp_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 `pezpallet-mmr` configuration.
|
||||
type MmrHashing: pezsp_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 `pezpallet-mmr` configuration.
|
||||
type MmrHash: pezsp_std::hash::Hash
|
||||
+ Parameter
|
||||
+ Copy
|
||||
+ AsRef<[u8]>
|
||||
+ Default
|
||||
+ MaybeSerializeDeserialize
|
||||
+ PartialOrd;
|
||||
|
||||
/// 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 `pezpallet-beefy` configuration.
|
||||
type AuthorityId: BeefyAuthorityId<Self::CommitmentHasher> + Parameter;
|
||||
|
||||
/// A way to convert validator id to its raw representation in the BEEFY merkle tree.
|
||||
///
|
||||
/// Corresponds to the `BeefyAuthorityToMerkleLeaf` field of the `pezpallet-beefy-mmr`
|
||||
/// configuration.
|
||||
type AuthorityIdToMerkleLeaf: Convert<Self::AuthorityId, Vec<u8>>;
|
||||
}
|
||||
|
||||
/// BEEFY validator id used by given Bizinikiwi 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> = pezsp_consensus_beefy::mmr::BeefyAuthoritySet<MmrHashOf<C>>;
|
||||
/// BEEFY validator signature used by given Bizinikiwi chain.
|
||||
pub type BeefyValidatorSignatureOf<C> =
|
||||
<<C as ChainWithBeefy>::AuthorityId as RuntimeAppPublic>::Signature;
|
||||
/// Signed BEEFY commitment used by given Bizinikiwi 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 Bizinikiwi chain.
|
||||
pub type MmrHashingOf<C> = <C as ChainWithBeefy>::MmrHashing;
|
||||
/// Hash type, used in MMR construction by given Bizinikiwi chain.
|
||||
pub type MmrHashOf<C> = <C as ChainWithBeefy>::MmrHash;
|
||||
/// BEEFY MMR proof type used by the given Bizinikiwi chain.
|
||||
pub type MmrProofOf<C> = MmrProof<MmrHashOf<C>>;
|
||||
/// The type of the MMR leaf extra data used by the given Bizinikiwi 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 Bizinikiwi chain.
|
||||
pub type BeefyAuthorityIdToMerkleLeafOf<C> = <C as ChainWithBeefy>::AuthorityIdToMerkleLeaf;
|
||||
/// Actual type of leafs in the BEEFY MMR.
|
||||
pub type BeefyMmrLeafOf<C> = pezsp_consensus_beefy::mmr::MmrLeaf<
|
||||
BlockNumberOf<C>,
|
||||
HashOf<C>,
|
||||
MmrHashOf<C>,
|
||||
BeefyMmrLeafExtraOf<C>,
|
||||
>;
|
||||
|
||||
/// Data required for initializing the BEEFY pezpallet.
|
||||
///
|
||||
/// 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, Serialize, Deserialize)]
|
||||
pub struct InitializationData<BlockNumber, Hash> {
|
||||
/// Pezpallet 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 pezpallet 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,
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
[package]
|
||||
name = "bp-header-pez-chain"
|
||||
description = "A common interface for describing what a bridge pezpallet should be able to do."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-header-pez-chain"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
finality-grandpa = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezsp-consensus-grandpa = { features = ["serde"], workspace = true }
|
||||
pezsp-core = { features = ["serde"], workspace = true }
|
||||
pezsp-runtime = { features = ["serde"], workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-test-utils = { workspace = true, default-features = true }
|
||||
hex = { workspace = true, default-features = true }
|
||||
hex-literal = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"finality-grandpa/std",
|
||||
"pezframe-support/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-consensus-grandpa/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-test-utils/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezsp-consensus-grandpa/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines structures related to calls of the `pezpallet-bridge-grandpa` pezpallet.
|
||||
|
||||
use crate::{justification, InitializationData};
|
||||
|
||||
use pezbp_runtime::HeaderOf;
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{weights::Weight, RuntimeDebugNoBound};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_consensus_grandpa::SetId;
|
||||
use pezsp_runtime::traits::{Header as HeaderT, Zero};
|
||||
use pezsp_std::{boxed::Box, fmt::Debug};
|
||||
|
||||
/// A minimized version of `pezpallet-bridge-grandpa::Call` that can be used without a runtime.
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeGrandpaCall<Header: HeaderT> {
|
||||
/// `pezpallet-bridge-grandpa::Call::submit_finality_proof`
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof {
|
||||
/// The header that we are going to finalize.
|
||||
finality_target: Box<Header>,
|
||||
/// Finality justification for the `finality_target`.
|
||||
justification: justification::GrandpaJustification<Header>,
|
||||
},
|
||||
/// `pezpallet-bridge-grandpa::Call::initialize`
|
||||
#[codec(index = 1)]
|
||||
initialize {
|
||||
/// All data, required to initialize the pezpallet.
|
||||
init_data: InitializationData<Header>,
|
||||
},
|
||||
/// `pezpallet-bridge-grandpa::Call::submit_finality_proof_ex`
|
||||
#[codec(index = 4)]
|
||||
submit_finality_proof_ex {
|
||||
/// The header that we are going to finalize.
|
||||
finality_target: Box<Header>,
|
||||
/// Finality justification for the `finality_target`.
|
||||
justification: justification::GrandpaJustification<Header>,
|
||||
/// An identifier of the validators set, that have signed the justification.
|
||||
current_set_id: SetId,
|
||||
},
|
||||
}
|
||||
|
||||
/// The `BridgeGrandpaCall` for a pezpallet that bridges with given `C`;
|
||||
pub type BridgeGrandpaCallOf<C> = BridgeGrandpaCall<HeaderOf<C>>;
|
||||
|
||||
/// A digest information on the `BridgeGrandpaCall::submit_finality_proof` call.
|
||||
#[derive(Copy, Clone, PartialEq, RuntimeDebugNoBound)]
|
||||
pub struct SubmitFinalityProofInfo<N: Debug> {
|
||||
/// Number of the finality target.
|
||||
pub block_number: N,
|
||||
/// An identifier of the validators set that has signed the submitted justification.
|
||||
/// It might be `None` if deprecated version of the `submit_finality_proof` is used.
|
||||
pub current_set_id: Option<SetId>,
|
||||
/// If `true`, then the call proves new **mandatory** header.
|
||||
pub is_mandatory: bool,
|
||||
/// If `true`, then the call must be free (assuming that everything else is valid) to
|
||||
/// be treated as valid.
|
||||
pub is_free_execution_expected: bool,
|
||||
/// Extra weight that we assume is included in the call.
|
||||
///
|
||||
/// We have some assumptions about headers and justifications of the bridged chain.
|
||||
/// We know that if our assumptions are correct, then the call must not have the
|
||||
/// weight above some limit. The fee paid for weight above that limit, is never refunded.
|
||||
pub extra_weight: Weight,
|
||||
/// Extra size (in bytes) that we assume are included in the call.
|
||||
///
|
||||
/// We have some assumptions about headers and justifications of the bridged chain.
|
||||
/// We know that if our assumptions are correct, then the call must not have the
|
||||
/// weight above some limit. The fee paid for bytes above that limit, is never refunded.
|
||||
pub extra_size: u32,
|
||||
}
|
||||
|
||||
impl<N: Debug> SubmitFinalityProofInfo<N> {
|
||||
/// Returns `true` if call size/weight is below our estimations for regular calls.
|
||||
pub fn fits_limits(&self) -> bool {
|
||||
self.extra_weight.is_zero() && self.extra_size.is_zero()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for checking GRANDPA Finality Proofs.
|
||||
//!
|
||||
//! Adapted copy of bizinikiwi/client/finality-grandpa/src/justification.rs. If origin
|
||||
//! will ever be moved to the pezsp_consensus_grandpa, we should reuse that implementation.
|
||||
|
||||
mod verification;
|
||||
|
||||
use crate::ChainWithGrandpa;
|
||||
pub use verification::{
|
||||
equivocation::{EquivocationsCollector, GrandpaEquivocationsFinder},
|
||||
optimizer::verify_and_optimize_justification,
|
||||
strict::verify_justification,
|
||||
AncestryChain, Error as JustificationVerificationError, JustificationVerificationContext,
|
||||
PrecommitError,
|
||||
};
|
||||
|
||||
use pezbp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthoritySignature};
|
||||
use pezsp_runtime::{traits::Header as HeaderT, RuntimeDebug, SaturatedConversion};
|
||||
use pezsp_std::prelude::*;
|
||||
|
||||
/// A GRANDPA Justification is a proof that a given header was finalized
|
||||
/// at a certain height and with a certain set of authorities.
|
||||
///
|
||||
/// This particular proof is used to prove that headers on a bridged chain
|
||||
/// (so not our chain) have been finalized correctly.
|
||||
#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct GrandpaJustification<Header: HeaderT> {
|
||||
/// The round (voting period) this justification is valid for.
|
||||
pub round: u64,
|
||||
/// The set of votes for the chain which is to be finalized.
|
||||
pub commit:
|
||||
finality_grandpa::Commit<Header::Hash, Header::Number, AuthoritySignature, AuthorityId>,
|
||||
/// A proof that the chain of blocks in the commit are related to each other.
|
||||
pub votes_ancestries: Vec<Header>,
|
||||
}
|
||||
|
||||
// A proper Debug impl for no-std is not possible for the `GrandpaJustification` since the `Commit`
|
||||
// type only implements Debug that for `std` here:
|
||||
// https://github.com/paritytech/finality-grandpa/blob/8c45a664c05657f0c71057158d3ba555ba7d20de/src/lib.rs#L275
|
||||
// so we do a manual impl.
|
||||
#[cfg(not(feature = "std"))]
|
||||
impl<H: HeaderT> core::fmt::Debug for GrandpaJustification<H> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "GrandpaJustification {{ round: {:?}, commit: <wasm:stripped>, votes_ancestries: {:?} }}", self.round, self.votes_ancestries)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: HeaderT> GrandpaJustification<H> {
|
||||
/// Returns reasonable size of justification using constants from the provided chain.
|
||||
///
|
||||
/// An imprecise analogue of `MaxEncodedLen` implementation. We don't use it for
|
||||
/// any precise calculations - that's just an estimation.
|
||||
pub fn max_reasonable_size<C>(required_precommits: u32) -> u32
|
||||
where
|
||||
C: Chain + ChainWithGrandpa,
|
||||
{
|
||||
// we don't need precise results here - just estimations, so some details
|
||||
// are removed from computations (e.g. bytes required to encode vector length)
|
||||
|
||||
// structures in `finality_grandpa` crate are not implementing `MaxEncodedLength`, so
|
||||
// here's our estimation for the `finality_grandpa::Commit` struct size
|
||||
//
|
||||
// precommit is: hash + number
|
||||
// signed precommit is: precommit + signature (64b) + authority id
|
||||
// commit is: hash + number + vec of signed precommits
|
||||
let signed_precommit_size: u32 = BlockNumberOf::<C>::max_encoded_len()
|
||||
.saturating_add(HashOf::<C>::max_encoded_len().saturated_into())
|
||||
.saturating_add(64)
|
||||
.saturating_add(AuthorityId::max_encoded_len().saturated_into())
|
||||
.saturated_into();
|
||||
let max_expected_signed_commit_size = signed_precommit_size
|
||||
.saturating_mul(required_precommits)
|
||||
.saturating_add(BlockNumberOf::<C>::max_encoded_len().saturated_into())
|
||||
.saturating_add(HashOf::<C>::max_encoded_len().saturated_into());
|
||||
|
||||
let max_expected_votes_ancestries_size =
|
||||
C::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY.saturating_mul(C::AVERAGE_HEADER_SIZE);
|
||||
|
||||
// justification is round number (u64=8b), a signed GRANDPA commit and the
|
||||
// `votes_ancestries` vector
|
||||
8u32.saturating_add(max_expected_signed_commit_size)
|
||||
.saturating_add(max_expected_votes_ancestries_size)
|
||||
}
|
||||
|
||||
/// Return identifier of header that this justification claims to finalize.
|
||||
pub fn commit_target_id(&self) -> HeaderId<H::Hash, H::Number> {
|
||||
HeaderId(self.commit.target_number, self.commit.target_hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: HeaderT> crate::FinalityProof<H::Hash, H::Number> for GrandpaJustification<H> {
|
||||
fn target_header_hash(&self) -> H::Hash {
|
||||
self.commit.target_hash
|
||||
}
|
||||
|
||||
fn target_header_number(&self) -> H::Number {
|
||||
self.commit.target_number
|
||||
}
|
||||
}
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Failed to decode justification.
|
||||
JustificationDecode,
|
||||
}
|
||||
|
||||
/// Given GRANDPA authorities set size, return number of valid authorities votes that the
|
||||
/// justification must have to be valid.
|
||||
///
|
||||
/// This function assumes that all authorities have the same vote weight.
|
||||
pub fn required_justification_precommits(authorities_set_length: u32) -> u32 {
|
||||
authorities_set_length - authorities_set_length.saturating_sub(1) / 3
|
||||
}
|
||||
|
||||
/// Decode justification target.
|
||||
pub fn decode_justification_target<Header: HeaderT>(
|
||||
raw_justification: &[u8],
|
||||
) -> Result<(Header::Hash, Header::Number), Error> {
|
||||
GrandpaJustification::<Header>::decode(&mut &*raw_justification)
|
||||
.map(|justification| (justification.commit.target_hash, justification.commit.target_number))
|
||||
.map_err(|_| Error::JustificationDecode)
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for extracting equivocations from multiple GRANDPA Finality Proofs.
|
||||
|
||||
use crate::{
|
||||
justification::{
|
||||
verification::{
|
||||
Error as JustificationVerificationError, IterationFlow,
|
||||
JustificationVerificationContext, JustificationVerifier, PrecommitError,
|
||||
SignedPrecommit,
|
||||
},
|
||||
GrandpaJustification,
|
||||
},
|
||||
ChainWithGrandpa, FindEquivocations,
|
||||
};
|
||||
|
||||
use pezbp_runtime::{BlockNumberOf, HashOf, HeaderOf};
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthoritySignature, EquivocationProof, Precommit};
|
||||
use pezsp_runtime::traits::Header as HeaderT;
|
||||
use pezsp_std::{
|
||||
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
||||
prelude::*,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
enum AuthorityVotes<Header: HeaderT> {
|
||||
SingleVote(SignedPrecommit<Header>),
|
||||
Equivocation(
|
||||
finality_grandpa::Equivocation<AuthorityId, Precommit<Header>, AuthoritySignature>,
|
||||
),
|
||||
}
|
||||
|
||||
/// Structure that can extract equivocations from multiple GRANDPA justifications.
|
||||
pub struct EquivocationsCollector<'a, Header: HeaderT> {
|
||||
round: u64,
|
||||
context: &'a JustificationVerificationContext,
|
||||
|
||||
votes: BTreeMap<AuthorityId, AuthorityVotes<Header>>,
|
||||
}
|
||||
|
||||
impl<'a, Header: HeaderT> EquivocationsCollector<'a, Header> {
|
||||
/// Create a new instance of `EquivocationsCollector`.
|
||||
pub fn new(
|
||||
context: &'a JustificationVerificationContext,
|
||||
base_justification: &GrandpaJustification<Header>,
|
||||
) -> Result<Self, JustificationVerificationError> {
|
||||
let mut checker = Self { round: base_justification.round, context, votes: BTreeMap::new() };
|
||||
|
||||
checker.verify_justification(
|
||||
(base_justification.commit.target_hash, base_justification.commit.target_number),
|
||||
checker.context,
|
||||
base_justification,
|
||||
)?;
|
||||
|
||||
Ok(checker)
|
||||
}
|
||||
|
||||
/// Parse additional justifications for equivocations.
|
||||
pub fn parse_justifications(&mut self, justifications: &[GrandpaJustification<Header>]) {
|
||||
let round = self.round;
|
||||
for justification in
|
||||
justifications.iter().filter(|justification| round == justification.round)
|
||||
{
|
||||
// We ignore the Errors received here since we don't care if the proofs are valid.
|
||||
// We only care about collecting equivocations.
|
||||
let _ = self.verify_justification(
|
||||
(justification.commit.target_hash, justification.commit.target_number),
|
||||
self.context,
|
||||
justification,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the equivocation proofs that have been collected.
|
||||
pub fn into_equivocation_proofs(self) -> Vec<EquivocationProof<Header::Hash, Header::Number>> {
|
||||
let mut equivocations = vec![];
|
||||
for (_authority, vote) in self.votes {
|
||||
if let AuthorityVotes::Equivocation(equivocation) = vote {
|
||||
equivocations.push(EquivocationProof::new(
|
||||
self.context.authority_set_id,
|
||||
pezsp_consensus_grandpa::Equivocation::Precommit(equivocation),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
equivocations
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Header: HeaderT> JustificationVerifier<Header> for EquivocationsCollector<'a, Header> {
|
||||
fn process_duplicate_votes_ancestries(
|
||||
&mut self,
|
||||
_duplicate_votes_ancestries: Vec<usize>,
|
||||
) -> Result<(), JustificationVerificationError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_redundant_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
Ok(IterationFlow::Run)
|
||||
}
|
||||
|
||||
fn process_known_authority_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
_signed: &SignedPrecommit<Header>,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
Ok(IterationFlow::Run)
|
||||
}
|
||||
|
||||
fn process_unknown_authority_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_unrelated_ancestry_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
Ok(IterationFlow::Run)
|
||||
}
|
||||
|
||||
fn process_invalid_signature_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>) {
|
||||
match self.votes.get_mut(&signed.id) {
|
||||
Some(vote) => match vote {
|
||||
AuthorityVotes::SingleVote(first_vote) => {
|
||||
if first_vote.precommit != signed.precommit {
|
||||
*vote = AuthorityVotes::Equivocation(finality_grandpa::Equivocation {
|
||||
round_number: self.round,
|
||||
identity: signed.id.clone(),
|
||||
first: (first_vote.precommit.clone(), first_vote.signature.clone()),
|
||||
second: (signed.precommit.clone(), signed.signature.clone()),
|
||||
});
|
||||
}
|
||||
},
|
||||
AuthorityVotes::Equivocation(_) => {},
|
||||
},
|
||||
None => {
|
||||
self.votes.insert(signed.id.clone(), AuthorityVotes::SingleVote(signed.clone()));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn process_redundant_votes_ancestries(
|
||||
&mut self,
|
||||
_redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
) -> Result<(), JustificationVerificationError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for finding equivocations in GRANDPA proofs.
|
||||
pub struct GrandpaEquivocationsFinder<C>(pezsp_std::marker::PhantomData<C>);
|
||||
|
||||
impl<C: ChainWithGrandpa>
|
||||
FindEquivocations<
|
||||
GrandpaJustification<HeaderOf<C>>,
|
||||
JustificationVerificationContext,
|
||||
EquivocationProof<HashOf<C>, BlockNumberOf<C>>,
|
||||
> for GrandpaEquivocationsFinder<C>
|
||||
{
|
||||
type Error = JustificationVerificationError;
|
||||
|
||||
fn find_equivocations(
|
||||
verification_context: &JustificationVerificationContext,
|
||||
synced_proof: &GrandpaJustification<HeaderOf<C>>,
|
||||
source_proofs: &[GrandpaJustification<HeaderOf<C>>],
|
||||
) -> Result<Vec<EquivocationProof<HashOf<C>, BlockNumberOf<C>>>, Self::Error> {
|
||||
let mut equivocations_collector =
|
||||
EquivocationsCollector::new(verification_context, synced_proof)?;
|
||||
|
||||
equivocations_collector.parse_justifications(source_proofs);
|
||||
|
||||
Ok(equivocations_collector.into_equivocation_proofs())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for checking GRANDPA Finality Proofs.
|
||||
|
||||
pub mod equivocation;
|
||||
pub mod optimizer;
|
||||
pub mod strict;
|
||||
|
||||
use crate::{justification::GrandpaJustification, AuthoritySet};
|
||||
|
||||
use pezbp_runtime::HeaderId;
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthoritySignature, SetId};
|
||||
use pezsp_runtime::{traits::Header as HeaderT, RuntimeDebug};
|
||||
use pezsp_std::{
|
||||
collections::{
|
||||
btree_map::{
|
||||
BTreeMap,
|
||||
Entry::{Occupied, Vacant},
|
||||
},
|
||||
btree_set::BTreeSet,
|
||||
},
|
||||
prelude::*,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
type SignedPrecommit<Header> = finality_grandpa::SignedPrecommit<
|
||||
<Header as HeaderT>::Hash,
|
||||
<Header as HeaderT>::Number,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// Votes ancestries with useful methods.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct AncestryChain<Header: HeaderT> {
|
||||
/// We expect all forks in the ancestry chain to be descendants of base.
|
||||
base: HeaderId<Header::Hash, Header::Number>,
|
||||
/// Header hash => parent header hash mapping.
|
||||
parents: BTreeMap<Header::Hash, Header::Hash>,
|
||||
/// Hashes of headers that were not visited by `ancestry()`.
|
||||
unvisited: BTreeSet<Header::Hash>,
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> AncestryChain<Header> {
|
||||
/// Creates a new instance of `AncestryChain` starting from a `GrandpaJustification`.
|
||||
///
|
||||
/// Returns the `AncestryChain` and a `Vec` containing the `votes_ancestries` entries
|
||||
/// that were ignored when creating it, because they are duplicates.
|
||||
pub fn new(
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> (AncestryChain<Header>, Vec<usize>) {
|
||||
let mut parents = BTreeMap::new();
|
||||
let mut unvisited = BTreeSet::new();
|
||||
let mut ignored_idxs = Vec::new();
|
||||
for (idx, ancestor) in justification.votes_ancestries.iter().enumerate() {
|
||||
let hash = ancestor.hash();
|
||||
match parents.entry(hash) {
|
||||
Occupied(_) => {
|
||||
ignored_idxs.push(idx);
|
||||
},
|
||||
Vacant(entry) => {
|
||||
entry.insert(*ancestor.parent_hash());
|
||||
unvisited.insert(hash);
|
||||
},
|
||||
}
|
||||
}
|
||||
(AncestryChain { base: justification.commit_target_id(), parents, unvisited }, ignored_idxs)
|
||||
}
|
||||
|
||||
/// Returns the hash of a block's parent if the block is present in the ancestry.
|
||||
pub fn parent_hash_of(&self, hash: &Header::Hash) -> Option<&Header::Hash> {
|
||||
self.parents.get(hash)
|
||||
}
|
||||
|
||||
/// Returns a route if the precommit target block is a descendant of the `base` block.
|
||||
pub fn ancestry(
|
||||
&self,
|
||||
precommit_target_hash: &Header::Hash,
|
||||
precommit_target_number: &Header::Number,
|
||||
) -> Option<Vec<Header::Hash>> {
|
||||
if precommit_target_number < &self.base.number() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut route = vec![];
|
||||
let mut current_hash = *precommit_target_hash;
|
||||
loop {
|
||||
if current_hash == self.base.hash() {
|
||||
break;
|
||||
}
|
||||
|
||||
current_hash = match self.parent_hash_of(¤t_hash) {
|
||||
Some(parent_hash) => {
|
||||
let is_visited_before = self.unvisited.get(¤t_hash).is_none();
|
||||
if is_visited_before {
|
||||
// If the current header has been visited in a previous call, it is a
|
||||
// descendent of `base` (we assume that the previous call was successful).
|
||||
return Some(route);
|
||||
}
|
||||
route.push(current_hash);
|
||||
|
||||
*parent_hash
|
||||
},
|
||||
None => return None,
|
||||
};
|
||||
}
|
||||
|
||||
Some(route)
|
||||
}
|
||||
|
||||
fn mark_route_as_visited(&mut self, route: Vec<Header::Hash>) {
|
||||
for hash in route {
|
||||
self.unvisited.remove(&hash);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_fully_visited(&self) -> bool {
|
||||
self.unvisited.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Could not convert `AuthorityList` to `VoterSet`.
|
||||
InvalidAuthorityList,
|
||||
/// Justification is finalizing unexpected header.
|
||||
InvalidJustificationTarget,
|
||||
/// The justification contains duplicate headers in its `votes_ancestries` field.
|
||||
DuplicateVotesAncestries,
|
||||
/// Error validating a precommit
|
||||
Precommit(PrecommitError),
|
||||
/// The cumulative weight of all votes in the justification is not enough to justify commit
|
||||
/// header finalization.
|
||||
TooLowCumulativeWeight,
|
||||
/// The justification contains extra (unused) headers in its `votes_ancestries` field.
|
||||
RedundantVotesAncestries,
|
||||
}
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub enum PrecommitError {
|
||||
/// Justification contains redundant votes.
|
||||
RedundantAuthorityVote,
|
||||
/// Justification contains unknown authority precommit.
|
||||
UnknownAuthorityVote,
|
||||
/// Justification contains duplicate authority precommit.
|
||||
DuplicateAuthorityVote,
|
||||
/// The authority has provided an invalid signature.
|
||||
InvalidAuthoritySignature,
|
||||
/// The justification contains precommit for header that is not a descendant of the commit
|
||||
/// header.
|
||||
UnrelatedAncestryVote,
|
||||
}
|
||||
|
||||
/// The context needed for validating GRANDPA finality proofs.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct JustificationVerificationContext {
|
||||
/// The authority set used to verify the justification.
|
||||
pub voter_set: VoterSet<AuthorityId>,
|
||||
/// The ID of the authority set used to verify the justification.
|
||||
pub authority_set_id: SetId,
|
||||
}
|
||||
|
||||
impl TryFrom<AuthoritySet> for JustificationVerificationContext {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(authority_set: AuthoritySet) -> Result<Self, Self::Error> {
|
||||
let voter_set =
|
||||
VoterSet::new(authority_set.authorities).ok_or(Error::InvalidAuthorityList)?;
|
||||
Ok(JustificationVerificationContext { voter_set, authority_set_id: authority_set.set_id })
|
||||
}
|
||||
}
|
||||
|
||||
enum IterationFlow {
|
||||
Run,
|
||||
Skip,
|
||||
}
|
||||
|
||||
/// Verification callbacks.
|
||||
trait JustificationVerifier<Header: HeaderT> {
|
||||
/// Called when there are duplicate headers in the votes ancestries.
|
||||
fn process_duplicate_votes_ancestries(
|
||||
&mut self,
|
||||
duplicate_votes_ancestries: Vec<usize>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn process_redundant_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError>;
|
||||
|
||||
fn process_known_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
signed: &SignedPrecommit<Header>,
|
||||
) -> Result<IterationFlow, PrecommitError>;
|
||||
|
||||
fn process_unknown_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError>;
|
||||
|
||||
fn process_unrelated_ancestry_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError>;
|
||||
|
||||
fn process_invalid_signature_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError>;
|
||||
|
||||
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>);
|
||||
|
||||
/// Called when there are redundant headers in the votes ancestries.
|
||||
fn process_redundant_votes_ancestries(
|
||||
&mut self,
|
||||
redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn verify_justification(
|
||||
&mut self,
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
context: &JustificationVerificationContext,
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
// ensure that it is justification for the expected header
|
||||
if (justification.commit.target_hash, justification.commit.target_number) !=
|
||||
finalized_target
|
||||
{
|
||||
return Err(Error::InvalidJustificationTarget);
|
||||
}
|
||||
|
||||
let threshold = context.voter_set.threshold().get();
|
||||
let (mut chain, ignored_idxs) = AncestryChain::new(justification);
|
||||
let mut signature_buffer = Vec::new();
|
||||
let mut cumulative_weight = 0u64;
|
||||
|
||||
if !ignored_idxs.is_empty() {
|
||||
self.process_duplicate_votes_ancestries(ignored_idxs)?;
|
||||
}
|
||||
|
||||
for (precommit_idx, signed) in justification.commit.precommits.iter().enumerate() {
|
||||
if cumulative_weight >= threshold {
|
||||
let action =
|
||||
self.process_redundant_vote(precommit_idx).map_err(Error::Precommit)?;
|
||||
if matches!(action, IterationFlow::Skip) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// authority must be in the set
|
||||
let authority_info = match context.voter_set.get(&signed.id) {
|
||||
Some(authority_info) => {
|
||||
// The implementer may want to do extra checks here.
|
||||
// For example to see if the authority has already voted in the same round.
|
||||
let action = self
|
||||
.process_known_authority_vote(precommit_idx, signed)
|
||||
.map_err(Error::Precommit)?;
|
||||
if matches!(action, IterationFlow::Skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
authority_info
|
||||
},
|
||||
None => {
|
||||
self.process_unknown_authority_vote(precommit_idx).map_err(Error::Precommit)?;
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
// all precommits must be descendants of the target block
|
||||
let maybe_route =
|
||||
chain.ancestry(&signed.precommit.target_hash, &signed.precommit.target_number);
|
||||
if maybe_route.is_none() {
|
||||
let action = self
|
||||
.process_unrelated_ancestry_vote(precommit_idx)
|
||||
.map_err(Error::Precommit)?;
|
||||
if matches!(action, IterationFlow::Skip) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// verify authority signature
|
||||
if !pezsp_consensus_grandpa::check_message_signature_with_buffer(
|
||||
&finality_grandpa::Message::Precommit(signed.precommit.clone()),
|
||||
&signed.id,
|
||||
&signed.signature,
|
||||
justification.round,
|
||||
context.authority_set_id,
|
||||
&mut signature_buffer,
|
||||
)
|
||||
.is_valid()
|
||||
{
|
||||
self.process_invalid_signature_vote(precommit_idx).map_err(Error::Precommit)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
// now we can count the vote since we know that it is valid
|
||||
self.process_valid_vote(signed);
|
||||
if let Some(route) = maybe_route {
|
||||
chain.mark_route_as_visited(route);
|
||||
cumulative_weight = cumulative_weight.saturating_add(authority_info.weight().get());
|
||||
}
|
||||
}
|
||||
|
||||
// check that the cumulative weight of validators that voted for the justification target
|
||||
// (or one of its descendants) is larger than the required threshold.
|
||||
if cumulative_weight < threshold {
|
||||
return Err(Error::TooLowCumulativeWeight);
|
||||
}
|
||||
|
||||
// check that there are no extra headers in the justification
|
||||
if !chain.is_fully_visited() {
|
||||
self.process_redundant_votes_ancestries(chain.unvisited)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for optimizing GRANDPA Finality Proofs.
|
||||
|
||||
use crate::justification::{
|
||||
verification::{Error, JustificationVerifier, PrecommitError},
|
||||
GrandpaJustification,
|
||||
};
|
||||
|
||||
use crate::justification::verification::{
|
||||
IterationFlow, JustificationVerificationContext, SignedPrecommit,
|
||||
};
|
||||
use pezsp_consensus_grandpa::AuthorityId;
|
||||
use pezsp_runtime::traits::Header as HeaderT;
|
||||
use pezsp_std::{collections::btree_set::BTreeSet, prelude::*, vec, vec::Vec};
|
||||
|
||||
// Verification callbacks for justification optimization.
|
||||
struct JustificationOptimizer<Header: HeaderT> {
|
||||
votes: BTreeSet<AuthorityId>,
|
||||
|
||||
extra_precommits: Vec<usize>,
|
||||
duplicate_votes_ancestries_idxs: Vec<usize>,
|
||||
redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> JustificationOptimizer<Header> {
|
||||
fn optimize(self, justification: &mut GrandpaJustification<Header>) {
|
||||
for invalid_precommit_idx in self.extra_precommits.into_iter().rev() {
|
||||
justification.commit.precommits.remove(invalid_precommit_idx);
|
||||
}
|
||||
if !self.duplicate_votes_ancestries_idxs.is_empty() {
|
||||
for idx in self.duplicate_votes_ancestries_idxs.iter().rev() {
|
||||
justification.votes_ancestries.swap_remove(*idx);
|
||||
}
|
||||
}
|
||||
if !self.redundant_votes_ancestries.is_empty() {
|
||||
justification
|
||||
.votes_ancestries
|
||||
.retain(|header| !self.redundant_votes_ancestries.contains(&header.hash()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> JustificationVerifier<Header> for JustificationOptimizer<Header> {
|
||||
fn process_duplicate_votes_ancestries(
|
||||
&mut self,
|
||||
duplicate_votes_ancestries: Vec<usize>,
|
||||
) -> Result<(), Error> {
|
||||
self.duplicate_votes_ancestries_idxs = duplicate_votes_ancestries.to_vec();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_redundant_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
Ok(IterationFlow::Skip)
|
||||
}
|
||||
|
||||
fn process_known_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
signed: &SignedPrecommit<Header>,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
// Skip duplicate votes
|
||||
if self.votes.contains(&signed.id) {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
return Ok(IterationFlow::Skip);
|
||||
}
|
||||
|
||||
Ok(IterationFlow::Run)
|
||||
}
|
||||
|
||||
fn process_unknown_authority_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_unrelated_ancestry_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
Ok(IterationFlow::Skip)
|
||||
}
|
||||
|
||||
fn process_invalid_signature_vote(
|
||||
&mut self,
|
||||
precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
self.extra_precommits.push(precommit_idx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>) {
|
||||
self.votes.insert(signed.id.clone());
|
||||
}
|
||||
|
||||
fn process_redundant_votes_ancestries(
|
||||
&mut self,
|
||||
redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
) -> Result<(), Error> {
|
||||
self.redundant_votes_ancestries = redundant_votes_ancestries;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify and optimize given justification by removing unknown and duplicate votes.
|
||||
pub fn verify_and_optimize_justification<Header: HeaderT>(
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
context: &JustificationVerificationContext,
|
||||
justification: &mut GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
let mut optimizer = JustificationOptimizer {
|
||||
votes: BTreeSet::new(),
|
||||
extra_precommits: vec![],
|
||||
duplicate_votes_ancestries_idxs: vec![],
|
||||
redundant_votes_ancestries: Default::default(),
|
||||
};
|
||||
optimizer.verify_justification(finalized_target, context, justification)?;
|
||||
optimizer.optimize(justification);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for checking if GRANDPA Finality Proofs are valid and optimal.
|
||||
|
||||
use crate::justification::{
|
||||
verification::{Error, JustificationVerifier, PrecommitError},
|
||||
GrandpaJustification,
|
||||
};
|
||||
|
||||
use crate::justification::verification::{
|
||||
IterationFlow, JustificationVerificationContext, SignedPrecommit,
|
||||
};
|
||||
use pezsp_consensus_grandpa::AuthorityId;
|
||||
use pezsp_runtime::traits::Header as HeaderT;
|
||||
use pezsp_std::{collections::btree_set::BTreeSet, vec::Vec};
|
||||
|
||||
/// Verification callbacks that reject all unknown, duplicate or redundant votes.
|
||||
struct StrictJustificationVerifier {
|
||||
votes: BTreeSet<AuthorityId>,
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> JustificationVerifier<Header> for StrictJustificationVerifier {
|
||||
fn process_duplicate_votes_ancestries(
|
||||
&mut self,
|
||||
_duplicate_votes_ancestries: Vec<usize>,
|
||||
) -> Result<(), Error> {
|
||||
Err(Error::DuplicateVotesAncestries)
|
||||
}
|
||||
|
||||
fn process_redundant_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
Err(PrecommitError::RedundantAuthorityVote)
|
||||
}
|
||||
|
||||
fn process_known_authority_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
signed: &SignedPrecommit<Header>,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
if self.votes.contains(&signed.id) {
|
||||
// There's a lot of code in `validate_commit` and `import_precommit` functions
|
||||
// inside `finality-grandpa` crate (mostly related to reporting equivocations).
|
||||
// But the only thing that we care about is that only first vote from the
|
||||
// authority is accepted
|
||||
return Err(PrecommitError::DuplicateAuthorityVote);
|
||||
}
|
||||
|
||||
Ok(IterationFlow::Run)
|
||||
}
|
||||
|
||||
fn process_unknown_authority_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
Err(PrecommitError::UnknownAuthorityVote)
|
||||
}
|
||||
|
||||
fn process_unrelated_ancestry_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<IterationFlow, PrecommitError> {
|
||||
Err(PrecommitError::UnrelatedAncestryVote)
|
||||
}
|
||||
|
||||
fn process_invalid_signature_vote(
|
||||
&mut self,
|
||||
_precommit_idx: usize,
|
||||
) -> Result<(), PrecommitError> {
|
||||
Err(PrecommitError::InvalidAuthoritySignature)
|
||||
}
|
||||
|
||||
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>) {
|
||||
self.votes.insert(signed.id.clone());
|
||||
}
|
||||
|
||||
fn process_redundant_votes_ancestries(
|
||||
&mut self,
|
||||
_redundant_votes_ancestries: BTreeSet<Header::Hash>,
|
||||
) -> Result<(), Error> {
|
||||
Err(Error::RedundantVotesAncestries)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that justification, that is generated by given authority set, finalizes given header.
|
||||
pub fn verify_justification<Header: HeaderT>(
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
context: &JustificationVerificationContext,
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> Result<(), Error> {
|
||||
let mut verifier = StrictJustificationVerifier { votes: BTreeSet::new() };
|
||||
verifier.verify_justification(finalized_target, context, justification)
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines traits which represent a common interface for Bizinikiwi pallets which want to
|
||||
//! incorporate bridge functionality.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use crate::justification::{
|
||||
GrandpaJustification, JustificationVerificationContext, JustificationVerificationError,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
BasicOperatingMode, BlockNumberOf, Chain, HashOf, HasherOf, HeaderOf, RawStorageProof,
|
||||
StorageProofChecker, StorageProofError, UnderlyingChainProvider,
|
||||
};
|
||||
use codec::{Codec, Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen};
|
||||
use core::{clone::Clone, cmp::Eq, default::Default, fmt::Debug};
|
||||
use pezframe_support::PalletError;
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pezsp_consensus_grandpa::{
|
||||
AuthorityList, ConsensusLog, ScheduledChange, SetId, GRANDPA_ENGINE_ID,
|
||||
};
|
||||
use pezsp_runtime::{traits::Header as HeaderT, Digest, RuntimeDebug, SaturatedConversion};
|
||||
use pezsp_std::{boxed::Box, vec::Vec};
|
||||
|
||||
pub use call_info::{BridgeGrandpaCall, BridgeGrandpaCallOf, SubmitFinalityProofInfo};
|
||||
|
||||
mod call_info;
|
||||
|
||||
pub mod justification;
|
||||
pub mod storage_keys;
|
||||
|
||||
/// Header chain error.
|
||||
#[derive(
|
||||
Clone, Decode, DecodeWithMemTracking, Encode, Eq, PartialEq, PalletError, Debug, TypeInfo,
|
||||
)]
|
||||
pub enum HeaderChainError {
|
||||
/// Header with given hash is missing from the chain.
|
||||
UnknownHeader,
|
||||
/// Error generated by the `storage_proof` module.
|
||||
StorageProof(StorageProofError),
|
||||
}
|
||||
|
||||
/// Header data that we're storing on-chain.
|
||||
///
|
||||
/// Even though we may store full header, our applications (XCM) only use couple of header
|
||||
/// fields. Extracting those values makes on-chain storage and PoV smaller, which is good.
|
||||
#[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct StoredHeaderData<Number, Hash> {
|
||||
/// Header number.
|
||||
pub number: Number,
|
||||
/// Header state root.
|
||||
pub state_root: Hash,
|
||||
}
|
||||
|
||||
/// Stored header data builder.
|
||||
pub trait StoredHeaderDataBuilder<Number, Hash> {
|
||||
/// Build header data from self.
|
||||
fn build(&self) -> StoredHeaderData<Number, Hash>;
|
||||
}
|
||||
|
||||
impl<H: HeaderT> StoredHeaderDataBuilder<H::Number, H::Hash> for H {
|
||||
fn build(&self) -> StoredHeaderData<H::Number, H::Hash> {
|
||||
StoredHeaderData { number: *self.number(), state_root: *self.state_root() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Bizinikiwi header chain, abstracted from the way it is stored.
|
||||
pub trait HeaderChain<C: Chain> {
|
||||
/// Returns state (storage) root of given finalized header.
|
||||
fn finalized_header_state_root(header_hash: HashOf<C>) -> Option<HashOf<C>>;
|
||||
|
||||
/// Get storage proof checker using finalized header.
|
||||
fn verify_storage_proof(
|
||||
header_hash: HashOf<C>,
|
||||
storage_proof: RawStorageProof,
|
||||
) -> Result<StorageProofChecker<HasherOf<C>>, HeaderChainError> {
|
||||
let state_root = Self::finalized_header_state_root(header_hash)
|
||||
.ok_or(HeaderChainError::UnknownHeader)?;
|
||||
StorageProofChecker::new(state_root, storage_proof).map_err(HeaderChainError::StorageProof)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that can be used as a parameter in a dispatchable function.
|
||||
///
|
||||
/// When using `decl_module` all arguments for call functions must implement this trait.
|
||||
pub trait Parameter: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {}
|
||||
impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {}
|
||||
|
||||
/// A GRANDPA Authority List and ID.
|
||||
#[derive(
|
||||
Default, Encode, Eq, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Clone, TypeInfo,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct AuthoritySet {
|
||||
/// List of GRANDPA authorities for the current round.
|
||||
pub authorities: AuthorityList,
|
||||
/// Monotonic identifier of the current GRANDPA authority set.
|
||||
pub set_id: SetId,
|
||||
}
|
||||
|
||||
impl AuthoritySet {
|
||||
/// Create a new GRANDPA Authority Set.
|
||||
pub fn new(authorities: AuthorityList, set_id: SetId) -> Self {
|
||||
Self { authorities, set_id }
|
||||
}
|
||||
}
|
||||
|
||||
/// Data required for initializing the GRANDPA bridge pezpallet.
|
||||
///
|
||||
/// The bridge needs to know where to start its sync from, and this provides that initial context.
|
||||
#[derive(
|
||||
Default,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
RuntimeDebug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
TypeInfo,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct InitializationData<H: HeaderT> {
|
||||
/// The header from which we should start syncing.
|
||||
pub header: Box<H>,
|
||||
/// The initial authorities of the pezpallet.
|
||||
pub authority_list: AuthorityList,
|
||||
/// The ID of the initial authority set.
|
||||
pub set_id: SetId,
|
||||
/// Pezpallet operating mode.
|
||||
pub operating_mode: BasicOperatingMode,
|
||||
}
|
||||
|
||||
/// Abstract finality proof that is justifying block finality.
|
||||
pub trait FinalityProof<Hash, Number>: Clone + Send + Sync + Debug {
|
||||
/// Return hash of header that this proof is generated for.
|
||||
fn target_header_hash(&self) -> Hash;
|
||||
|
||||
/// Return number of header that this proof is generated for.
|
||||
fn target_header_number(&self) -> Number;
|
||||
}
|
||||
|
||||
/// A trait that provides helper methods for querying the consensus log.
|
||||
pub trait ConsensusLogReader {
|
||||
/// Returns true if digest contains item that schedules authorities set change.
|
||||
fn schedules_authorities_change(digest: &Digest) -> bool;
|
||||
}
|
||||
|
||||
/// A struct that provides helper methods for querying the GRANDPA consensus log.
|
||||
pub struct GrandpaConsensusLogReader<Number>(pezsp_std::marker::PhantomData<Number>);
|
||||
|
||||
impl<Number: Codec> GrandpaConsensusLogReader<Number> {
|
||||
/// Find and return scheduled (regular) change digest item.
|
||||
pub fn find_scheduled_change(digest: &Digest) -> Option<ScheduledChange<Number>> {
|
||||
use pezsp_runtime::generic::OpaqueDigestItemId;
|
||||
let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
|
||||
|
||||
let filter_log = |log: ConsensusLog<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.
|
||||
digest.convert_first(|l| l.try_to(id).and_then(filter_log))
|
||||
}
|
||||
|
||||
/// Find and return forced change digest item. Or light client can't do anything
|
||||
/// with forced changes, so we can't accept header with the forced change digest.
|
||||
pub fn find_forced_change(digest: &Digest) -> Option<(Number, ScheduledChange<Number>)> {
|
||||
// find the first consensus digest with the right ID which converts to
|
||||
// the right kind of consensus log.
|
||||
digest
|
||||
.convert_first(|log| log.consensus_try_to(&GRANDPA_ENGINE_ID))
|
||||
.and_then(|log| match log {
|
||||
ConsensusLog::ForcedChange(delay, change) => Some((delay, change)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Number: Codec> ConsensusLogReader for GrandpaConsensusLogReader<Number> {
|
||||
fn schedules_authorities_change(digest: &Digest) -> bool {
|
||||
GrandpaConsensusLogReader::<Number>::find_scheduled_change(digest).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// The finality-related info associated to a header.
|
||||
#[derive(Encode, Decode, DecodeWithMemTracking, Debug, PartialEq, Clone, TypeInfo)]
|
||||
pub struct HeaderFinalityInfo<FinalityProof, FinalityVerificationContext> {
|
||||
/// The header finality proof.
|
||||
pub finality_proof: FinalityProof,
|
||||
/// The new verification context introduced by the header.
|
||||
pub new_verification_context: Option<FinalityVerificationContext>,
|
||||
}
|
||||
|
||||
/// Grandpa-related info associated to a header. This info can be saved to events.
|
||||
pub type StoredHeaderGrandpaInfo<Header> =
|
||||
HeaderFinalityInfo<GrandpaJustification<Header>, AuthoritySet>;
|
||||
|
||||
/// Processed Grandpa-related info associated to a header.
|
||||
pub type HeaderGrandpaInfo<Header> =
|
||||
HeaderFinalityInfo<GrandpaJustification<Header>, JustificationVerificationContext>;
|
||||
|
||||
impl<Header: HeaderT> TryFrom<StoredHeaderGrandpaInfo<Header>> for HeaderGrandpaInfo<Header> {
|
||||
type Error = JustificationVerificationError;
|
||||
|
||||
fn try_from(grandpa_info: StoredHeaderGrandpaInfo<Header>) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
finality_proof: grandpa_info.finality_proof,
|
||||
new_verification_context: match grandpa_info.new_verification_context {
|
||||
Some(authority_set) => Some(authority_set.try_into()?),
|
||||
None => None,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for finding equivocations in finality proofs.
|
||||
pub trait FindEquivocations<FinalityProof, FinalityVerificationContext, EquivocationProof> {
|
||||
/// The type returned when encountering an error while looking for equivocations.
|
||||
type Error: Debug;
|
||||
|
||||
/// Find equivocations.
|
||||
fn find_equivocations(
|
||||
verification_context: &FinalityVerificationContext,
|
||||
synced_proof: &FinalityProof,
|
||||
source_proofs: &[FinalityProof],
|
||||
) -> Result<Vec<EquivocationProof>, Self::Error>;
|
||||
}
|
||||
|
||||
/// Bizinikiwi-based chain that is using direct GRANDPA finality.
|
||||
///
|
||||
/// Keep in mind that teyrchains are relying on relay chain GRANDPA, so they should not implement
|
||||
/// this trait.
|
||||
pub trait ChainWithGrandpa: Chain {
|
||||
/// Name of the bridge GRANDPA pezpallet (used in `construct_runtime` macro call) that is deployed
|
||||
/// at some other chain to bridge with this `ChainWithGrandpa`.
|
||||
///
|
||||
/// We assume that all chains that are bridging with this `ChainWithGrandpa` are using
|
||||
/// the same name.
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str;
|
||||
|
||||
/// Max number of GRANDPA authorities at the chain.
|
||||
///
|
||||
/// This is a strict constant. If bridged chain will have more authorities than that,
|
||||
/// the GRANDPA bridge pezpallet may halt.
|
||||
const MAX_AUTHORITIES_COUNT: u32;
|
||||
|
||||
/// Max reasonable number of headers in `votes_ancestries` vector of the GRANDPA justification.
|
||||
///
|
||||
/// This isn't a strict limit. The relay may submit justifications with more headers in its
|
||||
/// ancestry and the pezpallet will accept such justification. The limit is only used to compute
|
||||
/// maximal refund amount and submitting justifications which exceed the limit, may be costly
|
||||
/// to submitter.
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32;
|
||||
|
||||
/// Maximal size of the mandatory chain header. Mandatory header is the header that enacts new
|
||||
/// GRANDPA authorities set (so it has large digest inside).
|
||||
///
|
||||
/// This isn't a strict limit. The relay may submit larger headers and the pezpallet will accept
|
||||
/// the call. The limit is only used to compute maximal refund amount and doing calls which
|
||||
/// exceed the limit, may be costly to submitter.
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32;
|
||||
|
||||
/// Average size of the chain header. We don't expect to see there headers that change GRANDPA
|
||||
/// authorities set (GRANDPA will probably be able to finalize at least one additional header
|
||||
/// per session on non test chains), so this is average size of headers that aren't changing the
|
||||
/// set.
|
||||
///
|
||||
/// This isn't a strict limit. The relay may submit justifications with larger headers and the
|
||||
/// pezpallet will accept the call. However, if the total size of all `submit_finality_proof`
|
||||
/// arguments exceeds the maximal size, computed using this average size, relayer will only get
|
||||
/// partial refund.
|
||||
///
|
||||
/// We expect some headers on production chains that are above this size. But they are rare and
|
||||
/// if rellayer cares about its profitability, we expect it'll select other headers for
|
||||
/// submission.
|
||||
const AVERAGE_HEADER_SIZE: u32;
|
||||
}
|
||||
|
||||
impl<T> ChainWithGrandpa for T
|
||||
where
|
||||
T: Chain + UnderlyingChainProvider,
|
||||
T::Chain: ChainWithGrandpa,
|
||||
{
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str =
|
||||
<T::Chain as ChainWithGrandpa>::WITH_CHAIN_GRANDPA_PALLET_NAME;
|
||||
const MAX_AUTHORITIES_COUNT: u32 = <T::Chain as ChainWithGrandpa>::MAX_AUTHORITIES_COUNT;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 =
|
||||
<T::Chain as ChainWithGrandpa>::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 =
|
||||
<T::Chain as ChainWithGrandpa>::MAX_MANDATORY_HEADER_SIZE;
|
||||
const AVERAGE_HEADER_SIZE: u32 = <T::Chain as ChainWithGrandpa>::AVERAGE_HEADER_SIZE;
|
||||
}
|
||||
|
||||
/// Result of checking maximal expected submit finality proof call weight and size.
|
||||
#[derive(Debug)]
|
||||
pub struct SubmitFinalityProofCallExtras {
|
||||
/// If true, the call weight is larger than what we have assumed.
|
||||
///
|
||||
/// We have some assumptions about headers and justifications of the bridged chain.
|
||||
/// We know that if our assumptions are correct, then the call must not have the
|
||||
/// weight above some limit. The fee paid for weight above that limit, is never refunded.
|
||||
pub is_weight_limit_exceeded: bool,
|
||||
/// Extra size (in bytes) that we assume are included in the call.
|
||||
///
|
||||
/// We have some assumptions about headers and justifications of the bridged chain.
|
||||
/// We know that if our assumptions are correct, then the call must not have the
|
||||
/// weight above some limit. The fee paid for bytes above that limit, is never refunded.
|
||||
pub extra_size: u32,
|
||||
/// A flag that is true if the header is the mandatory header that enacts new
|
||||
/// authorities set.
|
||||
pub is_mandatory_finality_target: bool,
|
||||
}
|
||||
|
||||
/// Checks whether the given `header` and its finality `proof` fit the maximal expected
|
||||
/// call limits (size and weight). The submission may be refunded sometimes (see pezpallet
|
||||
/// configuration for details), but it should fit some limits. If the call has some extra
|
||||
/// weight and/or size included, though, we won't refund it or refund will be partial.
|
||||
pub fn submit_finality_proof_limits_extras<C: ChainWithGrandpa>(
|
||||
header: &C::Header,
|
||||
proof: &justification::GrandpaJustification<C::Header>,
|
||||
) -> SubmitFinalityProofCallExtras {
|
||||
// the `submit_finality_proof` call will reject justifications with invalid, duplicate,
|
||||
// unknown and extra signatures. It'll also reject justifications with less than necessary
|
||||
// signatures. So we do not care about extra weight because of additional signatures here.
|
||||
let precommits_len = proof.commit.precommits.len().saturated_into();
|
||||
let required_precommits = precommits_len;
|
||||
|
||||
// the weight check is simple - we assume that there are no more than the `limit`
|
||||
// headers in the ancestry proof
|
||||
let votes_ancestries_len: u32 = proof.votes_ancestries.len().saturated_into();
|
||||
let is_weight_limit_exceeded =
|
||||
votes_ancestries_len > C::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY;
|
||||
|
||||
// check if the `finality_target` is a mandatory header. If so, we are ready to refund larger
|
||||
// size
|
||||
let is_mandatory_finality_target =
|
||||
GrandpaConsensusLogReader::<BlockNumberOf<C>>::find_scheduled_change(header.digest())
|
||||
.is_some();
|
||||
|
||||
// we can estimate extra call size easily, without any additional significant overhead
|
||||
let actual_call_size: u32 =
|
||||
header.encoded_size().saturating_add(proof.encoded_size()).saturated_into();
|
||||
let max_expected_call_size = max_expected_submit_finality_proof_arguments_size::<C>(
|
||||
is_mandatory_finality_target,
|
||||
required_precommits,
|
||||
);
|
||||
let extra_size = actual_call_size.saturating_sub(max_expected_call_size);
|
||||
|
||||
SubmitFinalityProofCallExtras {
|
||||
is_weight_limit_exceeded,
|
||||
extra_size,
|
||||
is_mandatory_finality_target,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns maximal expected size of `submit_finality_proof` call arguments.
|
||||
pub fn max_expected_submit_finality_proof_arguments_size<C: ChainWithGrandpa>(
|
||||
is_mandatory_finality_target: bool,
|
||||
precommits: u32,
|
||||
) -> u32 {
|
||||
let max_expected_justification_size =
|
||||
GrandpaJustification::<HeaderOf<C>>::max_reasonable_size::<C>(precommits);
|
||||
|
||||
// call arguments are header and justification
|
||||
let max_expected_finality_target_size = if is_mandatory_finality_target {
|
||||
C::MAX_MANDATORY_HEADER_SIZE
|
||||
} else {
|
||||
C::AVERAGE_HEADER_SIZE
|
||||
};
|
||||
max_expected_finality_target_size.saturating_add(max_expected_justification_size)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezbp_runtime::ChainId;
|
||||
use pezframe_support::weights::Weight;
|
||||
use pezsp_runtime::{
|
||||
testing::H256, traits::BlakeTwo256, DigestItem, MultiSignature, StateVersion,
|
||||
};
|
||||
|
||||
struct TestChain;
|
||||
|
||||
impl Chain for TestChain {
|
||||
const ID: ChainId = *b"test";
|
||||
|
||||
type BlockNumber = u32;
|
||||
type Hash = H256;
|
||||
type Hasher = BlakeTwo256;
|
||||
type Header = pezsp_runtime::generic::Header<u32, BlakeTwo256>;
|
||||
type AccountId = u64;
|
||||
type Balance = u64;
|
||||
type Nonce = u64;
|
||||
type Signature = MultiSignature;
|
||||
|
||||
const STATE_VERSION: StateVersion = StateVersion::V1;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
0
|
||||
}
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainWithGrandpa for TestChain {
|
||||
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "Test";
|
||||
const MAX_AUTHORITIES_COUNT: u32 = 128;
|
||||
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 2;
|
||||
const MAX_MANDATORY_HEADER_SIZE: u32 = 100_000;
|
||||
const AVERAGE_HEADER_SIZE: u32 = 1_024;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_expected_submit_finality_proof_arguments_size_respects_mandatory_argument() {
|
||||
assert!(
|
||||
max_expected_submit_finality_proof_arguments_size::<TestChain>(true, 100) >
|
||||
max_expected_submit_finality_proof_arguments_size::<TestChain>(false, 100),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_scheduled_change_works() {
|
||||
let scheduled_change = ScheduledChange { next_authorities: vec![], delay: 0 };
|
||||
|
||||
// first
|
||||
let mut digest = Digest::default();
|
||||
digest.push(DigestItem::Consensus(
|
||||
GRANDPA_ENGINE_ID,
|
||||
ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(),
|
||||
));
|
||||
assert_eq!(
|
||||
GrandpaConsensusLogReader::find_scheduled_change(&digest),
|
||||
Some(scheduled_change.clone())
|
||||
);
|
||||
|
||||
// not first
|
||||
let mut digest = Digest::default();
|
||||
digest.push(DigestItem::Consensus(
|
||||
GRANDPA_ENGINE_ID,
|
||||
ConsensusLog::<u64>::OnDisabled(0).encode(),
|
||||
));
|
||||
digest.push(DigestItem::Consensus(
|
||||
GRANDPA_ENGINE_ID,
|
||||
ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(),
|
||||
));
|
||||
assert_eq!(
|
||||
GrandpaConsensusLogReader::find_scheduled_change(&digest),
|
||||
Some(scheduled_change.clone())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Storage keys of bridge GRANDPA pezpallet.
|
||||
|
||||
/// Name of the `IsHalted` storage value.
|
||||
pub const PALLET_OPERATING_MODE_VALUE_NAME: &str = "PalletOperatingMode";
|
||||
/// Name of the `BestFinalized` storage value.
|
||||
pub const BEST_FINALIZED_VALUE_NAME: &str = "BestFinalized";
|
||||
/// Name of the `CurrentAuthoritySet` storage value.
|
||||
pub const CURRENT_AUTHORITY_SET_VALUE_NAME: &str = "CurrentAuthoritySet";
|
||||
|
||||
use pezsp_core::storage::StorageKey;
|
||||
|
||||
/// Storage key of the `PalletOperatingMode` variable in the runtime storage.
|
||||
pub fn pezpallet_operating_mode_key(pezpallet_prefix: &str) -> StorageKey {
|
||||
StorageKey(
|
||||
pezbp_runtime::storage_value_final_key(
|
||||
pezpallet_prefix.as_bytes(),
|
||||
PALLET_OPERATING_MODE_VALUE_NAME.as_bytes(),
|
||||
)
|
||||
.to_vec(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Storage key of the `CurrentAuthoritySet` variable in the runtime storage.
|
||||
pub fn current_authority_set_key(pezpallet_prefix: &str) -> StorageKey {
|
||||
StorageKey(
|
||||
pezbp_runtime::storage_value_final_key(
|
||||
pezpallet_prefix.as_bytes(),
|
||||
CURRENT_AUTHORITY_SET_VALUE_NAME.as_bytes(),
|
||||
)
|
||||
.to_vec(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Storage key of the best finalized header number and hash value in the runtime storage.
|
||||
pub fn best_finalized_key(pezpallet_prefix: &str) -> StorageKey {
|
||||
StorageKey(
|
||||
pezbp_runtime::storage_value_final_key(
|
||||
pezpallet_prefix.as_bytes(),
|
||||
BEST_FINALIZED_VALUE_NAME.as_bytes(),
|
||||
)
|
||||
.to_vec(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use hex_literal::hex;
|
||||
|
||||
#[test]
|
||||
fn pezpallet_operating_mode_key_computed_properly() {
|
||||
// If this test fails, then something has been changed in module storage that is breaking
|
||||
// compatibility with previous pezpallet.
|
||||
let storage_key = pezpallet_operating_mode_key("BridgeGrandpa").0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("0b06f475eddb98cf933a12262e0388de0f4cf0917788d791142ff6c1f216e7b3").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_authority_set_key_computed_properly() {
|
||||
// If this test fails, then something has been changed in module storage that is breaking
|
||||
// compatibility with previous pezpallet.
|
||||
let storage_key = current_authority_set_key("BridgeGrandpa").0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("0b06f475eddb98cf933a12262e0388de24a7b8b5717ea33346fa595a66ccbcb0").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn best_finalized_key_computed_properly() {
|
||||
// If this test fails, then something has been changed in module storage that is breaking
|
||||
// compatibility with previous pezpallet.
|
||||
let storage_key = best_finalized_key("BridgeGrandpa").0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("0b06f475eddb98cf933a12262e0388dea4ebafdd473c549fdb24c5c991c5591c").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests inside this module are made to ensure that our custom justification verification
|
||||
//! implementation works similar to the [`finality_grandpa::validate_commit`] and explicitly
|
||||
//! show where we behave different.
|
||||
//!
|
||||
//! Some of tests in this module may partially duplicate tests from `justification.rs`,
|
||||
//! but their purpose is different.
|
||||
|
||||
use bp_header_pez_chain::justification::{
|
||||
verify_justification, GrandpaJustification, JustificationVerificationContext,
|
||||
JustificationVerificationError, PrecommitError,
|
||||
};
|
||||
use bp_test_utils::{
|
||||
header_id, make_justification_for_header, signed_precommit, test_header, Account,
|
||||
JustificationGeneratorParams, ALICE, BOB, CHARLIE, DAVE, EVE, FERDIE, TEST_GRANDPA_SET_ID,
|
||||
};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthorityWeight, SetId};
|
||||
use pezsp_runtime::traits::Header as HeaderT;
|
||||
|
||||
type TestHeader = pezsp_runtime::testing::Header;
|
||||
type TestHash = <TestHeader as HeaderT>::Hash;
|
||||
type TestNumber = <TestHeader as HeaderT>::Number;
|
||||
|
||||
/// Implementation of `finality_grandpa::Chain` that is used in tests.
|
||||
struct AncestryChain(bp_header_pez_chain::justification::AncestryChain<TestHeader>);
|
||||
|
||||
impl AncestryChain {
|
||||
fn new(justification: &GrandpaJustification<TestHeader>) -> Self {
|
||||
Self(bp_header_pez_chain::justification::AncestryChain::new(justification).0)
|
||||
}
|
||||
}
|
||||
|
||||
impl finality_grandpa::Chain<TestHash, TestNumber> for AncestryChain {
|
||||
fn ancestry(
|
||||
&self,
|
||||
base: TestHash,
|
||||
block: TestHash,
|
||||
) -> Result<Vec<TestHash>, finality_grandpa::Error> {
|
||||
let mut route = Vec::new();
|
||||
let mut current_hash = block;
|
||||
loop {
|
||||
if current_hash == base {
|
||||
break;
|
||||
}
|
||||
match self.0.parent_hash_of(¤t_hash) {
|
||||
Some(parent_hash) => {
|
||||
current_hash = *parent_hash;
|
||||
route.push(current_hash);
|
||||
},
|
||||
_ => return Err(finality_grandpa::Error::NotDescendent),
|
||||
}
|
||||
}
|
||||
route.pop(); // remove the base
|
||||
|
||||
Ok(route)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a full set of accounts.
|
||||
fn full_accounts_set() -> Vec<(Account, AuthorityWeight)> {
|
||||
vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1), (EVE, 1)]
|
||||
}
|
||||
|
||||
/// Get a full set of GRANDPA authorities.
|
||||
fn full_voter_set() -> VoterSet<AuthorityId> {
|
||||
VoterSet::new(full_accounts_set().iter().map(|(id, w)| (AuthorityId::from(*id), *w))).unwrap()
|
||||
}
|
||||
|
||||
pub fn full_verification_context(set_id: SetId) -> JustificationVerificationContext {
|
||||
let voter_set = full_voter_set();
|
||||
JustificationVerificationContext { voter_set, authority_set_id: set_id }
|
||||
}
|
||||
|
||||
/// Get a minimal set of accounts.
|
||||
fn minimal_accounts_set() -> Vec<(Account, AuthorityWeight)> {
|
||||
// there are 5 accounts in the full set => we need 2/3 + 1 accounts, which results in 4 accounts
|
||||
vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1)]
|
||||
}
|
||||
|
||||
/// Make a valid GRANDPA justification with sensible defaults.
|
||||
pub fn make_default_justification(header: &TestHeader) -> GrandpaJustification<TestHeader> {
|
||||
make_justification_for_header(JustificationGeneratorParams {
|
||||
header: header.clone(),
|
||||
authorities: minimal_accounts_set(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
// the `finality_grandpa::validate_commit` function has two ways to report an unsuccessful
|
||||
// commit validation:
|
||||
//
|
||||
// 1) to return `Err()` (which only may happen if `finality_grandpa::Chain` implementation returns
|
||||
// an error);
|
||||
// 2) to return `Ok(validation_result)` if `validation_result.is_valid()` is false.
|
||||
//
|
||||
// Our implementation would just return error in both cases.
|
||||
|
||||
#[test]
|
||||
fn same_result_when_precommit_target_has_lower_number_than_commit_target() {
|
||||
let mut justification = make_default_justification(&test_header(1));
|
||||
// the number of header in precommit (0) is lower than number of header in commit (1)
|
||||
justification.commit.precommits[0].precommit.target_number = 0;
|
||||
|
||||
// our implementation returns an error
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)),
|
||||
);
|
||||
|
||||
// original implementation returns `Ok(validation_result)`
|
||||
// with `validation_result.is_valid() == false`.
|
||||
let result = finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(!result.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_result_when_precommit_target_is_not_descendant_of_commit_target() {
|
||||
let not_descendant = test_header::<TestHeader>(10);
|
||||
let mut justification = make_default_justification(&test_header(1));
|
||||
// the route from header of commit (1) to header of precommit (10) is missing from
|
||||
// the votes ancestries
|
||||
justification.commit.precommits[0].precommit.target_number = *not_descendant.number();
|
||||
justification.commit.precommits[0].precommit.target_hash = not_descendant.hash();
|
||||
justification.votes_ancestries.push(not_descendant);
|
||||
|
||||
// our implementation returns an error
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)),
|
||||
);
|
||||
|
||||
// original implementation returns `Ok(validation_result)`
|
||||
// with `validation_result.is_valid() == false`.
|
||||
let result = finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(!result.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_result_when_there_are_not_enough_cumulative_weight_to_finalize_commit_target() {
|
||||
// just remove one authority from the minimal set and we shall not reach the threshold
|
||||
let mut authorities_set = minimal_accounts_set();
|
||||
authorities_set.pop();
|
||||
let justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
authorities: authorities_set,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// our implementation returns an error
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::TooLowCumulativeWeight),
|
||||
);
|
||||
// original implementation returns `Ok(validation_result)`
|
||||
// with `validation_result.is_valid() == false`.
|
||||
let result = finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(!result.is_valid());
|
||||
}
|
||||
|
||||
// tests below are our differences with the original implementation
|
||||
|
||||
#[test]
|
||||
fn different_result_when_justification_contains_duplicate_vote() {
|
||||
let mut justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
authorities: minimal_accounts_set(),
|
||||
ancestors: 0,
|
||||
..Default::default()
|
||||
});
|
||||
// the justification may contain exactly the same vote (i.e. same precommit and same signature)
|
||||
// multiple times && it isn't treated as an error by original implementation
|
||||
let last_precommit = justification.commit.precommits.pop().unwrap();
|
||||
justification.commit.precommits.push(justification.commit.precommits[0].clone());
|
||||
justification.commit.precommits.push(last_precommit);
|
||||
|
||||
// our implementation fails
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
|
||||
);
|
||||
// original implementation returns `Ok(validation_result)`
|
||||
// with `validation_result.is_valid() == true`.
|
||||
let result = finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(result.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_results_when_authority_equivocates_once_in_a_round() {
|
||||
let mut justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
authorities: minimal_accounts_set(),
|
||||
ancestors: 0,
|
||||
..Default::default()
|
||||
});
|
||||
// the justification original implementation allows authority to submit two different
|
||||
// votes in a single round, of which only first is 'accepted'
|
||||
let last_precommit = justification.commit.precommits.pop().unwrap();
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&ALICE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
justification.commit.precommits.push(last_precommit);
|
||||
|
||||
// our implementation fails
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
|
||||
);
|
||||
// original implementation returns `Ok(validation_result)`
|
||||
// with `validation_result.is_valid() == true`.
|
||||
let result = finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(result.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_results_when_authority_equivocates_twice_in_a_round() {
|
||||
let mut justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
authorities: minimal_accounts_set(),
|
||||
ancestors: 0,
|
||||
..Default::default()
|
||||
});
|
||||
// there's some code in the original implementation that should return an error when
|
||||
// same authority submits more than two different votes in a single round:
|
||||
// https://github.com/paritytech/finality-grandpa/blob/6aeea2d1159d0f418f0b86e70739f2130629ca09/src/lib.rs#L473
|
||||
// but there's also a code that prevents this from happening:
|
||||
// https://github.com/paritytech/finality-grandpa/blob/6aeea2d1159d0f418f0b86e70739f2130629ca09/src/round.rs#L287
|
||||
// => so now we are also just ignoring all votes from the same authority, except the first one
|
||||
let last_precommit = justification.commit.precommits.pop().unwrap();
|
||||
let prev_last_precommit = justification.commit.precommits.pop().unwrap();
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&ALICE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&ALICE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
justification.commit.precommits.push(last_precommit);
|
||||
justification.commit.precommits.push(prev_last_precommit);
|
||||
|
||||
// our implementation fails
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
|
||||
);
|
||||
// original implementation returns `Ok(validation_result)`
|
||||
// with `validation_result.is_valid() == true`.
|
||||
let result = finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(result.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_results_when_there_are_more_than_enough_votes() {
|
||||
let mut justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
authorities: minimal_accounts_set(),
|
||||
ancestors: 0,
|
||||
..Default::default()
|
||||
});
|
||||
// the reference implementation just keep verifying signatures even if we have
|
||||
// collected enough votes. We are not
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&EVE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
|
||||
// our implementation fails
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::RedundantAuthorityVote)),
|
||||
);
|
||||
// original implementation returns `Ok(validation_result)`
|
||||
// with `validation_result.is_valid() == true`.
|
||||
let result = finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(result.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_results_when_there_is_a_vote_of_unknown_authority() {
|
||||
let mut justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
authorities: minimal_accounts_set(),
|
||||
ancestors: 0,
|
||||
..Default::default()
|
||||
});
|
||||
// the reference implementation just keep verifying signatures even if we have
|
||||
// collected enough votes. We are not
|
||||
let last_precommit = justification.commit.precommits.pop().unwrap();
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&FERDIE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
justification.commit.precommits.push(last_precommit);
|
||||
|
||||
// our implementation fails
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&full_verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::UnknownAuthorityVote)),
|
||||
);
|
||||
// original implementation returns `Ok(validation_result)`
|
||||
// with `validation_result.is_valid() == true`.
|
||||
let result = finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(result.is_valid());
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for Grandpa equivocations collector code.
|
||||
|
||||
use bp_header_pez_chain::justification::EquivocationsCollector;
|
||||
use bp_test_utils::*;
|
||||
use finality_grandpa::Precommit;
|
||||
use pezsp_consensus_grandpa::EquivocationProof;
|
||||
|
||||
type TestHeader = pezsp_runtime::testing::Header;
|
||||
|
||||
#[test]
|
||||
fn duplicate_votes_are_not_considered_equivocations() {
|
||||
let verification_context = verification_context(TEST_GRANDPA_SET_ID);
|
||||
let base_justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
|
||||
let mut collector =
|
||||
EquivocationsCollector::new(&verification_context, &base_justification).unwrap();
|
||||
collector.parse_justifications(&[base_justification.clone()]);
|
||||
|
||||
assert_eq!(collector.into_equivocation_proofs().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equivocations_are_detected_in_base_justification_redundant_votes() {
|
||||
let mut base_justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
|
||||
let first_vote = base_justification.commit.precommits[0].clone();
|
||||
let equivocation = signed_precommit::<TestHeader>(
|
||||
&ALICE,
|
||||
header_id::<TestHeader>(1),
|
||||
base_justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
);
|
||||
base_justification.commit.precommits.push(equivocation.clone());
|
||||
|
||||
let verification_context = verification_context(TEST_GRANDPA_SET_ID);
|
||||
let collector =
|
||||
EquivocationsCollector::new(&verification_context, &base_justification).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
collector.into_equivocation_proofs(),
|
||||
vec![EquivocationProof::new(
|
||||
1,
|
||||
pezsp_consensus_grandpa::Equivocation::Precommit(finality_grandpa::Equivocation {
|
||||
round_number: 1,
|
||||
identity: ALICE.into(),
|
||||
first: (
|
||||
Precommit {
|
||||
target_hash: first_vote.precommit.target_hash,
|
||||
target_number: first_vote.precommit.target_number
|
||||
},
|
||||
first_vote.signature
|
||||
),
|
||||
second: (
|
||||
Precommit {
|
||||
target_hash: equivocation.precommit.target_hash,
|
||||
target_number: equivocation.precommit.target_number
|
||||
},
|
||||
equivocation.signature
|
||||
)
|
||||
})
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equivocations_are_detected_in_extra_justification_redundant_votes() {
|
||||
let base_justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
let first_vote = base_justification.commit.precommits[0].clone();
|
||||
|
||||
let mut extra_justification = base_justification.clone();
|
||||
let equivocation = signed_precommit::<TestHeader>(
|
||||
&ALICE,
|
||||
header_id::<TestHeader>(1),
|
||||
base_justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
);
|
||||
extra_justification.commit.precommits.push(equivocation.clone());
|
||||
|
||||
let verification_context = verification_context(TEST_GRANDPA_SET_ID);
|
||||
let mut collector =
|
||||
EquivocationsCollector::new(&verification_context, &base_justification).unwrap();
|
||||
collector.parse_justifications(&[extra_justification]);
|
||||
|
||||
assert_eq!(
|
||||
collector.into_equivocation_proofs(),
|
||||
vec![EquivocationProof::new(
|
||||
1,
|
||||
pezsp_consensus_grandpa::Equivocation::Precommit(finality_grandpa::Equivocation {
|
||||
round_number: 1,
|
||||
identity: ALICE.into(),
|
||||
first: (
|
||||
Precommit {
|
||||
target_hash: first_vote.precommit.target_hash,
|
||||
target_number: first_vote.precommit.target_number
|
||||
},
|
||||
first_vote.signature
|
||||
),
|
||||
second: (
|
||||
Precommit {
|
||||
target_hash: equivocation.precommit.target_hash,
|
||||
target_number: equivocation.precommit.target_number
|
||||
},
|
||||
equivocation.signature
|
||||
)
|
||||
})
|
||||
)]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for Grandpa Justification optimizer code.
|
||||
|
||||
use bp_header_pez_chain::justification::verify_and_optimize_justification;
|
||||
use bp_test_utils::*;
|
||||
use finality_grandpa::SignedPrecommit;
|
||||
use pezsp_consensus_grandpa::AuthoritySignature;
|
||||
|
||||
type TestHeader = pezsp_runtime::testing::Header;
|
||||
|
||||
#[test]
|
||||
fn optimizer_does_noting_with_minimal_justification() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
let num_precommits_after = justification.commit.precommits.len();
|
||||
|
||||
assert_eq!(num_precommits_before, num_precommits_after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_authority_votes_are_removed_by_optimizer() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&bp_test_utils::Account(42),
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
let num_precommits_after = justification.commit.precommits.len();
|
||||
|
||||
assert_eq!(num_precommits_before - 1, num_precommits_after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_authority_votes_are_removed_by_optimizer() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification
|
||||
.commit
|
||||
.precommits
|
||||
.push(justification.commit.precommits.first().cloned().unwrap());
|
||||
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
let num_precommits_after = justification.commit.precommits.len();
|
||||
|
||||
assert_eq!(num_precommits_before - 1, num_precommits_after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_authority_signatures_are_removed_by_optimizer() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
|
||||
let target = header_id::<TestHeader>(1);
|
||||
let invalid_raw_signature: Vec<u8> = ALICE.sign(b"").to_bytes().into();
|
||||
justification.commit.precommits.insert(
|
||||
0,
|
||||
SignedPrecommit {
|
||||
precommit: finality_grandpa::Precommit {
|
||||
target_hash: target.0,
|
||||
target_number: target.1,
|
||||
},
|
||||
signature: AuthoritySignature::try_from(invalid_raw_signature).unwrap(),
|
||||
id: ALICE.into(),
|
||||
},
|
||||
);
|
||||
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
let num_precommits_after = justification.commit.precommits.len();
|
||||
|
||||
assert_eq!(num_precommits_before - 1, num_precommits_after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redundant_authority_votes_are_removed_by_optimizer() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&EVE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
let num_precommits_after = justification.commit.precommits.len();
|
||||
|
||||
assert_eq!(num_precommits_before - 1, num_precommits_after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unrelated_ancestry_votes_are_removed_by_optimizer() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(2));
|
||||
justification.commit.precommits.insert(
|
||||
0,
|
||||
signed_precommit::<TestHeader>(
|
||||
&ALICE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
),
|
||||
);
|
||||
|
||||
let num_precommits_before = justification.commit.precommits.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(2),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
let num_precommits_after = justification.commit.precommits.len();
|
||||
|
||||
assert_eq!(num_precommits_before - 1, num_precommits_after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_votes_ancestries_are_removed_by_optimizer() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
let optimized_votes_ancestries = justification.votes_ancestries.clone();
|
||||
justification.votes_ancestries = justification
|
||||
.votes_ancestries
|
||||
.into_iter()
|
||||
.flat_map(|item| std::iter::repeat(item).take(3))
|
||||
.collect();
|
||||
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(justification.votes_ancestries, optimized_votes_ancestries);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redundant_votes_ancestries_are_removed_by_optimizer() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.votes_ancestries.push(test_header(100));
|
||||
|
||||
let num_votes_ancestries_before = justification.votes_ancestries.len();
|
||||
verify_and_optimize_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&mut justification,
|
||||
)
|
||||
.unwrap();
|
||||
let num_votes_ancestries_after = justification.votes_ancestries.len();
|
||||
|
||||
assert_eq!(num_votes_ancestries_before - 1, num_votes_ancestries_after);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for Grandpa strict justification verifier code.
|
||||
|
||||
use bp_header_pez_chain::justification::{
|
||||
required_justification_precommits, verify_justification, JustificationVerificationContext,
|
||||
JustificationVerificationError, PrecommitError,
|
||||
};
|
||||
use bp_test_utils::*;
|
||||
|
||||
type TestHeader = pezsp_runtime::testing::Header;
|
||||
|
||||
#[test]
|
||||
fn valid_justification_accepted() {
|
||||
let authorities = vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1)];
|
||||
let params = JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: authorities.clone(),
|
||||
ancestors: 7,
|
||||
forks: 3,
|
||||
};
|
||||
|
||||
let justification = make_justification_for_header::<TestHeader>(params.clone());
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
assert_eq!(justification.commit.precommits.len(), authorities.len());
|
||||
assert_eq!(justification.votes_ancestries.len(), params.ancestors as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_justification_accepted_with_single_fork() {
|
||||
let params = JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1)],
|
||||
ancestors: 5,
|
||||
forks: 1,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&make_justification_for_header::<TestHeader>(params)
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_justification_accepted_with_arbitrary_number_of_authorities() {
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use pezsp_consensus_grandpa::AuthorityId;
|
||||
|
||||
let n = 15;
|
||||
let required_signatures = required_justification_precommits(n as _);
|
||||
let authorities = accounts(n).iter().map(|k| (*k, 1)).collect::<Vec<_>>();
|
||||
|
||||
let params = JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: authorities.clone().into_iter().take(required_signatures as _).collect(),
|
||||
ancestors: n.into(),
|
||||
forks: required_signatures,
|
||||
};
|
||||
|
||||
let authorities = authorities
|
||||
.iter()
|
||||
.map(|(id, w)| (AuthorityId::from(*id), *w))
|
||||
.collect::<Vec<(AuthorityId, _)>>();
|
||||
let voter_set = VoterSet::new(authorities).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&JustificationVerificationContext { voter_set, authority_set_id: TEST_GRANDPA_SET_ID },
|
||||
&make_justification_for_header::<TestHeader>(params)
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justification_with_invalid_target_rejected() {
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(2),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&make_default_justification::<TestHeader>(&test_header(1)),
|
||||
),
|
||||
Err(JustificationVerificationError::InvalidJustificationTarget),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justification_with_invalid_commit_rejected() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.commit.precommits.clear();
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::TooLowCumulativeWeight),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justification_with_invalid_authority_signature_rejected() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.commit.precommits[0].signature =
|
||||
pezsp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64]);
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::Precommit(PrecommitError::InvalidAuthoritySignature)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justification_with_duplicate_votes_ancestry() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.votes_ancestries.push(justification.votes_ancestries[0].clone());
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::DuplicateVotesAncestries),
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn justification_with_redundant_votes_ancestry() {
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.votes_ancestries.push(test_header(10));
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&justification,
|
||||
),
|
||||
Err(JustificationVerificationError::RedundantVotesAncestries),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justification_is_invalid_if_we_dont_meet_threshold() {
|
||||
// Need at least three authorities to sign off or else the voter set threshold can't be reached
|
||||
let authorities = vec![(ALICE, 1), (BOB, 1)];
|
||||
|
||||
let params = JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: authorities.clone(),
|
||||
ancestors: 2 * authorities.len() as u32,
|
||||
forks: 2,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
&verification_context(TEST_GRANDPA_SET_ID),
|
||||
&make_justification_for_header::<TestHeader>(params)
|
||||
),
|
||||
Err(JustificationVerificationError::TooLowCumulativeWeight),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
mod justification {
|
||||
mod equivocation;
|
||||
mod optimizer;
|
||||
mod strict;
|
||||
}
|
||||
|
||||
mod implementation_match;
|
||||
@@ -0,0 +1,52 @@
|
||||
[package]
|
||||
name = "bp-messages"
|
||||
description = "Primitives of messages module."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-messages"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["bit-vec", "derive"], workspace = true }
|
||||
scale-info = { features = ["bit-vec", "derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = { workspace = true, default-features = true }
|
||||
hex-literal = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines structures related to calls of the `pezpallet-bridge-messages` pezpallet.
|
||||
|
||||
use crate::{MessageNonce, UnrewardedRelayersState};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::weights::Weight;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::RuntimeDebug;
|
||||
use pezsp_std::ops::RangeInclusive;
|
||||
|
||||
/// A minimized version of `pezpallet-bridge-messages::Call` that can be used without a runtime.
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeMessagesCall<AccountId, MessagesProof, MessagesDeliveryProof> {
|
||||
/// `pezpallet-bridge-messages::Call::receive_messages_proof`
|
||||
#[codec(index = 2)]
|
||||
receive_messages_proof {
|
||||
/// Account id of relayer at the **bridged** chain.
|
||||
relayer_id_at_bridged_chain: AccountId,
|
||||
/// Messages proof.
|
||||
proof: MessagesProof,
|
||||
/// A number of messages in the proof.
|
||||
messages_count: u32,
|
||||
/// Total dispatch weight of messages in the proof.
|
||||
dispatch_weight: Weight,
|
||||
},
|
||||
/// `pezpallet-bridge-messages::Call::receive_messages_delivery_proof`
|
||||
#[codec(index = 3)]
|
||||
receive_messages_delivery_proof {
|
||||
/// Messages delivery proof.
|
||||
proof: MessagesDeliveryProof,
|
||||
/// "Digest" of unrewarded relayers state at the bridged chain.
|
||||
relayers_state: UnrewardedRelayersState,
|
||||
},
|
||||
}
|
||||
|
||||
/// Generic info about a messages delivery/confirmation proof.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub struct BaseMessagesProofInfo<LaneId> {
|
||||
/// Message lane, used by the call.
|
||||
pub lane_id: LaneId,
|
||||
/// Nonces of messages, included in the call.
|
||||
///
|
||||
/// For delivery transaction, it is nonces of bundled messages. For confirmation
|
||||
/// transaction, it is nonces that are to be confirmed during the call.
|
||||
pub bundled_range: RangeInclusive<MessageNonce>,
|
||||
/// Nonce of the best message, stored by this chain before the call is dispatched.
|
||||
///
|
||||
/// For delivery transaction, it is the nonce of best delivered message before the call.
|
||||
/// For confirmation transaction, it is the nonce of best confirmed message before the call.
|
||||
pub best_stored_nonce: MessageNonce,
|
||||
}
|
||||
|
||||
impl<LaneId> BaseMessagesProofInfo<LaneId> {
|
||||
/// Returns true if `bundled_range` continues the `0..=best_stored_nonce` range.
|
||||
pub fn appends_to_stored_nonce(&self) -> bool {
|
||||
Some(*self.bundled_range.start()) == self.best_stored_nonce.checked_add(1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Occupation state of the unrewarded relayers vector.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
#[cfg_attr(test, derive(Default))]
|
||||
pub struct UnrewardedRelayerOccupation {
|
||||
/// The number of remaining unoccupied entries for new relayers.
|
||||
pub free_relayer_slots: MessageNonce,
|
||||
/// The number of messages that we are ready to accept.
|
||||
pub free_message_slots: MessageNonce,
|
||||
}
|
||||
|
||||
/// Info about a `ReceiveMessagesProof` call which tries to update a single lane.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub struct ReceiveMessagesProofInfo<LaneId> {
|
||||
/// Base messages proof info
|
||||
pub base: BaseMessagesProofInfo<LaneId>,
|
||||
/// State of unrewarded relayers vector.
|
||||
pub unrewarded_relayers: UnrewardedRelayerOccupation,
|
||||
}
|
||||
|
||||
impl<LaneId> ReceiveMessagesProofInfo<LaneId> {
|
||||
/// Returns true if:
|
||||
///
|
||||
/// - either inbound lane is ready to accept bundled messages;
|
||||
///
|
||||
/// - or there are no bundled messages, but the inbound lane is blocked by too many unconfirmed
|
||||
/// messages and/or unrewarded relayers.
|
||||
pub fn is_obsolete(&self, is_dispatcher_active: bool) -> bool {
|
||||
// if dispatcher is inactive, we don't accept any delivery transactions
|
||||
if !is_dispatcher_active {
|
||||
return true;
|
||||
}
|
||||
|
||||
// transactions with zero bundled nonces are not allowed, unless they're message
|
||||
// delivery transactions, which brings reward confirmations required to unblock
|
||||
// the lane
|
||||
if self.base.bundled_range.is_empty() {
|
||||
let empty_transactions_allowed =
|
||||
// we allow empty transactions when we can't accept delivery from new relayers
|
||||
self.unrewarded_relayers.free_relayer_slots == 0 ||
|
||||
// or if we can't accept new messages at all
|
||||
self.unrewarded_relayers.free_message_slots == 0;
|
||||
|
||||
return !empty_transactions_allowed;
|
||||
}
|
||||
|
||||
// otherwise we require bundled messages to continue stored range
|
||||
!self.base.appends_to_stored_nonce()
|
||||
}
|
||||
}
|
||||
|
||||
/// Info about a `ReceiveMessagesDeliveryProof` call which tries to update a single lane.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub struct ReceiveMessagesDeliveryProofInfo<LaneId>(pub BaseMessagesProofInfo<LaneId>);
|
||||
|
||||
impl<LaneId> ReceiveMessagesDeliveryProofInfo<LaneId> {
|
||||
/// Returns true if outbound lane is ready to accept confirmations of bundled messages.
|
||||
pub fn is_obsolete(&self) -> bool {
|
||||
self.0.bundled_range.is_empty() || !self.0.appends_to_stored_nonce()
|
||||
}
|
||||
}
|
||||
|
||||
/// Info about a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call
|
||||
/// which tries to update a single lane.
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub enum MessagesCallInfo<LaneId: Clone + Copy> {
|
||||
/// Messages delivery call info.
|
||||
ReceiveMessagesProof(ReceiveMessagesProofInfo<LaneId>),
|
||||
/// Messages delivery confirmation call info.
|
||||
ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo<LaneId>),
|
||||
}
|
||||
|
||||
impl<LaneId: Clone + Copy> MessagesCallInfo<LaneId> {
|
||||
/// Returns lane, used by the call.
|
||||
pub fn lane_id(&self) -> LaneId {
|
||||
match *self {
|
||||
Self::ReceiveMessagesProof(ref info) => info.base.lane_id,
|
||||
Self::ReceiveMessagesDeliveryProof(ref info) => info.0.lane_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns range of messages, bundled with the call.
|
||||
pub fn bundled_messages(&self) -> RangeInclusive<MessageNonce> {
|
||||
match *self {
|
||||
Self::ReceiveMessagesProof(ref info) => info.base.bundled_range.clone(),
|
||||
Self::ReceiveMessagesDeliveryProof(ref info) => info.0.bundled_range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of messages module, that represents lane id.
|
||||
|
||||
use codec::{Codec, Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use pezsp_core::{RuntimeDebug, TypeId, H256};
|
||||
use pezsp_io::hashing::blake2_256;
|
||||
use pezsp_std::fmt::Debug;
|
||||
|
||||
/// Trait representing a generic `LaneId` type.
|
||||
pub trait LaneIdType:
|
||||
Clone
|
||||
+ Copy
|
||||
+ Codec
|
||||
+ EncodeLike
|
||||
+ Debug
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ TypeInfo
|
||||
+ MaxEncodedLen
|
||||
+ Serialize
|
||||
+ DeserializeOwned
|
||||
{
|
||||
/// Creates a new `LaneId` type (if supported).
|
||||
fn try_new<E: Ord + Encode>(endpoint1: E, endpoint2: E) -> Result<Self, ()>;
|
||||
}
|
||||
|
||||
/// Bridge lane identifier (legacy).
|
||||
///
|
||||
/// Note: For backwards compatibility reasons, we also handle the older format `[u8; 4]`.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Default,
|
||||
Encode,
|
||||
Eq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
PartialEq,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct LegacyLaneId(pub [u8; 4]);
|
||||
|
||||
impl LaneIdType for LegacyLaneId {
|
||||
/// Create lane identifier from two locations.
|
||||
fn try_new<T: Ord + Encode>(_endpoint1: T, _endpoint2: T) -> Result<Self, ()> {
|
||||
// we don't support this for `LegacyLaneId`, because it was hard-coded before
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl TryFrom<Vec<u8>> for LegacyLaneId {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
if value.len() == 4 {
|
||||
return <[u8; 4]>::try_from(value).map(Self).map_err(|_| ());
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for LegacyLaneId {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
self.0.fmt(fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for LegacyLaneId {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeId for LegacyLaneId {
|
||||
const TYPE_ID: [u8; 4] = *b"blan";
|
||||
}
|
||||
|
||||
/// Bridge lane identifier.
|
||||
///
|
||||
/// Lane connects two endpoints at both sides of the bridge. We assume that every endpoint
|
||||
/// has its own unique identifier. We want lane identifiers to be **the same on the both sides
|
||||
/// of the bridge** (and naturally unique across global consensus if endpoints have unique
|
||||
/// identifiers). So lane id is the hash (`blake2_256`) of **ordered** encoded locations
|
||||
/// concatenation (separated by some binary data). I.e.:
|
||||
///
|
||||
/// ```nocompile
|
||||
/// let endpoint1 = X2(GlobalConsensus(NetworkId::Pezkuwi), Teyrchain(42));
|
||||
/// let endpoint2 = X2(GlobalConsensus(NetworkId::Kusama), Teyrchain(777));
|
||||
///
|
||||
/// let final_lane_key = if endpoint1 < endpoint2 {
|
||||
/// (endpoint1, VALUES_SEPARATOR, endpoint2)
|
||||
/// } else {
|
||||
/// (endpoint2, VALUES_SEPARATOR, endpoint1)
|
||||
/// }.using_encoded(blake2_256);
|
||||
/// ```
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Default,
|
||||
Encode,
|
||||
Eq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
PartialEq,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct HashedLaneId(H256);
|
||||
|
||||
impl HashedLaneId {
|
||||
/// Create lane identifier from given hash.
|
||||
///
|
||||
/// There's no `From<H256>` implementation for the `LaneId`, because using this conversion
|
||||
/// in a wrong way (i.e. computing hash of endpoints manually) may lead to issues. So we
|
||||
/// want the call to be explicit.
|
||||
#[cfg(feature = "std")]
|
||||
pub const fn from_inner(inner: H256) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
/// Access the inner lane representation.
|
||||
pub fn inner(&self) -> &H256 {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for HashedLaneId {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
core::fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for HashedLaneId {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
core::fmt::Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeId for HashedLaneId {
|
||||
const TYPE_ID: [u8; 4] = *b"hlan";
|
||||
}
|
||||
|
||||
impl LaneIdType for HashedLaneId {
|
||||
/// Create lane identifier from two locations.
|
||||
fn try_new<T: Ord + Encode>(endpoint1: T, endpoint2: T) -> Result<Self, ()> {
|
||||
const VALUES_SEPARATOR: [u8; 31] = *b"bridges-lane-id-value-separator";
|
||||
|
||||
Ok(Self(
|
||||
if endpoint1 < endpoint2 {
|
||||
(endpoint1, VALUES_SEPARATOR, endpoint2)
|
||||
} else {
|
||||
(endpoint2, VALUES_SEPARATOR, endpoint1)
|
||||
}
|
||||
.using_encoded(blake2_256)
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl TryFrom<Vec<u8>> for HashedLaneId {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
if value.len() == 32 {
|
||||
return <[u8; 32]>::try_from(value).map(|v| Self(H256::from(v))).map_err(|_| ());
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Lane state.
|
||||
#[derive(Clone, Copy, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen, RuntimeDebug)]
|
||||
pub enum LaneState {
|
||||
/// Lane is opened and messages may be sent/received over it.
|
||||
Opened,
|
||||
/// Lane is closed and all attempts to send/receive messages to/from this lane
|
||||
/// will fail.
|
||||
///
|
||||
/// Keep in mind that the lane has two ends and the state of the same lane at
|
||||
/// its ends may be different. Those who are controlling/serving the lane
|
||||
/// and/or sending messages over the lane, have to coordinate their actions on
|
||||
/// both ends to make sure that lane is operating smoothly on both ends.
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl LaneState {
|
||||
/// Returns true if lane state allows sending/receiving messages.
|
||||
pub fn is_active(&self) -> bool {
|
||||
matches!(*self, LaneState::Opened)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::MessageNonce;
|
||||
|
||||
#[test]
|
||||
fn lane_id_debug_format_matches_inner_hash_format() {
|
||||
assert_eq!(
|
||||
format!("{:?}", HashedLaneId(H256::from([1u8; 32]))),
|
||||
format!("{:?}", H256::from([1u8; 32])),
|
||||
);
|
||||
assert_eq!(format!("{:?}", LegacyLaneId([0, 0, 0, 1])), format!("{:?}", [0, 0, 0, 1]),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashed_encode_decode_works() {
|
||||
// simple encode/decode - new format
|
||||
let lane_id = HashedLaneId(H256::from([1u8; 32]));
|
||||
let encoded_lane_id = lane_id.encode();
|
||||
let decoded_lane_id = HashedLaneId::decode(&mut &encoded_lane_id[..]).expect("decodable");
|
||||
assert_eq!(lane_id, decoded_lane_id);
|
||||
assert_eq!(
|
||||
"0101010101010101010101010101010101010101010101010101010101010101",
|
||||
hex::encode(encoded_lane_id)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_encode_decode_works() {
|
||||
// simple encode/decode - old format
|
||||
let lane_id = LegacyLaneId([0, 0, 0, 1]);
|
||||
let encoded_lane_id = lane_id.encode();
|
||||
let decoded_lane_id = LegacyLaneId::decode(&mut &encoded_lane_id[..]).expect("decodable");
|
||||
assert_eq!(lane_id, decoded_lane_id);
|
||||
assert_eq!("00000001", hex::encode(encoded_lane_id));
|
||||
|
||||
// decode sample
|
||||
let bytes = vec![0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0];
|
||||
let (lane, nonce_start, nonce_end): (LegacyLaneId, MessageNonce, MessageNonce) =
|
||||
Decode::decode(&mut &bytes[..]).unwrap();
|
||||
assert_eq!(lane, LegacyLaneId([0, 0, 0, 2]));
|
||||
assert_eq!(nonce_start, 1);
|
||||
assert_eq!(nonce_end, 1);
|
||||
|
||||
// run encode/decode for `LaneId` with different positions
|
||||
let expected_lane = LegacyLaneId([0, 0, 0, 1]);
|
||||
let expected_nonce_start = 1088_u64;
|
||||
let expected_nonce_end = 9185_u64;
|
||||
|
||||
// decode: LaneId,Nonce,Nonce
|
||||
let bytes = (expected_lane, expected_nonce_start, expected_nonce_end).encode();
|
||||
let (lane, nonce_start, nonce_end): (LegacyLaneId, MessageNonce, MessageNonce) =
|
||||
Decode::decode(&mut &bytes[..]).unwrap();
|
||||
assert_eq!(lane, expected_lane);
|
||||
assert_eq!(nonce_start, expected_nonce_start);
|
||||
assert_eq!(nonce_end, expected_nonce_end);
|
||||
|
||||
// decode: Nonce,LaneId,Nonce
|
||||
let bytes = (expected_nonce_start, expected_lane, expected_nonce_end).encode();
|
||||
let (nonce_start, lane, nonce_end): (MessageNonce, LegacyLaneId, MessageNonce) =
|
||||
Decode::decode(&mut &bytes[..]).unwrap();
|
||||
assert_eq!(lane, expected_lane);
|
||||
assert_eq!(nonce_start, expected_nonce_start);
|
||||
assert_eq!(nonce_end, expected_nonce_end);
|
||||
|
||||
// decode: Nonce,Nonce,LaneId
|
||||
let bytes = (expected_nonce_start, expected_nonce_end, expected_lane).encode();
|
||||
let (nonce_start, nonce_end, lane): (MessageNonce, MessageNonce, LegacyLaneId) =
|
||||
Decode::decode(&mut &bytes[..]).unwrap();
|
||||
assert_eq!(lane, expected_lane);
|
||||
assert_eq!(nonce_start, expected_nonce_start);
|
||||
assert_eq!(nonce_end, expected_nonce_end);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashed_lane_id_is_generated_using_ordered_endpoints() {
|
||||
assert_eq!(HashedLaneId::try_new(1, 2).unwrap(), HashedLaneId::try_new(2, 1).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashed_lane_id_is_different_for_different_endpoints() {
|
||||
assert_ne!(HashedLaneId::try_new(1, 2).unwrap(), HashedLaneId::try_new(1, 3).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashed_lane_id_is_different_even_if_arguments_has_partial_matching_encoding() {
|
||||
/// Some artificial type that generates the same encoding for different values
|
||||
/// concatenations. I.e. the encoding for `(Either::Two(1, 2), Either::Two(3, 4))`
|
||||
/// is the same as encoding of `(Either::Three(1, 2, 3), Either::One(4))`.
|
||||
/// In practice, this type is not useful, because you can't do a proper decoding.
|
||||
/// But still there may be some collisions even in proper types.
|
||||
#[derive(Eq, Ord, PartialEq, PartialOrd)]
|
||||
enum Either {
|
||||
Three(u64, u64, u64),
|
||||
Two(u64, u64),
|
||||
One(u64),
|
||||
}
|
||||
|
||||
impl codec::Encode for Either {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
match *self {
|
||||
Self::One(a) => a.encode(),
|
||||
Self::Two(a, b) => (a, b).encode(),
|
||||
Self::Three(a, b, c) => (a, b, c).encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_ne!(
|
||||
HashedLaneId::try_new(Either::Two(1, 2), Either::Two(3, 4)).unwrap(),
|
||||
HashedLaneId::try_new(Either::Three(1, 2, 3), Either::One(4)).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,624 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of messages module.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_header_pez_chain::HeaderChainError;
|
||||
use pezbp_runtime::{
|
||||
messages::MessageDispatchResult, BasicOperatingMode, Chain, OperatingMode, RangeInclusiveExt,
|
||||
StorageProofError, UnderlyingChainOf, UnderlyingChainProvider,
|
||||
};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use pezframe_support::PalletError;
|
||||
// Weight is reexported to avoid additional pezframe-support dependencies in related crates.
|
||||
pub use pezframe_support::weights::Weight;
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use source_chain::RelayersRewards;
|
||||
use pezsp_core::RuntimeDebug;
|
||||
use pezsp_std::{collections::vec_deque::VecDeque, ops::RangeInclusive, prelude::*};
|
||||
|
||||
pub use call_info::{
|
||||
BaseMessagesProofInfo, BridgeMessagesCall, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo,
|
||||
ReceiveMessagesProofInfo, UnrewardedRelayerOccupation,
|
||||
};
|
||||
pub use lane::{HashedLaneId, LaneIdType, LaneState, LegacyLaneId};
|
||||
|
||||
mod call_info;
|
||||
mod lane;
|
||||
pub mod source_chain;
|
||||
pub mod storage_keys;
|
||||
pub mod target_chain;
|
||||
|
||||
/// Hard limit on message size that can be sent over the bridge.
|
||||
pub const HARD_MESSAGE_SIZE_LIMIT: u32 = 64 * 1024;
|
||||
|
||||
/// Bizinikiwi-based chain with messaging support.
|
||||
pub trait ChainWithMessages: Chain {
|
||||
/// Name of the bridge messages pezpallet (used in `construct_runtime` macro call) that is
|
||||
/// deployed at some other chain to bridge with this `ChainWithMessages`.
|
||||
///
|
||||
/// We assume that all chains that are bridging with this `ChainWithMessages` are using
|
||||
/// the same name.
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str;
|
||||
|
||||
/// Maximal number of unrewarded relayers in a single confirmation transaction at this
|
||||
/// `ChainWithMessages`. Unrewarded means that the relayer has delivered messages, but
|
||||
/// either confirmations haven't been delivered back to the source chain, or we haven't
|
||||
/// received reward confirmations yet.
|
||||
///
|
||||
/// This constant limits maximal number of entries in the `InboundLaneData::relayers`. Keep
|
||||
/// in mind that the same relayer account may take several (non-consecutive) entries in this
|
||||
/// set.
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce;
|
||||
/// Maximal number of unconfirmed messages in a single confirmation transaction at this
|
||||
/// `ChainWithMessages`. Unconfirmed means that the
|
||||
/// message has been delivered, but either confirmations haven't been delivered back to the
|
||||
/// source chain, or we haven't received reward confirmations for these messages yet.
|
||||
///
|
||||
/// This constant limits difference between last message from last entry of the
|
||||
/// `InboundLaneData::relayers` and first message at the first entry.
|
||||
///
|
||||
/// There is no point of making this parameter lesser than
|
||||
/// `MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX`, because then maximal number of relayer entries
|
||||
/// will be limited by maximal number of messages.
|
||||
///
|
||||
/// This value also represents maximal number of messages in single delivery transaction.
|
||||
/// Transaction that is declaring more messages than this value, will be rejected. Even if
|
||||
/// these messages are from different lanes.
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce;
|
||||
|
||||
/// Return maximal dispatch weight of the message we're able to receive.
|
||||
fn maximal_incoming_message_dispatch_weight() -> Weight {
|
||||
// we leave 1/2 of `max_extrinsic_weight` for the delivery transaction itself
|
||||
Self::max_extrinsic_weight() / 2
|
||||
}
|
||||
|
||||
/// Return maximal size of the message we're able to receive.
|
||||
fn maximal_incoming_message_size() -> u32 {
|
||||
maximal_incoming_message_size(Self::max_extrinsic_size())
|
||||
}
|
||||
}
|
||||
|
||||
/// Return maximal size of the message the chain with `max_extrinsic_size` is able to receive.
|
||||
pub fn maximal_incoming_message_size(max_extrinsic_size: u32) -> u32 {
|
||||
// The maximal size of extrinsic at Bizinikiwi-based chain depends on the
|
||||
// `pezframe_system::Config::MaximumBlockLength` and
|
||||
// `pezframe_system::Config::AvailableBlockRatio` constants. This check is here to be sure that
|
||||
// the lane won't stuck because message is too large to fit into delivery transaction.
|
||||
//
|
||||
// **IMPORTANT NOTE**: the delivery transaction contains storage proof of the message, not
|
||||
// the message itself. The proof is always larger than the message. But unless chain state
|
||||
// is enormously large, it should be several dozens/hundreds of bytes. The delivery
|
||||
// transaction also contains signatures and signed extensions. Because of this, we reserve
|
||||
// 1/3 of the the maximal extrinsic size for this data.
|
||||
//
|
||||
// **ANOTHER IMPORTANT NOTE**: large message means not only larger proofs and heavier
|
||||
// proof verification, but also heavier message decoding and dispatch. So we have a hard
|
||||
// limit of `64Kb`, which in practice limits the message size on all chains. Without this
|
||||
// limit the **weight** (not the size) of the message will be higher than the
|
||||
// `Self::maximal_incoming_message_dispatch_weight()`.
|
||||
|
||||
pezsp_std::cmp::min(max_extrinsic_size / 3 * 2, HARD_MESSAGE_SIZE_LIMIT)
|
||||
}
|
||||
|
||||
impl<T> ChainWithMessages for T
|
||||
where
|
||||
T: Chain + UnderlyingChainProvider,
|
||||
UnderlyingChainOf<T>: ChainWithMessages,
|
||||
{
|
||||
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
|
||||
UnderlyingChainOf::<T>::WITH_CHAIN_MESSAGES_PALLET_NAME;
|
||||
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce =
|
||||
UnderlyingChainOf::<T>::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
|
||||
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce =
|
||||
UnderlyingChainOf::<T>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
|
||||
}
|
||||
|
||||
/// Messages pezpallet operating mode.
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub enum MessagesOperatingMode {
|
||||
/// Basic operating mode (Normal/Halted)
|
||||
Basic(BasicOperatingMode),
|
||||
/// The pezpallet is not accepting outbound messages. Inbound messages and receiving proofs
|
||||
/// are still accepted.
|
||||
///
|
||||
/// This mode may be used e.g. when bridged chain expects upgrade. Then to avoid dispatch
|
||||
/// failures, the pezpallet owner may stop accepting new messages, while continuing to deliver
|
||||
/// queued messages to the bridged chain. Once upgrade is completed, the mode may be switched
|
||||
/// back to `Normal`.
|
||||
RejectingOutboundMessages,
|
||||
}
|
||||
|
||||
impl Default for MessagesOperatingMode {
|
||||
fn default() -> Self {
|
||||
MessagesOperatingMode::Basic(BasicOperatingMode::Normal)
|
||||
}
|
||||
}
|
||||
|
||||
impl OperatingMode for MessagesOperatingMode {
|
||||
fn is_halted(&self) -> bool {
|
||||
match self {
|
||||
Self::Basic(operating_mode) => operating_mode.is_halted(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Message nonce. Valid messages will never have 0 nonce.
|
||||
pub type MessageNonce = u64;
|
||||
|
||||
/// Opaque message payload. We only decode this payload when it is dispatched.
|
||||
pub type MessagePayload = Vec<u8>;
|
||||
|
||||
/// Message key (unique message identifier) as it is stored in the storage.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct MessageKey<LaneId: Encode> {
|
||||
/// ID of the message lane.
|
||||
pub lane_id: LaneId,
|
||||
/// Message nonce.
|
||||
pub nonce: MessageNonce,
|
||||
}
|
||||
|
||||
/// Message as it is stored in the storage.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct Message<LaneId: Encode> {
|
||||
/// Message key.
|
||||
pub key: MessageKey<LaneId>,
|
||||
/// Message payload.
|
||||
pub payload: MessagePayload,
|
||||
}
|
||||
|
||||
/// Inbound lane data.
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)]
|
||||
pub struct InboundLaneData<RelayerId> {
|
||||
/// Identifiers of relayers and messages that they have delivered to this lane (ordered by
|
||||
/// message nonce).
|
||||
///
|
||||
/// This serves as a helper storage item, to allow the source chain to easily pay rewards
|
||||
/// to the relayers who successfully delivered messages to the target chain (inbound lane).
|
||||
///
|
||||
/// It is guaranteed to have at most N entries, where N is configured at the module level.
|
||||
/// If there are N entries in this vec, then:
|
||||
/// 1) all incoming messages are rejected if they're missing corresponding
|
||||
/// `proof-of(outbound-lane.state)`; 2) all incoming messages are rejected if
|
||||
/// `proof-of(outbound-lane.state).last_delivered_nonce` is equal to
|
||||
/// `self.last_confirmed_nonce`. Given what is said above, all nonces in this queue are in
|
||||
/// range: `(self.last_confirmed_nonce; self.last_delivered_nonce()]`.
|
||||
///
|
||||
/// When a relayer sends a single message, both of MessageNonces are the same.
|
||||
/// When relayer sends messages in a batch, the first arg is the lowest nonce, second arg the
|
||||
/// highest nonce. Multiple dispatches from the same relayer are allowed.
|
||||
pub relayers: VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||
|
||||
/// Nonce of the last message that
|
||||
/// a) has been delivered to the target (this) chain and
|
||||
/// b) the delivery has been confirmed on the source chain
|
||||
///
|
||||
/// that the target chain knows of.
|
||||
///
|
||||
/// This value is updated indirectly when an `OutboundLane` state of the source
|
||||
/// chain is received alongside with new messages delivery.
|
||||
pub last_confirmed_nonce: MessageNonce,
|
||||
|
||||
/// Inbound lane state.
|
||||
///
|
||||
/// If state is `Closed`, then all attempts to deliver messages to this end will fail.
|
||||
pub state: LaneState,
|
||||
}
|
||||
|
||||
impl<RelayerId> Default for InboundLaneData<RelayerId> {
|
||||
fn default() -> Self {
|
||||
InboundLaneData {
|
||||
state: LaneState::Closed,
|
||||
relayers: VecDeque::new(),
|
||||
last_confirmed_nonce: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<RelayerId> InboundLaneData<RelayerId> {
|
||||
/// Returns default inbound lane data with opened state.
|
||||
pub fn opened() -> Self {
|
||||
InboundLaneData { state: LaneState::Opened, ..Default::default() }
|
||||
}
|
||||
|
||||
/// Returns approximate size of the struct, given a number of entries in the `relayers` set and
|
||||
/// size of each entry.
|
||||
///
|
||||
/// Returns `None` if size overflows `usize` limits.
|
||||
pub fn encoded_size_hint(relayers_entries: usize) -> Option<usize>
|
||||
where
|
||||
RelayerId: MaxEncodedLen,
|
||||
{
|
||||
relayers_entries
|
||||
.checked_mul(UnrewardedRelayer::<RelayerId>::max_encoded_len())?
|
||||
.checked_add(MessageNonce::max_encoded_len())
|
||||
}
|
||||
|
||||
/// Returns the approximate size of the struct as u32, given a number of entries in the
|
||||
/// `relayers` set and the size of each entry.
|
||||
///
|
||||
/// Returns `u32::MAX` if size overflows `u32` limits.
|
||||
pub fn encoded_size_hint_u32(relayers_entries: usize) -> u32
|
||||
where
|
||||
RelayerId: MaxEncodedLen,
|
||||
{
|
||||
Self::encoded_size_hint(relayers_entries)
|
||||
.and_then(|x| u32::try_from(x).ok())
|
||||
.unwrap_or(u32::MAX)
|
||||
}
|
||||
|
||||
/// Nonce of the last message that has been delivered to this (target) chain.
|
||||
pub fn last_delivered_nonce(&self) -> MessageNonce {
|
||||
self.relayers
|
||||
.back()
|
||||
.map(|entry| entry.messages.end)
|
||||
.unwrap_or(self.last_confirmed_nonce)
|
||||
}
|
||||
|
||||
/// Returns the total number of messages in the `relayers` vector,
|
||||
/// saturating in case of underflow or overflow.
|
||||
pub fn total_unrewarded_messages(&self) -> MessageNonce {
|
||||
let relayers = &self.relayers;
|
||||
match (relayers.front(), relayers.back()) {
|
||||
(Some(front), Some(back)) =>
|
||||
(front.messages.begin..=back.messages.end).saturating_len(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Outbound message details, returned by runtime APIs.
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)]
|
||||
pub struct OutboundMessageDetails {
|
||||
/// Nonce assigned to the message.
|
||||
pub nonce: MessageNonce,
|
||||
/// Message dispatch weight.
|
||||
///
|
||||
/// Depending on messages pezpallet configuration, it may be declared by the message submitter,
|
||||
/// computed automatically or just be zero if dispatch fee is paid at the target chain.
|
||||
pub dispatch_weight: Weight,
|
||||
/// Size of the encoded message.
|
||||
pub size: u32,
|
||||
}
|
||||
|
||||
/// Inbound message details, returned by runtime APIs.
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)]
|
||||
pub struct InboundMessageDetails {
|
||||
/// Computed message dispatch weight.
|
||||
///
|
||||
/// Runtime API guarantees that it will match the value, returned by
|
||||
/// `target_chain::MessageDispatch::dispatch_weight`. This means that if the runtime
|
||||
/// has failed to decode the message, it will be zero - that's because `undecodable`
|
||||
/// message cannot be dispatched.
|
||||
pub dispatch_weight: Weight,
|
||||
}
|
||||
|
||||
/// Unrewarded relayer entry stored in the inbound lane data.
|
||||
///
|
||||
/// This struct represents a continuous range of messages that have been delivered by the same
|
||||
/// relayer and whose confirmations are still pending.
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
|
||||
pub struct UnrewardedRelayer<RelayerId> {
|
||||
/// Identifier of the relayer.
|
||||
pub relayer: RelayerId,
|
||||
/// Messages range, delivered by this relayer.
|
||||
pub messages: DeliveredMessages,
|
||||
}
|
||||
|
||||
/// Received messages with their dispatch result.
|
||||
#[derive(Clone, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, TypeInfo)]
|
||||
pub struct ReceivedMessages<DispatchLevelResult, LaneId> {
|
||||
/// Id of the lane which is receiving messages.
|
||||
pub lane: LaneId,
|
||||
/// Result of messages which we tried to dispatch
|
||||
pub receive_results: Vec<(MessageNonce, ReceptionResult<DispatchLevelResult>)>,
|
||||
}
|
||||
|
||||
impl<DispatchLevelResult, LaneId> ReceivedMessages<DispatchLevelResult, LaneId> {
|
||||
/// Creates new `ReceivedMessages` structure from given results.
|
||||
pub fn new(
|
||||
lane: LaneId,
|
||||
receive_results: Vec<(MessageNonce, ReceptionResult<DispatchLevelResult>)>,
|
||||
) -> Self {
|
||||
ReceivedMessages { lane: lane.into(), receive_results }
|
||||
}
|
||||
|
||||
/// Push `result` of the `message` delivery onto `receive_results` vector.
|
||||
pub fn push(&mut self, message: MessageNonce, result: ReceptionResult<DispatchLevelResult>) {
|
||||
self.receive_results.push((message, result));
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of single message receival.
|
||||
#[derive(RuntimeDebug, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, Clone, TypeInfo)]
|
||||
pub enum ReceptionResult<DispatchLevelResult> {
|
||||
/// Message has been received and dispatched. Note that we don't care whether dispatch has
|
||||
/// been successful or not - in both case message falls into this category.
|
||||
///
|
||||
/// The message dispatch result is also returned.
|
||||
Dispatched(MessageDispatchResult<DispatchLevelResult>),
|
||||
/// Message has invalid nonce and lane has rejected to accept this message.
|
||||
InvalidNonce,
|
||||
/// There are too many unrewarded relayer entries at the lane.
|
||||
TooManyUnrewardedRelayers,
|
||||
/// There are too many unconfirmed messages at the lane.
|
||||
TooManyUnconfirmedMessages,
|
||||
}
|
||||
|
||||
/// Delivered messages with their dispatch result.
|
||||
#[derive(
|
||||
Clone,
|
||||
Default,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
RuntimeDebug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct DeliveredMessages {
|
||||
/// Nonce of the first message that has been delivered (inclusive).
|
||||
pub begin: MessageNonce,
|
||||
/// Nonce of the last message that has been delivered (inclusive).
|
||||
pub end: MessageNonce,
|
||||
}
|
||||
|
||||
impl DeliveredMessages {
|
||||
/// Create new `DeliveredMessages` struct that confirms delivery of single nonce with given
|
||||
/// dispatch result.
|
||||
pub fn new(nonce: MessageNonce) -> Self {
|
||||
DeliveredMessages { begin: nonce, end: nonce }
|
||||
}
|
||||
|
||||
/// Return total count of delivered messages.
|
||||
pub fn total_messages(&self) -> MessageNonce {
|
||||
(self.begin..=self.end).saturating_len()
|
||||
}
|
||||
|
||||
/// Note new dispatched message.
|
||||
pub fn note_dispatched_message(&mut self) {
|
||||
self.end += 1;
|
||||
}
|
||||
|
||||
/// Returns true if delivered messages contain message with given nonce.
|
||||
pub fn contains_message(&self, nonce: MessageNonce) -> bool {
|
||||
(self.begin..=self.end).contains(&nonce)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gist of `InboundLaneData::relayers` field used by runtime APIs.
|
||||
#[derive(
|
||||
Clone, Default, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, TypeInfo,
|
||||
)]
|
||||
pub struct UnrewardedRelayersState {
|
||||
/// Number of entries in the `InboundLaneData::relayers` set.
|
||||
pub unrewarded_relayer_entries: MessageNonce,
|
||||
/// Number of messages in the oldest entry of `InboundLaneData::relayers`. This is the
|
||||
/// minimal number of reward proofs required to push out this entry from the set.
|
||||
pub messages_in_oldest_entry: MessageNonce,
|
||||
/// Total number of messages in the relayers vector.
|
||||
pub total_messages: MessageNonce,
|
||||
/// Nonce of the latest message that has been delivered to the target chain.
|
||||
///
|
||||
/// This corresponds to the result of the `InboundLaneData::last_delivered_nonce` call
|
||||
/// at the bridged chain.
|
||||
pub last_delivered_nonce: MessageNonce,
|
||||
}
|
||||
|
||||
impl UnrewardedRelayersState {
|
||||
/// Verify that the relayers state corresponds with the `InboundLaneData`.
|
||||
pub fn is_valid<RelayerId>(&self, lane_data: &InboundLaneData<RelayerId>) -> bool {
|
||||
self == &lane_data.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<RelayerId> From<&InboundLaneData<RelayerId>> for UnrewardedRelayersState {
|
||||
fn from(lane: &InboundLaneData<RelayerId>) -> UnrewardedRelayersState {
|
||||
UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: lane.relayers.len() as _,
|
||||
messages_in_oldest_entry: lane
|
||||
.relayers
|
||||
.front()
|
||||
.map(|entry| entry.messages.total_messages())
|
||||
.unwrap_or(0),
|
||||
total_messages: lane.total_unrewarded_messages(),
|
||||
last_delivered_nonce: lane.last_delivered_nonce(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Outbound lane data.
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
|
||||
pub struct OutboundLaneData {
|
||||
/// Nonce of the oldest message that we haven't yet pruned. May point to not-yet-generated
|
||||
/// message if all sent messages are already pruned.
|
||||
pub oldest_unpruned_nonce: MessageNonce,
|
||||
/// Nonce of the latest message, received by bridged chain.
|
||||
pub latest_received_nonce: MessageNonce,
|
||||
/// Nonce of the latest message, generated by us.
|
||||
pub latest_generated_nonce: MessageNonce,
|
||||
/// Lane state.
|
||||
///
|
||||
/// If state is `Closed`, then all attempts to send messages at this end will fail.
|
||||
pub state: LaneState,
|
||||
}
|
||||
|
||||
impl OutboundLaneData {
|
||||
/// Returns default outbound lane data with opened state.
|
||||
pub fn opened() -> Self {
|
||||
OutboundLaneData { state: LaneState::Opened, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OutboundLaneData {
|
||||
fn default() -> Self {
|
||||
OutboundLaneData {
|
||||
state: LaneState::Closed,
|
||||
// it is 1 because we're pruning everything in [oldest_unpruned_nonce;
|
||||
// latest_received_nonce]
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 0,
|
||||
latest_generated_nonce: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutboundLaneData {
|
||||
/// Return nonces of all currently queued messages (i.e. messages that we believe
|
||||
/// are not delivered yet).
|
||||
pub fn queued_messages(&self) -> RangeInclusive<MessageNonce> {
|
||||
(self.latest_received_nonce + 1)..=self.latest_generated_nonce
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the number of messages that the relayers have delivered.
|
||||
pub fn calc_relayers_rewards<AccountId>(
|
||||
pez_messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
|
||||
received_range: &RangeInclusive<MessageNonce>,
|
||||
) -> RelayersRewards<AccountId>
|
||||
where
|
||||
AccountId: pezsp_std::cmp::Ord,
|
||||
{
|
||||
// remember to reward relayers that have delivered messages
|
||||
// this loop is bounded by `T::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX` on the bridged chain
|
||||
let mut relayers_rewards = RelayersRewards::new();
|
||||
for entry in pez_messages_relayers {
|
||||
let nonce_begin = pezsp_std::cmp::max(entry.messages.begin, *received_range.start());
|
||||
let nonce_end = pezsp_std::cmp::min(entry.messages.end, *received_range.end());
|
||||
if nonce_end >= nonce_begin {
|
||||
*relayers_rewards.entry(entry.relayer).or_default() += nonce_end - nonce_begin + 1;
|
||||
}
|
||||
}
|
||||
relayers_rewards
|
||||
}
|
||||
|
||||
/// Error that happens during message verification.
|
||||
#[derive(
|
||||
Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
|
||||
)]
|
||||
pub enum VerificationError {
|
||||
/// The message proof is empty.
|
||||
EmptyMessageProof,
|
||||
/// Error returned by the bridged header chain.
|
||||
HeaderChain(HeaderChainError),
|
||||
/// Error returned while reading/decoding inbound lane data from the storage proof.
|
||||
InboundLaneStorage(StorageProofError),
|
||||
/// The declared message weight is incorrect.
|
||||
InvalidMessageWeight,
|
||||
/// Declared messages count doesn't match actual value.
|
||||
MessagesCountMismatch,
|
||||
/// Error returned while reading/decoding message data from the `VerifiedStorageProof`.
|
||||
MessageStorage(StorageProofError),
|
||||
/// The message is too large.
|
||||
MessageTooLarge,
|
||||
/// Error returned while reading/decoding outbound lane data from the `VerifiedStorageProof`.
|
||||
OutboundLaneStorage(StorageProofError),
|
||||
/// Storage proof related error.
|
||||
StorageProof(StorageProofError),
|
||||
/// Custom error
|
||||
Other(#[codec(skip)] &'static str),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn lane_is_closed_by_default() {
|
||||
assert_eq!(InboundLaneData::<()>::default().state, LaneState::Closed);
|
||||
assert_eq!(OutboundLaneData::default().state, LaneState::Closed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_unrewarded_messages_does_not_overflow() {
|
||||
let lane_data = InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: vec![
|
||||
UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(0) },
|
||||
UnrewardedRelayer {
|
||||
relayer: 2,
|
||||
messages: DeliveredMessages::new(MessageNonce::MAX),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
last_confirmed_nonce: 0,
|
||||
};
|
||||
assert_eq!(lane_data.total_unrewarded_messages(), MessageNonce::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inbound_lane_data_returns_correct_hint() {
|
||||
let test_cases = vec![
|
||||
// single relayer, multiple messages
|
||||
(1, 128u8),
|
||||
// multiple relayers, single message per relayer
|
||||
(128u8, 128u8),
|
||||
// several messages per relayer
|
||||
(13u8, 128u8),
|
||||
];
|
||||
for (relayer_entries, messages_count) in test_cases {
|
||||
let expected_size = InboundLaneData::<u8>::encoded_size_hint(relayer_entries as _);
|
||||
let actual_size = InboundLaneData {
|
||||
state: LaneState::Opened,
|
||||
relayers: (1u8..=relayer_entries)
|
||||
.map(|i| UnrewardedRelayer {
|
||||
relayer: i,
|
||||
messages: DeliveredMessages::new(i as _),
|
||||
})
|
||||
.collect(),
|
||||
last_confirmed_nonce: messages_count as _,
|
||||
}
|
||||
.encode()
|
||||
.len();
|
||||
let difference = (expected_size.unwrap() as f64 - actual_size as f64).abs();
|
||||
assert!(
|
||||
difference / (std::cmp::min(actual_size, expected_size.unwrap()) as f64) < 0.1,
|
||||
"Too large difference between actual ({actual_size}) and expected ({expected_size:?}) inbound lane data size. Test case: {relayer_entries}+{messages_count}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_result_works() {
|
||||
let delivered_messages = DeliveredMessages { begin: 100, end: 150 };
|
||||
|
||||
assert!(!delivered_messages.contains_message(99));
|
||||
assert!(delivered_messages.contains_message(100));
|
||||
assert!(delivered_messages.contains_message(150));
|
||||
assert!(!delivered_messages.contains_message(151));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of messages module, that are used on the source chain.
|
||||
|
||||
use crate::{MessageNonce, UnrewardedRelayer};
|
||||
|
||||
use pezbp_runtime::{raw_storage_proof_size, RawStorageProof, Size};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::RuntimeDebug;
|
||||
use pezsp_std::{
|
||||
collections::{btree_map::BTreeMap, vec_deque::VecDeque},
|
||||
fmt::Debug,
|
||||
ops::RangeInclusive,
|
||||
};
|
||||
|
||||
/// Messages delivery proof from the bridged chain.
|
||||
///
|
||||
/// It contains everything required to prove that our (this chain) messages have been
|
||||
/// delivered to the bridged (target) chain:
|
||||
///
|
||||
/// - hash of finalized header;
|
||||
///
|
||||
/// - storage proof of the inbound lane state;
|
||||
///
|
||||
/// - lane id.
|
||||
#[derive(Clone, Decode, DecodeWithMemTracking, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, LaneId> {
|
||||
/// Hash of the bridge header the proof is for.
|
||||
pub bridged_header_hash: BridgedHeaderHash,
|
||||
/// Storage trie proof generated for [`Self::bridged_header_hash`].
|
||||
pub storage_proof: RawStorageProof,
|
||||
/// Lane id of which messages were delivered and the proof is for.
|
||||
pub lane: LaneId,
|
||||
}
|
||||
|
||||
impl<BridgedHeaderHash, LaneId> Size
|
||||
for FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, LaneId>
|
||||
{
|
||||
fn size(&self) -> u32 {
|
||||
use pezframe_support::pezsp_runtime::SaturatedConversion;
|
||||
raw_storage_proof_size(&self.storage_proof).saturated_into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of messages, delivered by relayers.
|
||||
pub type RelayersRewards<AccountId> = BTreeMap<AccountId, MessageNonce>;
|
||||
|
||||
/// Manages payments that are happening at the source chain during delivery confirmation
|
||||
/// transaction.
|
||||
pub trait DeliveryConfirmationPayments<AccountId, LaneId> {
|
||||
/// Error type.
|
||||
type Error: Debug + Into<&'static str>;
|
||||
|
||||
/// Pay rewards for delivering messages to the given relayers.
|
||||
///
|
||||
/// The implementation may also choose to pay reward to the `confirmation_relayer`, which is
|
||||
/// a relayer that has submitted delivery confirmation transaction.
|
||||
///
|
||||
/// Returns number of actually rewarded relayers.
|
||||
fn pay_reward(
|
||||
lane_id: LaneId,
|
||||
pez_messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
|
||||
confirmation_relayer: &AccountId,
|
||||
received_range: &RangeInclusive<MessageNonce>,
|
||||
) -> MessageNonce;
|
||||
}
|
||||
|
||||
impl<AccountId, LaneId> DeliveryConfirmationPayments<AccountId, LaneId> for () {
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_reward(
|
||||
_lane_id: LaneId,
|
||||
_pez_messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
|
||||
_confirmation_relayer: &AccountId,
|
||||
_received_range: &RangeInclusive<MessageNonce>,
|
||||
) -> MessageNonce {
|
||||
// this implementation is not rewarding relayers at all
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback that is called at the source chain (bridge hub) when we get delivery confirmation
|
||||
/// for new messages.
|
||||
pub trait OnMessagesDelivered<LaneId> {
|
||||
/// New messages delivery has been confirmed.
|
||||
///
|
||||
/// The only argument of the function is the number of yet undelivered messages
|
||||
fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce);
|
||||
}
|
||||
|
||||
impl<LaneId> OnMessagesDelivered<LaneId> for () {
|
||||
fn on_messages_delivered(_lane: LaneId, _enqueued_messages: MessageNonce) {}
|
||||
}
|
||||
|
||||
/// Send message artifacts.
|
||||
#[derive(Eq, RuntimeDebug, PartialEq)]
|
||||
pub struct SendMessageArtifacts {
|
||||
/// Nonce of the message.
|
||||
pub nonce: MessageNonce,
|
||||
/// Number of enqueued messages at the lane, after the message is sent.
|
||||
pub enqueued_messages: MessageNonce,
|
||||
}
|
||||
|
||||
/// Messages bridge API to be used from other pallets.
|
||||
pub trait MessagesBridge<Payload, LaneId> {
|
||||
/// Error type.
|
||||
type Error: Debug;
|
||||
|
||||
/// Intermediary structure returned by `validate_message()`.
|
||||
///
|
||||
/// It can than be passed to `send_message()` in order to actually send the message
|
||||
/// on the bridge.
|
||||
type SendMessageArgs;
|
||||
|
||||
/// Check if the message can be sent over the bridge.
|
||||
fn validate_message(
|
||||
lane: LaneId,
|
||||
message: &Payload,
|
||||
) -> Result<Self::SendMessageArgs, Self::Error>;
|
||||
|
||||
/// Send message over the bridge.
|
||||
///
|
||||
/// Returns unique message nonce or error if send has failed.
|
||||
fn send_message(message: Self::SendMessageArgs) -> SendMessageArtifacts;
|
||||
}
|
||||
|
||||
/// Structure that may be used in place `MessageDeliveryAndDispatchPayment` on chains,
|
||||
/// where outbound messages are forbidden.
|
||||
pub struct ForbidOutboundMessages;
|
||||
|
||||
impl<AccountId, LaneId> DeliveryConfirmationPayments<AccountId, LaneId> for ForbidOutboundMessages {
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_reward(
|
||||
_lane_id: LaneId,
|
||||
_pez_messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
|
||||
_confirmation_relayer: &AccountId,
|
||||
_received_range: &RangeInclusive<MessageNonce>,
|
||||
) -> MessageNonce {
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Storage keys of bridge messages pezpallet.
|
||||
|
||||
/// Name of the `OPERATING_MODE_VALUE_NAME` storage value.
|
||||
pub const OPERATING_MODE_VALUE_NAME: &str = "PalletOperatingMode";
|
||||
/// Name of the `OutboundMessages` storage map.
|
||||
pub const OUTBOUND_MESSAGES_MAP_NAME: &str = "OutboundMessages";
|
||||
/// Name of the `OutboundLanes` storage map.
|
||||
pub const OUTBOUND_LANES_MAP_NAME: &str = "OutboundLanes";
|
||||
/// Name of the `InboundLanes` storage map.
|
||||
pub const INBOUND_LANES_MAP_NAME: &str = "InboundLanes";
|
||||
|
||||
use crate::{MessageKey, MessageNonce};
|
||||
|
||||
use codec::Encode;
|
||||
use pezframe_support::Blake2_128Concat;
|
||||
use pezsp_core::storage::StorageKey;
|
||||
|
||||
/// Storage key of the `PalletOperatingMode` value in the runtime storage.
|
||||
pub fn operating_mode_key(pezpallet_prefix: &str) -> StorageKey {
|
||||
StorageKey(
|
||||
pezbp_runtime::storage_value_final_key(
|
||||
pezpallet_prefix.as_bytes(),
|
||||
OPERATING_MODE_VALUE_NAME.as_bytes(),
|
||||
)
|
||||
.to_vec(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Storage key of the outbound message in the runtime storage.
|
||||
pub fn message_key<LaneId: Encode>(
|
||||
pezpallet_prefix: &str,
|
||||
lane: LaneId,
|
||||
nonce: MessageNonce,
|
||||
) -> StorageKey {
|
||||
pezbp_runtime::storage_map_final_key::<Blake2_128Concat>(
|
||||
pezpallet_prefix,
|
||||
OUTBOUND_MESSAGES_MAP_NAME,
|
||||
&MessageKey { lane_id: lane, nonce }.encode(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Storage key of the outbound message lane state in the runtime storage.
|
||||
pub fn outbound_lane_data_key<LaneId: Encode>(pezpallet_prefix: &str, lane: &LaneId) -> StorageKey {
|
||||
pezbp_runtime::storage_map_final_key::<Blake2_128Concat>(
|
||||
pezpallet_prefix,
|
||||
OUTBOUND_LANES_MAP_NAME,
|
||||
&lane.encode(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Storage key of the inbound message lane state in the runtime storage.
|
||||
pub fn inbound_lane_data_key<LaneId: Encode>(pezpallet_prefix: &str, lane: &LaneId) -> StorageKey {
|
||||
pezbp_runtime::storage_map_final_key::<Blake2_128Concat>(
|
||||
pezpallet_prefix,
|
||||
INBOUND_LANES_MAP_NAME,
|
||||
&lane.encode(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
lane::{HashedLaneId, LegacyLaneId},
|
||||
LaneIdType,
|
||||
};
|
||||
use hex_literal::hex;
|
||||
|
||||
#[test]
|
||||
fn operating_mode_key_computed_properly() {
|
||||
// If this test fails, then something has been changed in module storage that is possibly
|
||||
// breaking all existing message relays.
|
||||
let storage_key = operating_mode_key("BridgeMessages").0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("dd16c784ebd3390a9bc0357c7511ed010f4cf0917788d791142ff6c1f216e7b3").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_message_key_computed_properly() {
|
||||
// If this test fails, then something has been changed in module storage that is breaking
|
||||
// all previously crafted messages proofs.
|
||||
let storage_key =
|
||||
message_key("BridgeMessages", &HashedLaneId::try_new(1, 2).unwrap(), 42).0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("dd16c784ebd3390a9bc0357c7511ed018a395e6242c6813b196ca31ed0547ea70e9bdb8f50c68d12f06eabb57759ee5eb1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dcf87f9793be208e5ea02a00000000000000").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
|
||||
// check backwards compatibility
|
||||
let storage_key = message_key("BridgeMessages", &LegacyLaneId(*b"test"), 42).0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("dd16c784ebd3390a9bc0357c7511ed018a395e6242c6813b196ca31ed0547ea79446af0e09063bd4a7874aef8a997cec746573742a00000000000000").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outbound_lane_data_key_computed_properly() {
|
||||
// If this test fails, then something has been changed in module storage that is breaking
|
||||
// all previously crafted outbound lane state proofs.
|
||||
let storage_key =
|
||||
outbound_lane_data_key("BridgeMessages", &HashedLaneId::try_new(1, 2).unwrap()).0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("dd16c784ebd3390a9bc0357c7511ed0196c246acb9b55077390e3ca723a0ca1fd3bef8b00df8ca7b01813b5e2741950db1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dcf87f9793be208e5ea0").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
|
||||
// check backwards compatibility
|
||||
let storage_key = outbound_lane_data_key("BridgeMessages", &LegacyLaneId(*b"test")).0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("dd16c784ebd3390a9bc0357c7511ed0196c246acb9b55077390e3ca723a0ca1f44a8995dd50b6657a037a7839304535b74657374").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inbound_lane_data_key_computed_properly() {
|
||||
// If this test fails, then something has been changed in module storage that is breaking
|
||||
// all previously crafted inbound lane state proofs.
|
||||
let storage_key =
|
||||
inbound_lane_data_key("BridgeMessages", &HashedLaneId::try_new(1, 2).unwrap()).0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("dd16c784ebd3390a9bc0357c7511ed01e5f83cf83f2127eb47afdc35d6e43fabd3bef8b00df8ca7b01813b5e2741950db1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dcf87f9793be208e5ea0").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
|
||||
// check backwards compatibility
|
||||
let storage_key = inbound_lane_data_key("BridgeMessages", &LegacyLaneId(*b"test")).0;
|
||||
assert_eq!(
|
||||
storage_key,
|
||||
hex!("dd16c784ebd3390a9bc0357c7511ed01e5f83cf83f2127eb47afdc35d6e43fab44a8995dd50b6657a037a7839304535b74657374").to_vec(),
|
||||
"Unexpected storage key: {}",
|
||||
hex::encode(&storage_key),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of messages module, that are used on the target chain.
|
||||
|
||||
use crate::{Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData};
|
||||
|
||||
use pezbp_runtime::{messages::MessageDispatchResult, raw_storage_proof_size, RawStorageProof, Size};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, Error as CodecError};
|
||||
use pezframe_support::weights::Weight;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::RuntimeDebug;
|
||||
use pezsp_std::{fmt::Debug, marker::PhantomData, prelude::*};
|
||||
|
||||
/// Messages proof from bridged chain.
|
||||
///
|
||||
/// It contains everything required to prove that bridged (source) chain has
|
||||
/// sent us some messages:
|
||||
///
|
||||
/// - hash of finalized header;
|
||||
///
|
||||
/// - storage proof of messages and (optionally) outbound lane state;
|
||||
///
|
||||
/// - lane id;
|
||||
///
|
||||
/// - nonces (inclusive range) of messages which are included in this proof.
|
||||
#[derive(Clone, Decode, DecodeWithMemTracking, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct FromBridgedChainMessagesProof<BridgedHeaderHash, Lane> {
|
||||
/// Hash of the finalized bridged header the proof is for.
|
||||
pub bridged_header_hash: BridgedHeaderHash,
|
||||
/// A storage trie proof of messages being delivered.
|
||||
pub storage_proof: RawStorageProof,
|
||||
/// Messages in this proof are sent over this lane.
|
||||
pub lane: Lane,
|
||||
/// Nonce of the first message being delivered.
|
||||
pub nonces_start: MessageNonce,
|
||||
/// Nonce of the last message being delivered.
|
||||
pub nonces_end: MessageNonce,
|
||||
}
|
||||
|
||||
impl<BridgedHeaderHash, Lane> Size for FromBridgedChainMessagesProof<BridgedHeaderHash, Lane> {
|
||||
fn size(&self) -> u32 {
|
||||
use pezframe_support::pezsp_runtime::SaturatedConversion;
|
||||
raw_storage_proof_size(&self.storage_proof).saturated_into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Proved messages from the source chain.
|
||||
pub type ProvedMessages<LaneId, Message> = (LaneId, ProvedLaneMessages<Message>);
|
||||
|
||||
/// Proved messages from single lane of the source chain.
|
||||
#[derive(RuntimeDebug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
|
||||
pub struct ProvedLaneMessages<Message> {
|
||||
/// Optional outbound lane state.
|
||||
pub lane_state: Option<OutboundLaneData>,
|
||||
/// Messages sent through this lane.
|
||||
pub messages: Vec<Message>,
|
||||
}
|
||||
|
||||
/// Message data with decoded dispatch payload.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct DispatchMessageData<DispatchPayload> {
|
||||
/// Result of dispatch payload decoding.
|
||||
pub payload: Result<DispatchPayload, CodecError>,
|
||||
}
|
||||
|
||||
/// Message with decoded dispatch payload.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct DispatchMessage<DispatchPayload, LaneId: Encode> {
|
||||
/// Message key.
|
||||
pub key: MessageKey<LaneId>,
|
||||
/// Message data with decoded dispatch payload.
|
||||
pub data: DispatchMessageData<DispatchPayload>,
|
||||
}
|
||||
|
||||
/// Called when inbound message is received.
|
||||
pub trait MessageDispatch {
|
||||
/// Decoded message payload type. Valid message may contain invalid payload. In this case
|
||||
/// message is delivered, but dispatch fails. Therefore, two separate types of payload
|
||||
/// (opaque `MessagePayload` used in delivery and this `DispatchPayload` used in dispatch).
|
||||
type DispatchPayload: Decode;
|
||||
|
||||
/// Fine-grained result of single message dispatch (for better diagnostic purposes)
|
||||
type DispatchLevelResult: Clone + pezsp_std::fmt::Debug + Eq;
|
||||
|
||||
/// Lane identifier type.
|
||||
type LaneId: Encode;
|
||||
|
||||
/// Returns `true` if dispatcher is ready to accept additional messages. The `false` should
|
||||
/// be treated as a hint by both dispatcher and its consumers - i.e. dispatcher shall not
|
||||
/// simply drop messages if it returns `false`. The consumer may still call the `dispatch`
|
||||
/// if dispatcher has returned `false`.
|
||||
///
|
||||
/// We check it in the messages delivery transaction prologue. So if it becomes `false`
|
||||
/// after some portion of messages is already dispatched, it doesn't fail the whole transaction.
|
||||
fn is_active(lane: Self::LaneId) -> bool;
|
||||
|
||||
/// Estimate dispatch weight.
|
||||
///
|
||||
/// This function must return correct upper bound of dispatch weight. The return value
|
||||
/// of this function is expected to match return value of the corresponding
|
||||
/// `From<Chain>InboundLaneApi::message_details().dispatch_weight` call.
|
||||
fn dispatch_weight(
|
||||
message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight;
|
||||
|
||||
/// Called when inbound message is received.
|
||||
///
|
||||
/// It is up to the implementers of this trait to determine whether the message
|
||||
/// is invalid (i.e. improperly encoded, has too large weight, ...) or not.
|
||||
fn dispatch(
|
||||
message: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult>;
|
||||
}
|
||||
|
||||
/// Manages payments that are happening at the target chain during message delivery transaction.
|
||||
pub trait DeliveryPayments<AccountId> {
|
||||
/// Error type.
|
||||
type Error: Debug + Into<&'static str>;
|
||||
|
||||
/// Pay rewards for delivering messages to the given relayer.
|
||||
///
|
||||
/// This method is called during message delivery transaction which has been submitted
|
||||
/// by the `relayer`. The transaction brings `total_messages` messages but only
|
||||
/// `valid_messages` have been accepted. The post-dispatch transaction weight is the
|
||||
/// `actual_weight`.
|
||||
fn pay_reward(
|
||||
relayer: AccountId,
|
||||
total_messages: MessageNonce,
|
||||
valid_messages: MessageNonce,
|
||||
actual_weight: Weight,
|
||||
);
|
||||
}
|
||||
|
||||
impl<Message> Default for ProvedLaneMessages<Message> {
|
||||
fn default() -> Self {
|
||||
ProvedLaneMessages { lane_state: None, messages: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<DispatchPayload: Decode, LaneId: Encode> From<Message<LaneId>>
|
||||
for DispatchMessage<DispatchPayload, LaneId>
|
||||
{
|
||||
fn from(message: Message<LaneId>) -> Self {
|
||||
DispatchMessage { key: message.key, data: message.payload.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<DispatchPayload: Decode> From<MessagePayload> for DispatchMessageData<DispatchPayload> {
|
||||
fn from(payload: MessagePayload) -> Self {
|
||||
DispatchMessageData { payload: DispatchPayload::decode(&mut &payload[..]) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId> DeliveryPayments<AccountId> for () {
|
||||
type Error = &'static str;
|
||||
|
||||
fn pay_reward(
|
||||
_relayer: AccountId,
|
||||
_total_messages: MessageNonce,
|
||||
_valid_messages: MessageNonce,
|
||||
_actual_weight: Weight,
|
||||
) {
|
||||
// this implementation is not rewarding relayer at all
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure that may be used in place of `MessageDispatch` on chains,
|
||||
/// where inbound messages are forbidden.
|
||||
pub struct ForbidInboundMessages<DispatchPayload, LaneId>(PhantomData<(DispatchPayload, LaneId)>);
|
||||
|
||||
impl<DispatchPayload: Decode, LaneId: Encode> MessageDispatch
|
||||
for ForbidInboundMessages<DispatchPayload, LaneId>
|
||||
{
|
||||
type DispatchPayload = DispatchPayload;
|
||||
type DispatchLevelResult = ();
|
||||
type LaneId = LaneId;
|
||||
|
||||
fn is_active(_: LaneId) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn dispatch_weight(
|
||||
_message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
|
||||
fn dispatch(
|
||||
_: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
|
||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
[package]
|
||||
name = "bp-pezkuwi-core"
|
||||
description = "Primitives of Pezkuwi-like runtime."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-pezkuwi-core"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { optional = true, features = [
|
||||
"derive",
|
||||
], workspace = true, default-features = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-messages = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Based Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-messages/std",
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"scale-info/std",
|
||||
"serde",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,389 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of the Pezkuwi-like chains.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use pezbp_runtime::{
|
||||
self,
|
||||
extensions::{
|
||||
ChargeTransactionPayment, CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce,
|
||||
CheckSpecVersion, CheckTxVersion, CheckWeight, GenericTransactionExtension,
|
||||
TransactionExtensionSchema,
|
||||
},
|
||||
EncodedOrDecodedCall, StorageMapKeyProvider, TransactionEra,
|
||||
};
|
||||
use pezframe_support::{
|
||||
dispatch::DispatchClass,
|
||||
parameter_types,
|
||||
weights::{
|
||||
constants::{BlockExecutionWeight, WEIGHT_REF_TIME_PER_SECOND},
|
||||
Weight,
|
||||
},
|
||||
Blake2_128Concat,
|
||||
};
|
||||
use pezframe_system::limits;
|
||||
use pezsp_core::{storage::StorageKey, Hasher as HasherT};
|
||||
use pezsp_runtime::{
|
||||
generic,
|
||||
traits::{BlakeTwo256, IdentifyAccount, Verify},
|
||||
MultiAddress, MultiSignature, OpaqueExtrinsic,
|
||||
};
|
||||
use pezsp_std::prelude::Vec;
|
||||
|
||||
// Re-export's to avoid extra bizinikiwi dependencies in chain-specific crates.
|
||||
pub use pezframe_support::{weights::constants::ExtrinsicBaseWeight, Parameter};
|
||||
pub use pezsp_runtime::{traits::Convert, Perbill};
|
||||
|
||||
pub mod teyrchains;
|
||||
|
||||
/// Maximal number of GRANDPA authorities at Pezkuwi-like chains.
|
||||
///
|
||||
/// Ideally, we would set it to the value of `MaxAuthorities` constant from bridged runtime
|
||||
/// configurations. But right now it is set to the `100_000`, which makes PoV size for
|
||||
/// our bridge hub teyrchains huge. So let's stick to the real-world value here.
|
||||
///
|
||||
/// Right now both Kusama and Pezkuwi aim to have around 1000 validators. Let's be safe here and
|
||||
/// take a bit more here.
|
||||
pub const MAX_AUTHORITIES_COUNT: u32 = 1_256;
|
||||
|
||||
/// Reasonable number of headers in the `votes_ancestries` on Pezkuwi-like chains.
|
||||
///
|
||||
/// See [`bp-header-pez-chain::ChainWithGrandpa`] for more details.
|
||||
///
|
||||
/// This value comes from recent (December, 2023) Kusama and Pezkuwi headers. There are no
|
||||
/// justifications with any additional headers in votes ancestry, so reasonable headers may
|
||||
/// be set to zero. But we assume that there may be small GRANDPA lags, so we're leaving some
|
||||
/// reserve here.
|
||||
pub const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 2;
|
||||
|
||||
/// Average header size in `votes_ancestries` field of justification on Pezkuwi-like
|
||||
/// chains.
|
||||
///
|
||||
/// See [`bp-header-pez-chain::ChainWithGrandpa`] for more details.
|
||||
///
|
||||
/// This value comes from recent (December, 2023) Kusama headers. Most of headers are `327` bytes
|
||||
/// there, but let's have some reserve and make it 1024.
|
||||
pub const AVERAGE_HEADER_SIZE: u32 = 1024;
|
||||
|
||||
/// Approximate maximal header size on Pezkuwi-like chains.
|
||||
///
|
||||
/// See [`bp-header-pez-chain::ChainWithGrandpa`] for more details.
|
||||
///
|
||||
/// This value comes from recent (December, 2023) Kusama headers. Maximal header is a mandatory
|
||||
/// header. In its SCALE-encoded form it is `113407` bytes. Let's have some reserve here.
|
||||
pub const MAX_MANDATORY_HEADER_SIZE: u32 = 120 * 1024;
|
||||
|
||||
/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at
|
||||
/// Pezkuwi-like chain. This mostly depends on number of entries in the storage trie.
|
||||
/// Some reserve is reserved to account future chain growth.
|
||||
///
|
||||
/// To compute this value, we've synced Kusama chain blocks [0; 6545733] to see if there were
|
||||
/// any significant changes of the storage proof size (NO):
|
||||
///
|
||||
/// - at block 3072 the storage proof size overhead was 579 bytes;
|
||||
/// - at block 2479616 it was 578 bytes;
|
||||
/// - at block 4118528 it was 711 bytes;
|
||||
/// - at block 6540800 it was 779 bytes.
|
||||
///
|
||||
/// The number of storage entries at the block 6546170 was 351207 and number of trie nodes in
|
||||
/// the storage proof was 5 (log(16, 351207) ~ 4.6).
|
||||
///
|
||||
/// So the assumption is that the storage proof size overhead won't be larger than 1024 in the
|
||||
/// nearest future. If it'll ever break this barrier, then we'll need to update this constant
|
||||
/// at next runtime upgrade.
|
||||
pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
|
||||
|
||||
/// All Pezkuwi-like chains allow normal extrinsics to fill block up to 75 percent.
|
||||
///
|
||||
/// This is a copy-paste from the Pezkuwi repo's `pezkuwi-runtime-common` crate.
|
||||
const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
|
||||
|
||||
/// All Pezkuwi-like chains allow 2 seconds of compute with a 6-second average block time.
|
||||
///
|
||||
/// This is a copy-paste from the Pezkuwi repo's `pezkuwi-runtime-common` crate.
|
||||
pub const MAXIMUM_BLOCK_WEIGHT: Weight =
|
||||
Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), u64::MAX);
|
||||
|
||||
/// All Pezkuwi-like chains assume that an on-initialize consumes 1 percent of the weight on
|
||||
/// average, hence a single extrinsic will not be allowed to consume more than
|
||||
/// `AvailableBlockRatio - 1 percent`.
|
||||
///
|
||||
/// This is a copy-paste from the Pezkuwi repo's `pezkuwi-runtime-common` crate.
|
||||
pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(1);
|
||||
|
||||
parameter_types! {
|
||||
/// All Pezkuwi-like chains have maximal block size set to 5MB.
|
||||
///
|
||||
/// This is a copy-paste from the Pezkuwi repo's `pezkuwi-runtime-common` crate.
|
||||
pub BlockLength: limits::BlockLength = limits::BlockLength::max_with_normal_ratio(
|
||||
5 * 1024 * 1024,
|
||||
NORMAL_DISPATCH_RATIO,
|
||||
);
|
||||
/// All Pezkuwi-like chains have the same block weights.
|
||||
///
|
||||
/// This is a copy-paste from the Pezkuwi repo's `pezkuwi-runtime-common` crate.
|
||||
pub BlockWeights: limits::BlockWeights = limits::BlockWeights::builder()
|
||||
.base_block(BlockExecutionWeight::get())
|
||||
.for_class(DispatchClass::all(), |weights| {
|
||||
weights.base_extrinsic = ExtrinsicBaseWeight::get();
|
||||
})
|
||||
.for_class(DispatchClass::Normal, |weights| {
|
||||
weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT);
|
||||
})
|
||||
.for_class(DispatchClass::Operational, |weights| {
|
||||
weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT);
|
||||
// Operational transactions have an extra reserved space, so that they
|
||||
// are included even if block reached `MAXIMUM_BLOCK_WEIGHT`.
|
||||
weights.reserved = Some(
|
||||
MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT,
|
||||
);
|
||||
})
|
||||
.avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO)
|
||||
.build_or_panic();
|
||||
}
|
||||
|
||||
// TODO [#78] may need to be updated after https://github.com/pezkuwichain/kurdistan-sdk/issues/88
|
||||
/// Maximal number of messages in single delivery transaction.
|
||||
pub const MAX_MESSAGES_IN_DELIVERY_TRANSACTION: MessageNonce = 128;
|
||||
|
||||
/// Maximal number of bytes, included in the signed Pezkuwi-like transaction apart from the encoded
|
||||
/// call itself.
|
||||
///
|
||||
/// Can be computed by subtracting encoded call size from raw transaction size.
|
||||
pub const TX_EXTRA_BYTES: u32 = 256;
|
||||
|
||||
/// Re-export `time_units` to make usage easier.
|
||||
pub use time_units::*;
|
||||
|
||||
/// Human readable time units defined in terms of number of blocks.
|
||||
pub mod time_units {
|
||||
use super::BlockNumber;
|
||||
|
||||
/// Milliseconds between Pezkuwi-like chain blocks.
|
||||
pub const MILLISECS_PER_BLOCK: u64 = 6000;
|
||||
/// Slot duration in Pezkuwi-like chain consensus algorithms.
|
||||
pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
|
||||
|
||||
/// A minute, expressed in Pezkuwi-like chain blocks.
|
||||
pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
|
||||
/// A hour, expressed in Pezkuwi-like chain blocks.
|
||||
pub const HOURS: BlockNumber = MINUTES * 60;
|
||||
/// A day, expressed in Pezkuwi-like chain blocks.
|
||||
pub const DAYS: BlockNumber = HOURS * 24;
|
||||
}
|
||||
|
||||
/// Block number type used in Pezkuwi-like chains.
|
||||
pub type BlockNumber = u32;
|
||||
|
||||
/// Hash type used in Pezkuwi-like chains.
|
||||
pub type Hash = <BlakeTwo256 as HasherT>::Out;
|
||||
|
||||
/// Hashing type.
|
||||
pub type Hashing = BlakeTwo256;
|
||||
|
||||
/// The type of object that can produce hashes on Pezkuwi-like chains.
|
||||
pub type Hasher = BlakeTwo256;
|
||||
|
||||
/// The header type used by Pezkuwi-like chains.
|
||||
pub type Header = generic::Header<BlockNumber, Hasher>;
|
||||
|
||||
/// Signature type used by Pezkuwi-like chains.
|
||||
pub type Signature = MultiSignature;
|
||||
|
||||
/// Public key of account on Pezkuwi-like chains.
|
||||
pub type AccountPublic = <Signature as Verify>::Signer;
|
||||
|
||||
/// Id of account on Pezkuwi-like chains.
|
||||
pub type AccountId = <AccountPublic as IdentifyAccount>::AccountId;
|
||||
|
||||
/// Address of account on Pezkuwi-like chains.
|
||||
pub type AccountAddress = MultiAddress<AccountId, ()>;
|
||||
|
||||
/// Nonce of a transaction on the Pezkuwi-like chains.
|
||||
pub type Nonce = u32;
|
||||
|
||||
/// Block type of Pezkuwi-like chains.
|
||||
pub type Block = generic::Block<Header, OpaqueExtrinsic>;
|
||||
|
||||
/// Pezkuwi-like block signed with a Justification.
|
||||
pub type SignedBlock = generic::SignedBlock<Block>;
|
||||
|
||||
/// The balance of an account on Pezkuwi-like chain.
|
||||
pub type Balance = u128;
|
||||
|
||||
/// Unchecked Extrinsic type.
|
||||
pub type UncheckedExtrinsic<Call, TransactionExt> = generic::UncheckedExtrinsic<
|
||||
AccountAddress,
|
||||
EncodedOrDecodedCall<Call>,
|
||||
Signature,
|
||||
TransactionExt,
|
||||
>;
|
||||
|
||||
/// Account address, used by the Pezkuwi-like chain.
|
||||
pub type Address = MultiAddress<AccountId, ()>;
|
||||
|
||||
/// Returns maximal extrinsic size on all Pezkuwi-like chains.
|
||||
pub fn max_extrinsic_size() -> u32 {
|
||||
*BlockLength::get().max.get(DispatchClass::Normal)
|
||||
}
|
||||
|
||||
/// Returns maximal extrinsic weight on all Pezkuwi-like chains.
|
||||
pub fn max_extrinsic_weight() -> Weight {
|
||||
BlockWeights::get()
|
||||
.get(DispatchClass::Normal)
|
||||
.max_extrinsic
|
||||
.unwrap_or(Weight::MAX)
|
||||
}
|
||||
|
||||
/// Provides a storage key for account data.
|
||||
///
|
||||
/// We need to use this approach when we don't have access to the runtime.
|
||||
/// The equivalent command to invoke in case full `Runtime` is known is this:
|
||||
/// `let key = pezframe_system::Account::<Runtime>::storage_map_final_key(&account_id);`
|
||||
pub struct AccountInfoStorageMapKeyProvider;
|
||||
|
||||
impl StorageMapKeyProvider for AccountInfoStorageMapKeyProvider {
|
||||
const MAP_NAME: &'static str = "Account";
|
||||
type Hasher = Blake2_128Concat;
|
||||
type Key = AccountId;
|
||||
// This should actually be `AccountInfo`, but we don't use this property in order to decode the
|
||||
// data. So we use `Vec<u8>` as if we would work with encoded data.
|
||||
type Value = Vec<u8>;
|
||||
}
|
||||
|
||||
impl AccountInfoStorageMapKeyProvider {
|
||||
/// Name of the system pezpallet.
|
||||
const PALLET_NAME: &'static str = "System";
|
||||
|
||||
/// Return storage key for given account data.
|
||||
pub fn final_key(id: &AccountId) -> StorageKey {
|
||||
<Self as StorageMapKeyProvider>::final_key(Self::PALLET_NAME, id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra signed extension data that is used by most chains.
|
||||
pub type CommonTransactionExtra = (
|
||||
CheckNonZeroSender,
|
||||
CheckSpecVersion,
|
||||
CheckTxVersion,
|
||||
CheckGenesis<Hash>,
|
||||
CheckEra<Hash>,
|
||||
CheckNonce<Nonce>,
|
||||
CheckWeight,
|
||||
ChargeTransactionPayment<Balance>,
|
||||
);
|
||||
|
||||
/// Extra transaction extension data that starts with `CommonTransactionExtra`.
|
||||
pub type SuffixedCommonTransactionExtension<Suffix> =
|
||||
GenericTransactionExtension<(CommonTransactionExtra, Suffix)>;
|
||||
|
||||
/// Helper trait to define some extra methods on `SuffixedCommonTransactionExtension`.
|
||||
pub trait SuffixedCommonTransactionExtensionExt<Suffix: TransactionExtensionSchema> {
|
||||
/// Create signed extension from its components.
|
||||
fn from_params(
|
||||
spec_version: u32,
|
||||
transaction_version: u32,
|
||||
era: TransactionEra<BlockNumber, Hash>,
|
||||
genesis_hash: Hash,
|
||||
nonce: Nonce,
|
||||
tip: Balance,
|
||||
extra: (Suffix::Payload, Suffix::Implicit),
|
||||
) -> Self;
|
||||
|
||||
/// Return transaction nonce.
|
||||
fn nonce(&self) -> Nonce;
|
||||
|
||||
/// Return transaction tip.
|
||||
fn tip(&self) -> Balance;
|
||||
}
|
||||
|
||||
impl<Suffix> SuffixedCommonTransactionExtensionExt<Suffix>
|
||||
for SuffixedCommonTransactionExtension<Suffix>
|
||||
where
|
||||
Suffix: TransactionExtensionSchema,
|
||||
{
|
||||
fn from_params(
|
||||
spec_version: u32,
|
||||
transaction_version: u32,
|
||||
era: TransactionEra<BlockNumber, Hash>,
|
||||
genesis_hash: Hash,
|
||||
nonce: Nonce,
|
||||
tip: Balance,
|
||||
extra: (Suffix::Payload, Suffix::Implicit),
|
||||
) -> Self {
|
||||
GenericTransactionExtension::new(
|
||||
(
|
||||
(
|
||||
(), // non-zero sender
|
||||
(), // spec version
|
||||
(), // tx version
|
||||
(), // genesis
|
||||
era.frame_era(), // era
|
||||
nonce.into(), // nonce (compact encoding)
|
||||
(), // Check weight
|
||||
tip.into(), // transaction payment / tip (compact encoding)
|
||||
),
|
||||
extra.0,
|
||||
),
|
||||
Some((
|
||||
(
|
||||
(),
|
||||
spec_version,
|
||||
transaction_version,
|
||||
genesis_hash,
|
||||
era.signed_payload(genesis_hash),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
),
|
||||
extra.1,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
fn nonce(&self) -> Nonce {
|
||||
let common_payload = self.payload.0;
|
||||
common_payload.5 .0
|
||||
}
|
||||
|
||||
fn tip(&self) -> Balance {
|
||||
let common_payload = self.payload.0;
|
||||
common_payload.7 .0
|
||||
}
|
||||
}
|
||||
|
||||
/// Signed extension that is used by most chains.
|
||||
pub type CommonTransactionExtension = SuffixedCommonTransactionExtension<()>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_generate_storage_key() {
|
||||
let acc = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 26, 27, 28, 29, 30, 31, 32,
|
||||
]
|
||||
.into();
|
||||
let key = AccountInfoStorageMapKeyProvider::final_key(&acc);
|
||||
assert_eq!(hex::encode(key), "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da92dccd599abfe1920a1cff8a7358231430102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of pezkuwi-like chains, that are related to teyrchains functionality.
|
||||
//!
|
||||
//! Even though this (bridges) repository references pezkuwi repository, we can't
|
||||
//! reference pezkuwi crates from pallets. That's because bridges repository is
|
||||
//! included in the Pezcumulus repository and included pallets are used by Pezcumulus
|
||||
//! teyrchains. Having pallets that are referencing pezkuwi, would mean that there may
|
||||
//! be two versions of pezkuwi crates included in the runtime. Which is bad.
|
||||
|
||||
use pezbp_runtime::{raw_storage_proof_size, RawStorageProof, Size};
|
||||
use codec::{CompactAs, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::Hasher;
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
use pezsp_std::vec::Vec;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Teyrchain id.
|
||||
///
|
||||
/// This is an equivalent of the `pezkuwi_teyrchain_primitives::Id`, which is a compact-encoded
|
||||
/// `u32`.
|
||||
#[derive(
|
||||
Clone,
|
||||
CompactAs,
|
||||
Copy,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Default,
|
||||
Encode,
|
||||
Eq,
|
||||
Hash,
|
||||
MaxEncodedLen,
|
||||
Ord,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub struct ParaId(pub u32);
|
||||
|
||||
impl From<u32> for ParaId {
|
||||
fn from(id: u32) -> Self {
|
||||
ParaId(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Teyrchain head.
|
||||
///
|
||||
/// This is an equivalent of the `pezkuwi_teyrchain_primitives::HeadData`.
|
||||
///
|
||||
/// The teyrchain head means (at least in Pezcumulus) a SCALE-encoded teyrchain header.
|
||||
#[derive(
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
Default,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash))]
|
||||
pub struct ParaHead(pub Vec<u8>);
|
||||
|
||||
impl ParaHead {
|
||||
/// Returns the hash of this head data.
|
||||
pub fn hash(&self) -> crate::Hash {
|
||||
pezsp_runtime::traits::BlakeTwo256::hash(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Teyrchain head hash.
|
||||
pub type ParaHash = crate::Hash;
|
||||
|
||||
/// Teyrchain head hasher.
|
||||
pub type ParaHasher = crate::Hasher;
|
||||
|
||||
/// Raw storage proof of teyrchain heads, stored in pezkuwi-like chain runtime.
|
||||
#[derive(Clone, Decode, DecodeWithMemTracking, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct ParaHeadsProof {
|
||||
/// Unverified storage proof of finalized teyrchain heads.
|
||||
pub storage_proof: RawStorageProof,
|
||||
}
|
||||
|
||||
impl Size for ParaHeadsProof {
|
||||
fn size(&self) -> u32 {
|
||||
use pezframe_support::pezsp_runtime::SaturatedConversion;
|
||||
raw_storage_proof_size(&self.storage_proof).saturated_into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
[package]
|
||||
name = "bp-relayers"
|
||||
description = "Primitives of relayers module."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-relayers"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["bit-vec", "derive"], workspace = true }
|
||||
scale-info = { features = ["bit-vec", "derive"], workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-messages = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
bp-teyrchains = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-utility = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-messages/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-teyrchains/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-utility/std",
|
||||
"scale-info/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-teyrchains/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-utility/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! All runtime calls, supported by `pezpallet-bridge-relayers` when it acts as a signed
|
||||
//! extension.
|
||||
|
||||
use bp_header_pez_chain::SubmitFinalityProofInfo;
|
||||
use bp_messages::MessagesCallInfo;
|
||||
use pezbp_runtime::StaticStrProvider;
|
||||
use bp_teyrchains::SubmitTeyrchainHeadsInfo;
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{
|
||||
dispatch::CallableCallFor, traits::IsSubType, weights::Weight, RuntimeDebugNoBound,
|
||||
};
|
||||
use pezframe_system::Config as SystemConfig;
|
||||
use pezpallet_utility::{Call as UtilityCall, Pezpallet as UtilityPallet};
|
||||
use pezsp_runtime::{
|
||||
traits::Get,
|
||||
transaction_validity::{TransactionPriority, TransactionValidityError},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use pezsp_std::{fmt::Debug, marker::PhantomData, vec, vec::Vec};
|
||||
|
||||
/// Type of the call that the signed extension recognizes.
|
||||
#[derive(PartialEq, RuntimeDebugNoBound)]
|
||||
pub enum ExtensionCallInfo<RemoteGrandpaChainBlockNumber: Debug, LaneId: Clone + Copy + Debug> {
|
||||
/// Relay chain finality + teyrchain finality + message delivery/confirmation calls.
|
||||
AllFinalityAndMsgs(
|
||||
SubmitFinalityProofInfo<RemoteGrandpaChainBlockNumber>,
|
||||
SubmitTeyrchainHeadsInfo,
|
||||
MessagesCallInfo<LaneId>,
|
||||
),
|
||||
/// Relay chain finality + message delivery/confirmation calls.
|
||||
RelayFinalityAndMsgs(
|
||||
SubmitFinalityProofInfo<RemoteGrandpaChainBlockNumber>,
|
||||
MessagesCallInfo<LaneId>,
|
||||
),
|
||||
/// Teyrchain finality + message delivery/confirmation calls.
|
||||
///
|
||||
/// This variant is used only when bridging with teyrchain.
|
||||
TeyrchainFinalityAndMsgs(SubmitTeyrchainHeadsInfo, MessagesCallInfo<LaneId>),
|
||||
/// Standalone message delivery/confirmation call.
|
||||
Msgs(MessagesCallInfo<LaneId>),
|
||||
}
|
||||
|
||||
impl<RemoteGrandpaChainBlockNumber: Clone + Copy + Debug, LaneId: Clone + Copy + Debug>
|
||||
ExtensionCallInfo<RemoteGrandpaChainBlockNumber, LaneId>
|
||||
{
|
||||
/// Returns true if call is a message delivery call (with optional finality calls).
|
||||
pub fn is_receive_messages_proof_call(&self) -> bool {
|
||||
match self.messages_call_info() {
|
||||
MessagesCallInfo::ReceiveMessagesProof(_) => true,
|
||||
MessagesCallInfo::ReceiveMessagesDeliveryProof(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the pre-dispatch `finality_target` sent to the `SubmitFinalityProof` call.
|
||||
pub fn submit_finality_proof_info(
|
||||
&self,
|
||||
) -> Option<SubmitFinalityProofInfo<RemoteGrandpaChainBlockNumber>> {
|
||||
match *self {
|
||||
Self::AllFinalityAndMsgs(info, _, _) => Some(info),
|
||||
Self::RelayFinalityAndMsgs(info, _) => Some(info),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the pre-dispatch `SubmitTeyrchainHeadsInfo`.
|
||||
pub fn submit_teyrchain_heads_info(&self) -> Option<&SubmitTeyrchainHeadsInfo> {
|
||||
match self {
|
||||
Self::AllFinalityAndMsgs(_, info, _) => Some(info),
|
||||
Self::TeyrchainFinalityAndMsgs(info, _) => Some(info),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the pre-dispatch `ReceiveMessagesProofInfo`.
|
||||
pub fn messages_call_info(&self) -> &MessagesCallInfo<LaneId> {
|
||||
match self {
|
||||
Self::AllFinalityAndMsgs(_, _, info) => info,
|
||||
Self::RelayFinalityAndMsgs(_, info) => info,
|
||||
Self::TeyrchainFinalityAndMsgs(_, info) => info,
|
||||
Self::Msgs(info) => info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra post-dispatch data, associated with the supported runtime call.
|
||||
#[derive(Default, RuntimeDebug)]
|
||||
pub struct ExtensionCallData {
|
||||
/// Extra weight, consumed by the call. We have some assumptions about normal weight
|
||||
/// that may be consumed by expected calls. If the actual weight is larger than that,
|
||||
/// we do not refund relayer for this extra weight.
|
||||
pub extra_weight: Weight,
|
||||
/// Extra size, consumed by the call. We have some assumptions about normal size
|
||||
/// of the encoded call. If the actual size is larger than that, we do not refund relayer
|
||||
/// for this extra size.
|
||||
pub extra_size: u32,
|
||||
}
|
||||
|
||||
/// Signed extension configuration.
|
||||
///
|
||||
/// The single `pezpallet-bridge-relayers` instance may be shared by multiple messages
|
||||
/// pezpallet instances, bridging with different remote networks. We expect every instance
|
||||
/// of the messages pezpallet to add a separate signed extension to runtime. So it must
|
||||
/// have a separate configuration.
|
||||
pub trait ExtensionConfig {
|
||||
/// Unique identifier of the signed extension that will use this configuration.
|
||||
type IdProvider: StaticStrProvider;
|
||||
/// Runtime that optionally supports batched calls. We assume that batched call
|
||||
/// succeeds if and only if all of its nested calls succeed.
|
||||
type Runtime: pezframe_system::Config;
|
||||
/// Relayers pezpallet instance.
|
||||
type BridgeRelayersPalletInstance: 'static;
|
||||
/// Messages pezpallet instance.
|
||||
type BridgeMessagesPalletInstance: 'static;
|
||||
/// Additional priority that is added to base message delivery transaction priority
|
||||
/// for every additional bundled message.
|
||||
type PriorityBoostPerMessage: Get<TransactionPriority>;
|
||||
/// Block number for the remote **GRANDPA chain**. Mind that this chain is not
|
||||
/// necessarily the chain that we are bridging with. If we are bridging with
|
||||
/// teyrchain, it must be its parent relay chain. If we are bridging with the
|
||||
/// GRANDPA chain, it must be it.
|
||||
type RemoteGrandpaChainBlockNumber: Clone + Copy + Debug;
|
||||
/// Lane identifier type.
|
||||
type LaneId: Clone + Copy + Decode + Encode + Debug;
|
||||
|
||||
/// Given runtime call, check if it is supported by the transaction extension. Additionally,
|
||||
/// check if call (or any of batched calls) are obsolete.
|
||||
fn parse_and_check_for_obsolete_call(
|
||||
call: &<Self::Runtime as SystemConfig>::RuntimeCall,
|
||||
) -> Result<
|
||||
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
|
||||
TransactionValidityError,
|
||||
>;
|
||||
|
||||
/// Check if runtime call is already obsolete.
|
||||
fn check_obsolete_parsed_call(
|
||||
call: &<Self::Runtime as SystemConfig>::RuntimeCall,
|
||||
) -> Result<&<Self::Runtime as SystemConfig>::RuntimeCall, TransactionValidityError>;
|
||||
|
||||
/// Given runtime call info, check that this call has been successful and has updated
|
||||
/// runtime storage accordingly.
|
||||
fn check_call_result(
|
||||
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
|
||||
call_data: &mut ExtensionCallData,
|
||||
relayer: &<Self::Runtime as SystemConfig>::AccountId,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
/// Something that can unpack batch calls (all-or-nothing flavor) of given size.
|
||||
pub trait BatchCallUnpacker<Runtime: pezframe_system::Config> {
|
||||
/// Unpack batch call with no more than `max_packed_calls` calls.
|
||||
fn unpack(call: &Runtime::RuntimeCall, max_packed_calls: u32) -> Vec<&Runtime::RuntimeCall>;
|
||||
}
|
||||
|
||||
/// An `BatchCallUnpacker` adapter for runtimes with utility pezpallet.
|
||||
pub struct RuntimeWithUtilityPallet<Runtime>(PhantomData<Runtime>);
|
||||
|
||||
impl<Runtime> BatchCallUnpacker<Runtime> for RuntimeWithUtilityPallet<Runtime>
|
||||
where
|
||||
Runtime: pezpallet_utility::Config<RuntimeCall = <Runtime as SystemConfig>::RuntimeCall>,
|
||||
<Runtime as SystemConfig>::RuntimeCall:
|
||||
IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>,
|
||||
{
|
||||
fn unpack(
|
||||
call: &<Runtime as pezframe_system::Config>::RuntimeCall,
|
||||
max_packed_calls: u32,
|
||||
) -> Vec<&<Runtime as pezframe_system::Config>::RuntimeCall> {
|
||||
match call.is_sub_type() {
|
||||
Some(UtilityCall::<Runtime>::batch_all { ref calls })
|
||||
if calls.len() <= max_packed_calls as usize =>
|
||||
calls.iter().collect(),
|
||||
Some(_) => vec![],
|
||||
None => vec![call],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Runtime: pezframe_system::Config> BatchCallUnpacker<Runtime> for () {
|
||||
fn unpack(call: &Runtime::RuntimeCall, _max_packed_calls: u32) -> Vec<&Runtime::RuntimeCall> {
|
||||
vec![call]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of messages module.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use extension::{
|
||||
BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig,
|
||||
RuntimeWithUtilityPallet,
|
||||
};
|
||||
pub use registration::{ExplicitOrAccountParams, Registration, StakeAndSlash};
|
||||
|
||||
use pezbp_runtime::{ChainId, StorageDoubleMapKeyProvider};
|
||||
use pezframe_support::{traits::tokens::Preservation, Blake2_128Concat, Identity};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::{
|
||||
codec::{Codec, Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen},
|
||||
traits::AccountIdConversion,
|
||||
TypeId,
|
||||
};
|
||||
use pezsp_std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
mod extension;
|
||||
mod registration;
|
||||
|
||||
/// The owner of the sovereign account that should pay the rewards.
|
||||
///
|
||||
/// Each of the 2 final points connected by a bridge owns a sovereign account at each end of the
|
||||
/// bridge. So here, at this end of the bridge there can be 2 sovereign accounts that pay rewards.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Encode,
|
||||
Eq,
|
||||
PartialEq,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub enum RewardsAccountOwner {
|
||||
/// The sovereign account of the final chain on this end of the bridge.
|
||||
ThisChain,
|
||||
/// The sovereign account of the final chain on the other end of the bridge.
|
||||
BridgedChain,
|
||||
}
|
||||
|
||||
/// Structure used to identify the account that pays a reward to the relayer.
|
||||
///
|
||||
/// A bridge connects 2 bridge ends. Each one is located on a separate relay chain. The bridge ends
|
||||
/// can be the final destinations of the bridge, or they can be intermediary points
|
||||
/// (e.g. a bridge hub) used to forward messages between pairs of teyrchains on the bridged relay
|
||||
/// chains. A pair of such teyrchains is connected using a bridge lane. Each of the 2 final
|
||||
/// destinations of a bridge lane must have a sovereign account at each end of the bridge and each
|
||||
/// of the sovereign accounts will pay rewards for different operations. So we need multiple
|
||||
/// parameters to identify the account that pays a reward to the relayer.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Encode,
|
||||
Eq,
|
||||
PartialEq,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct RewardsAccountParams<LaneId> {
|
||||
// **IMPORTANT NOTE**: the order of fields here matters - we are using
|
||||
// `into_account_truncating` and lane id is already `32` byte, so if other fields are encoded
|
||||
// after it, they're simply dropped. So lane id shall be the last field.
|
||||
owner: RewardsAccountOwner,
|
||||
bridged_chain_id: ChainId,
|
||||
lane_id: LaneId,
|
||||
}
|
||||
|
||||
impl<LaneId: Decode + Encode> RewardsAccountParams<LaneId> {
|
||||
/// Create a new instance of `RewardsAccountParams`.
|
||||
pub const fn new(
|
||||
lane_id: LaneId,
|
||||
bridged_chain_id: ChainId,
|
||||
owner: RewardsAccountOwner,
|
||||
) -> Self {
|
||||
Self { lane_id, bridged_chain_id, owner }
|
||||
}
|
||||
|
||||
/// Getter for `lane_id`.
|
||||
pub const fn lane_id(&self) -> &LaneId {
|
||||
&self.lane_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<LaneId: Decode + Encode> TypeId for RewardsAccountParams<LaneId> {
|
||||
const TYPE_ID: [u8; 4] = *b"brap";
|
||||
}
|
||||
|
||||
/// Reward payment procedure.
|
||||
pub trait PaymentProcedure<Relayer, Reward, RewardBalance> {
|
||||
/// Error that may be returned by the procedure.
|
||||
type Error: Debug;
|
||||
|
||||
/// Type parameter used to identify the beneficiaries eligible to receive rewards.
|
||||
type Beneficiary: Clone + Debug + Decode + Encode + Eq + TypeInfo;
|
||||
|
||||
/// Pay reward to the relayer (or alternative beneficiary if provided) from the account with
|
||||
/// provided params.
|
||||
fn pay_reward(
|
||||
relayer: &Relayer,
|
||||
reward: Reward,
|
||||
reward_balance: RewardBalance,
|
||||
beneficiary: Self::Beneficiary,
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl<Relayer, Reward, RewardBalance> PaymentProcedure<Relayer, Reward, RewardBalance> for () {
|
||||
type Error = &'static str;
|
||||
type Beneficiary = ();
|
||||
|
||||
fn pay_reward(
|
||||
_: &Relayer,
|
||||
_: Reward,
|
||||
_: RewardBalance,
|
||||
_: Self::Beneficiary,
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Reward payment procedure that executes a `balances::transfer` call from the account
|
||||
/// derived from the given `RewardsAccountParams` to the relayer or an alternative beneficiary.
|
||||
pub struct PayRewardFromAccount<T, Relayer, LaneId, RewardBalance>(
|
||||
PhantomData<(T, Relayer, LaneId, RewardBalance)>,
|
||||
);
|
||||
|
||||
impl<T, Relayer, LaneId, RewardBalance> PayRewardFromAccount<T, Relayer, LaneId, RewardBalance>
|
||||
where
|
||||
Relayer: Decode + Encode,
|
||||
LaneId: Decode + Encode,
|
||||
{
|
||||
/// Return account that pays rewards based on the provided parameters.
|
||||
pub fn rewards_account(params: RewardsAccountParams<LaneId>) -> Relayer {
|
||||
params.into_sub_account_truncating(b"rewards-account")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Relayer, LaneId, RewardBalance>
|
||||
PaymentProcedure<Relayer, RewardsAccountParams<LaneId>, RewardBalance>
|
||||
for PayRewardFromAccount<T, Relayer, LaneId, RewardBalance>
|
||||
where
|
||||
T: pezframe_support::traits::fungible::Mutate<Relayer>,
|
||||
T::Balance: From<RewardBalance>,
|
||||
Relayer: Clone + Debug + Decode + Encode + Eq + TypeInfo,
|
||||
LaneId: Decode + Encode,
|
||||
{
|
||||
type Error = pezsp_runtime::DispatchError;
|
||||
type Beneficiary = Relayer;
|
||||
|
||||
fn pay_reward(
|
||||
_: &Relayer,
|
||||
reward_kind: RewardsAccountParams<LaneId>,
|
||||
reward: RewardBalance,
|
||||
beneficiary: Self::Beneficiary,
|
||||
) -> Result<(), Self::Error> {
|
||||
T::transfer(
|
||||
&Self::rewards_account(reward_kind),
|
||||
&beneficiary.into(),
|
||||
reward.into(),
|
||||
Preservation::Expendable,
|
||||
)
|
||||
.map(drop)
|
||||
}
|
||||
}
|
||||
|
||||
/// Can be used to access the runtime storage key within the `RelayerRewards` map of the relayers
|
||||
/// pezpallet.
|
||||
pub struct RelayerRewardsKeyProvider<AccountId, Reward, RewardBalance>(
|
||||
PhantomData<(AccountId, Reward, RewardBalance)>,
|
||||
);
|
||||
|
||||
impl<AccountId, Reward, RewardBalance> StorageDoubleMapKeyProvider
|
||||
for RelayerRewardsKeyProvider<AccountId, Reward, RewardBalance>
|
||||
where
|
||||
AccountId: 'static + Codec + EncodeLike + Send + Sync,
|
||||
Reward: Codec + EncodeLike + Send + Sync,
|
||||
RewardBalance: 'static + Codec + EncodeLike + Send + Sync,
|
||||
{
|
||||
const MAP_NAME: &'static str = "RelayerRewards";
|
||||
|
||||
type Hasher1 = Blake2_128Concat;
|
||||
type Key1 = AccountId;
|
||||
type Hasher2 = Identity;
|
||||
type Key2 = Reward;
|
||||
type Value = RewardBalance;
|
||||
}
|
||||
|
||||
/// A trait defining a reward ledger, which tracks rewards that can be later claimed.
|
||||
///
|
||||
/// This ledger allows registering rewards for a relayer, categorized by a specific `Reward`.
|
||||
/// The registered rewards can be claimed later through an appropriate payment procedure.
|
||||
pub trait RewardLedger<Relayer, Reward, RewardBalance> {
|
||||
/// Registers a reward for a given relayer.
|
||||
fn register_reward(relayer: &Relayer, reward: Reward, reward_balance: RewardBalance);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bp_messages::{HashedLaneId, LaneIdType, LegacyLaneId};
|
||||
use pezsp_runtime::{app_crypto::Ss58Codec, testing::H256};
|
||||
|
||||
#[test]
|
||||
fn different_lanes_are_using_different_accounts() {
|
||||
assert_eq!(
|
||||
PayRewardFromAccount::<(), H256, HashedLaneId, ()>::rewards_account(
|
||||
RewardsAccountParams::new(
|
||||
HashedLaneId::try_new(1, 2).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::ThisChain
|
||||
)
|
||||
),
|
||||
hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc")
|
||||
.into(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PayRewardFromAccount::<(), H256, HashedLaneId, ()>::rewards_account(
|
||||
RewardsAccountParams::new(
|
||||
HashedLaneId::try_new(1, 3).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::ThisChain
|
||||
)
|
||||
),
|
||||
hex_literal::hex!("627261700074657374a43e8951aa302c133beb5f85821a21645f07b487270ef3")
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_directions_are_using_different_accounts() {
|
||||
assert_eq!(
|
||||
PayRewardFromAccount::<(), H256, HashedLaneId, ()>::rewards_account(
|
||||
RewardsAccountParams::new(
|
||||
HashedLaneId::try_new(1, 2).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::ThisChain
|
||||
)
|
||||
),
|
||||
hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc")
|
||||
.into(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PayRewardFromAccount::<(), H256, HashedLaneId, ()>::rewards_account(
|
||||
RewardsAccountParams::new(
|
||||
HashedLaneId::try_new(1, 2).unwrap(),
|
||||
*b"test",
|
||||
RewardsAccountOwner::BridgedChain
|
||||
)
|
||||
),
|
||||
hex_literal::hex!("627261700174657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc")
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pay_reward_from_account_for_legacy_lane_id_works() {
|
||||
let test_data = vec![
|
||||
// Note: these accounts are used for integration tests within
|
||||
// `bridges_pezkuwichain_zagros.sh`
|
||||
(
|
||||
LegacyLaneId([0, 0, 0, 1]),
|
||||
b"bhks",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
(0_u16, "13E5fui97x6KTwNnSjaEKZ8s7kJNot5F3aUsy3jUtuoMyUec"),
|
||||
),
|
||||
(
|
||||
LegacyLaneId([0, 0, 0, 1]),
|
||||
b"bhks",
|
||||
RewardsAccountOwner::BridgedChain,
|
||||
(0_u16, "13E5fui9Ka9Vz4JbGN3xWjmwDNxnxF1N9Hhhbeu3VCqLChuj"),
|
||||
),
|
||||
(
|
||||
LegacyLaneId([0, 0, 0, 1]),
|
||||
b"bhpd",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
(2_u16, "EoQBtnwtXqnSnr9cgBEJpKU7NjeC9EnR4D1VjgcvHz9ZYmS"),
|
||||
),
|
||||
(
|
||||
LegacyLaneId([0, 0, 0, 1]),
|
||||
b"bhpd",
|
||||
RewardsAccountOwner::BridgedChain,
|
||||
(2_u16, "EoQBtnx69txxumxSJexVzxYD1Q4LWAuWmRq8LrBWb27nhYN"),
|
||||
),
|
||||
// Note: these accounts are used for integration tests within
|
||||
// `bridges_pezkuwi_kusama.sh` from fellows.
|
||||
(
|
||||
LegacyLaneId([0, 0, 0, 2]),
|
||||
b"bhwd",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
(4_u16, "SNihsskf7bFhnHH9HJFMjWD3FJ96ESdAQTFZUAtXudRQbaH"),
|
||||
),
|
||||
(
|
||||
LegacyLaneId([0, 0, 0, 2]),
|
||||
b"bhwd",
|
||||
RewardsAccountOwner::BridgedChain,
|
||||
(4_u16, "SNihsskrjeSDuD5xumyYv9H8sxZEbNkG7g5C5LT8CfPdaSE"),
|
||||
),
|
||||
(
|
||||
LegacyLaneId([0, 0, 0, 2]),
|
||||
b"bhro",
|
||||
RewardsAccountOwner::ThisChain,
|
||||
(4_u16, "SNihsskf7bF2vWogkC6uFoiqPhd3dUX6TGzYZ1ocJdo3xHp"),
|
||||
),
|
||||
(
|
||||
LegacyLaneId([0, 0, 0, 2]),
|
||||
b"bhro",
|
||||
RewardsAccountOwner::BridgedChain,
|
||||
(4_u16, "SNihsskrjeRZ3ScWNfq6SSnw2N3BzQeCAVpBABNCbfmHENB"),
|
||||
),
|
||||
];
|
||||
|
||||
for (lane_id, bridged_chain_id, owner, (expected_ss58, expected_account)) in test_data {
|
||||
assert_eq!(
|
||||
expected_account,
|
||||
pezsp_runtime::AccountId32::new(PayRewardFromAccount::<
|
||||
[u8; 32],
|
||||
[u8; 32],
|
||||
LegacyLaneId,
|
||||
(),
|
||||
>::rewards_account(RewardsAccountParams::new(
|
||||
lane_id,
|
||||
*bridged_chain_id,
|
||||
owner
|
||||
)))
|
||||
.to_ss58check_with_version(expected_ss58.into())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Bridge relayers registration and slashing scheme.
|
||||
//!
|
||||
//! There is an option to add a refund-relayer signed extension that will compensate
|
||||
//! relayer costs of the message delivery and confirmation transactions (as well as
|
||||
//! required finality proofs). This extension boosts priority of message delivery
|
||||
//! transactions, based on the number of bundled messages. So transaction with more
|
||||
//! messages has larger priority than the transaction with less messages.
|
||||
//! See `pezbridge_runtime_common::extensions::priority_calculator` for details;
|
||||
//!
|
||||
//! This encourages relayers to include more messages to their delivery transactions.
|
||||
//! At the same time, we are not verifying storage proofs before boosting
|
||||
//! priority. Instead, we simply trust relayer, when it says that transaction delivers
|
||||
//! `N` messages.
|
||||
//!
|
||||
//! This allows relayers to submit transactions which declare large number of bundled
|
||||
//! transactions to receive priority boost for free, potentially pushing actual delivery
|
||||
//! transactions from the block (or even transaction queue). Such transactions are
|
||||
//! not free, but their cost is relatively small.
|
||||
//!
|
||||
//! To alleviate that, we only boost transactions of relayers that have some stake
|
||||
//! that guarantees that their transactions are valid. Such relayers get priority
|
||||
//! for free, but they risk to lose their stake.
|
||||
|
||||
use crate::{PayRewardFromAccount, RewardsAccountParams};
|
||||
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::{
|
||||
traits::{Get, IdentifyAccount, Zero},
|
||||
DispatchError, DispatchResult,
|
||||
};
|
||||
|
||||
/// Either explicit account reference or `RewardsAccountParams`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ExplicitOrAccountParams<AccountId, LaneId: Decode + Encode> {
|
||||
/// Explicit account reference.
|
||||
Explicit(AccountId),
|
||||
/// Account, referenced using `RewardsAccountParams`.
|
||||
Params(RewardsAccountParams<LaneId>),
|
||||
}
|
||||
|
||||
impl<AccountId, LaneId: Decode + Encode> From<RewardsAccountParams<LaneId>>
|
||||
for ExplicitOrAccountParams<AccountId, LaneId>
|
||||
{
|
||||
fn from(params: RewardsAccountParams<LaneId>) -> Self {
|
||||
ExplicitOrAccountParams::Params(params)
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId: Decode + Encode, LaneId: Decode + Encode> IdentifyAccount
|
||||
for ExplicitOrAccountParams<AccountId, LaneId>
|
||||
{
|
||||
type AccountId = AccountId;
|
||||
|
||||
fn into_account(self) -> Self::AccountId {
|
||||
match self {
|
||||
ExplicitOrAccountParams::Explicit(account_id) => account_id,
|
||||
ExplicitOrAccountParams::Params(params) =>
|
||||
PayRewardFromAccount::<(), AccountId, LaneId, ()>::rewards_account(params),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Relayer registration.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Encode,
|
||||
Eq,
|
||||
PartialEq,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct Registration<BlockNumber, Balance> {
|
||||
/// The last block number, where this registration is considered active.
|
||||
///
|
||||
/// Relayer has an option to renew his registration (this may be done before it
|
||||
/// is spoiled as well). Starting from block `valid_till + 1`, relayer may `deregister`
|
||||
/// himself and get his stake back.
|
||||
///
|
||||
/// Please keep in mind that priority boost stops working some blocks before the
|
||||
/// registration ends (see [`StakeAndSlash::RequiredRegistrationLease`]).
|
||||
pub valid_till: BlockNumber,
|
||||
/// Active relayer stake, which is mapped to the relayer reserved balance.
|
||||
///
|
||||
/// If `stake` is less than the [`StakeAndSlash::RequiredStake`], the registration
|
||||
/// is considered inactive even if `valid_till + 1` is not yet reached.
|
||||
pub stake: Balance,
|
||||
}
|
||||
|
||||
/// Relayer stake-and-slash mechanism.
|
||||
pub trait StakeAndSlash<AccountId, BlockNumber, Balance> {
|
||||
/// The stake that the relayer must have to have its transactions boosted.
|
||||
type RequiredStake: Get<Balance>;
|
||||
/// Required **remaining** registration lease to be able to get transaction priority boost.
|
||||
///
|
||||
/// If the difference between registration's `valid_till` and the current block number
|
||||
/// is less than the `RequiredRegistrationLease`, it becomes inactive and relayer transaction
|
||||
/// won't get priority boost. This period exists, because priority is calculated when
|
||||
/// transaction is placed to the queue (and it is reevaluated periodically) and then some time
|
||||
/// may pass before transaction will be included into the block.
|
||||
type RequiredRegistrationLease: Get<BlockNumber>;
|
||||
|
||||
/// Reserve the given amount at relayer account.
|
||||
fn reserve(relayer: &AccountId, amount: Balance) -> DispatchResult;
|
||||
/// `Unreserve` the given amount from relayer account.
|
||||
///
|
||||
/// Returns amount that we have failed to `unreserve`.
|
||||
fn unreserve(relayer: &AccountId, amount: Balance) -> Balance;
|
||||
/// Slash up to `amount` from reserved balance of account `relayer` and send funds to given
|
||||
/// `beneficiary`.
|
||||
///
|
||||
/// Returns `Ok(_)` with non-zero balance if we have failed to repatriate some portion of stake.
|
||||
fn repatriate_reserved(
|
||||
relayer: &AccountId,
|
||||
beneficiary: &AccountId,
|
||||
amount: Balance,
|
||||
) -> Result<Balance, DispatchError>;
|
||||
}
|
||||
|
||||
impl<AccountId, BlockNumber, Balance> StakeAndSlash<AccountId, BlockNumber, Balance> for ()
|
||||
where
|
||||
Balance: Default + Zero,
|
||||
BlockNumber: Default,
|
||||
{
|
||||
type RequiredStake = ();
|
||||
type RequiredRegistrationLease = ();
|
||||
|
||||
fn reserve(_relayer: &AccountId, _amount: Balance) -> DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unreserve(_relayer: &AccountId, _amount: Balance) -> Balance {
|
||||
Zero::zero()
|
||||
}
|
||||
|
||||
fn repatriate_reserved(
|
||||
_relayer: &AccountId,
|
||||
_beneficiary: &AccountId,
|
||||
_amount: Balance,
|
||||
) -> Result<Balance, DispatchError> {
|
||||
Ok(Zero::zero())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
[package]
|
||||
name = "pezbp-runtime"
|
||||
description = "Primitives that may be used at (bridges) runtime level."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezbp-runtime"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
hash-db = { workspace = true }
|
||||
impl-trait-for-tuples = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { features = ["serde"], workspace = true }
|
||||
pezsp-state-machine = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
pezsp-trie = { workspace = true }
|
||||
trie-db = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"hash-db/std",
|
||||
"num-traits/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-state-machine/std",
|
||||
"pezsp-std/std",
|
||||
"pezsp-trie/std",
|
||||
"tracing/std",
|
||||
"trie-db/std",
|
||||
]
|
||||
test-helpers = []
|
||||
runtime-benchmarks = [
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-state-machine/runtime-benchmarks",
|
||||
"pezsp-trie/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,446 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{ChainId, HeaderIdProvider};
|
||||
|
||||
use codec::{Codec, Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{weights::Weight, Parameter};
|
||||
use num_traits::{AsPrimitive, Bounded, CheckedSub, Saturating, SaturatingAdd, Zero};
|
||||
use pezsp_runtime::{
|
||||
traits::{
|
||||
AtLeast32Bit, AtLeast32BitUnsigned, Hash as HashT, Header as HeaderT, MaybeDisplay,
|
||||
MaybeSerialize, MaybeSerializeDeserialize, Member, SimpleBitOps, Verify,
|
||||
},
|
||||
FixedPointOperand, StateVersion,
|
||||
};
|
||||
use pezsp_std::{fmt::Debug, hash::Hash, str::FromStr, vec, vec::Vec};
|
||||
|
||||
/// Chain call, that is either SCALE-encoded, or decoded.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum EncodedOrDecodedCall<ChainCall> {
|
||||
/// The call that is SCALE-encoded.
|
||||
///
|
||||
/// This variant is used when we the chain runtime is not bundled with the relay, but
|
||||
/// we still need the represent call in some RPC calls or transactions.
|
||||
Encoded(Vec<u8>),
|
||||
/// The decoded call.
|
||||
Decoded(ChainCall),
|
||||
}
|
||||
|
||||
impl<ChainCall: Clone + Codec> EncodedOrDecodedCall<ChainCall> {
|
||||
/// Returns decoded call.
|
||||
pub fn to_decoded(&self) -> Result<ChainCall, codec::Error> {
|
||||
match self {
|
||||
Self::Encoded(ref encoded_call) =>
|
||||
ChainCall::decode(&mut &encoded_call[..]).map_err(Into::into),
|
||||
Self::Decoded(ref decoded_call) => Ok(decoded_call.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts self to decoded call.
|
||||
pub fn into_decoded(self) -> Result<ChainCall, codec::Error> {
|
||||
match self {
|
||||
Self::Encoded(encoded_call) =>
|
||||
ChainCall::decode(&mut &encoded_call[..]).map_err(Into::into),
|
||||
Self::Decoded(decoded_call) => Ok(decoded_call),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts self to encoded call.
|
||||
pub fn into_encoded(self) -> Vec<u8> {
|
||||
match self {
|
||||
Self::Encoded(encoded_call) => encoded_call,
|
||||
Self::Decoded(decoded_call) => decoded_call.encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ChainCall> From<ChainCall> for EncodedOrDecodedCall<ChainCall> {
|
||||
fn from(call: ChainCall) -> EncodedOrDecodedCall<ChainCall> {
|
||||
EncodedOrDecodedCall::Decoded(call)
|
||||
}
|
||||
}
|
||||
|
||||
impl<ChainCall: Decode> Decode for EncodedOrDecodedCall<ChainCall> {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
// having encoded version is better than decoded, because decoding isn't required
|
||||
// everywhere and for mocked calls it may lead to **unneeded** errors
|
||||
match input.remaining_len()? {
|
||||
Some(remaining_len) => {
|
||||
let mut encoded_call = vec![0u8; remaining_len];
|
||||
input.read(&mut encoded_call)?;
|
||||
Ok(EncodedOrDecodedCall::Encoded(encoded_call))
|
||||
},
|
||||
None => Ok(EncodedOrDecodedCall::Decoded(ChainCall::decode(input)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ChainCall: Encode> Encode for EncodedOrDecodedCall<ChainCall> {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
match *self {
|
||||
Self::Encoded(ref encoded_call) => encoded_call.clone(),
|
||||
Self::Decoded(ref decoded_call) => decoded_call.encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal Bizinikiwi-based chain representation that may be used from no_std environment.
|
||||
pub trait Chain: Send + Sync + 'static {
|
||||
/// Chain id.
|
||||
const ID: ChainId;
|
||||
|
||||
/// A type that fulfills the abstract idea of what a Bizinikiwi block number is.
|
||||
// Constraints come from the associated Number type of `pezsp_runtime::traits::Header`
|
||||
// See here for more info:
|
||||
// https://docs.rs/sp-runtime/latest/pezsp_runtime/traits/trait.Header.html#associatedtype.Number
|
||||
//
|
||||
// Note that the `AsPrimitive<usize>` trait is required by the GRANDPA justification
|
||||
// verifier, and is not usually part of a Bizinikiwi Header's Number type.
|
||||
type BlockNumber: Parameter
|
||||
+ Member
|
||||
+ MaybeSerializeDeserialize
|
||||
+ Hash
|
||||
+ Copy
|
||||
+ Default
|
||||
+ MaybeDisplay
|
||||
+ AtLeast32BitUnsigned
|
||||
+ FromStr
|
||||
+ AsPrimitive<usize>
|
||||
+ Default
|
||||
+ Saturating
|
||||
+ MaxEncodedLen;
|
||||
|
||||
/// A type that fulfills the abstract idea of what a Bizinikiwi hash is.
|
||||
// Constraints come from the associated Hash type of `pezsp_runtime::traits::Header`
|
||||
// See here for more info:
|
||||
// https://docs.rs/sp-runtime/latest/pezsp_runtime/traits/trait.Header.html#associatedtype.Hash
|
||||
type Hash: Parameter
|
||||
+ Member
|
||||
+ MaybeSerializeDeserialize
|
||||
+ Hash
|
||||
+ Ord
|
||||
+ Copy
|
||||
+ MaybeDisplay
|
||||
+ Default
|
||||
+ SimpleBitOps
|
||||
+ AsRef<[u8]>
|
||||
+ AsMut<[u8]>
|
||||
+ MaxEncodedLen;
|
||||
|
||||
/// A type that fulfills the abstract idea of what a Bizinikiwi hasher (a type
|
||||
/// that produces hashes) is.
|
||||
// Constraints come from the associated Hashing type of `pezsp_runtime::traits::Header`
|
||||
// See here for more info:
|
||||
// https://docs.rs/sp-runtime/latest/pezsp_runtime/traits/trait.Header.html#associatedtype.Hashing
|
||||
type Hasher: HashT<Output = Self::Hash>;
|
||||
|
||||
/// A type that fulfills the abstract idea of what a Bizinikiwi header is.
|
||||
// See here for more info:
|
||||
// https://docs.rs/sp-runtime/latest/pezsp_runtime/traits/trait.Header.html
|
||||
type Header: Parameter
|
||||
+ HeaderT<Number = Self::BlockNumber, Hash = Self::Hash>
|
||||
+ HeaderIdProvider<Self::Header>
|
||||
+ MaybeSerializeDeserialize;
|
||||
|
||||
/// The user account identifier type for the runtime.
|
||||
type AccountId: Parameter
|
||||
+ Member
|
||||
+ MaybeSerializeDeserialize
|
||||
+ Debug
|
||||
+ MaybeDisplay
|
||||
+ Ord
|
||||
+ MaxEncodedLen;
|
||||
/// Balance of an account in native tokens.
|
||||
///
|
||||
/// The chain may support multiple tokens, but this particular type is for token that is used
|
||||
/// to pay for transaction dispatch, to reward different relayers (headers, messages), etc.
|
||||
type Balance: AtLeast32BitUnsigned
|
||||
+ FixedPointOperand
|
||||
+ Parameter
|
||||
+ Member
|
||||
+ MaybeSerializeDeserialize
|
||||
+ Clone
|
||||
+ Copy
|
||||
+ Bounded
|
||||
+ CheckedSub
|
||||
+ PartialOrd
|
||||
+ SaturatingAdd
|
||||
+ Zero
|
||||
+ TryFrom<pezsp_core::U256>
|
||||
+ MaxEncodedLen;
|
||||
/// Nonce of a transaction used by the chain.
|
||||
type Nonce: Parameter
|
||||
+ Member
|
||||
+ MaybeSerialize
|
||||
+ Debug
|
||||
+ Default
|
||||
+ MaybeDisplay
|
||||
+ MaybeSerializeDeserialize
|
||||
+ AtLeast32Bit
|
||||
+ Copy
|
||||
+ MaxEncodedLen;
|
||||
/// Signature type, used on this chain.
|
||||
type Signature: Parameter + Verify;
|
||||
|
||||
/// Version of the state implementation used by this chain. This is directly related with the
|
||||
/// `TrieLayout` configuration used by the storage.
|
||||
const STATE_VERSION: StateVersion;
|
||||
|
||||
/// Get the maximum size (in bytes) of a Normal extrinsic at this chain.
|
||||
fn max_extrinsic_size() -> u32;
|
||||
/// Get the maximum weight (compute time) that a Normal extrinsic at this chain can use.
|
||||
fn max_extrinsic_weight() -> Weight;
|
||||
}
|
||||
|
||||
/// A trait that provides the type of the underlying chain.
|
||||
pub trait UnderlyingChainProvider: Send + Sync + 'static {
|
||||
/// Underlying chain type.
|
||||
type Chain: Chain;
|
||||
}
|
||||
|
||||
impl<T> Chain for T
|
||||
where
|
||||
T: Send + Sync + 'static + UnderlyingChainProvider,
|
||||
{
|
||||
const ID: ChainId = <T::Chain as Chain>::ID;
|
||||
|
||||
type BlockNumber = <T::Chain as Chain>::BlockNumber;
|
||||
type Hash = <T::Chain as Chain>::Hash;
|
||||
type Hasher = <T::Chain as Chain>::Hasher;
|
||||
type Header = <T::Chain as Chain>::Header;
|
||||
type AccountId = <T::Chain as Chain>::AccountId;
|
||||
type Balance = <T::Chain as Chain>::Balance;
|
||||
type Nonce = <T::Chain as Chain>::Nonce;
|
||||
type Signature = <T::Chain as Chain>::Signature;
|
||||
|
||||
const STATE_VERSION: StateVersion = <T::Chain as Chain>::STATE_VERSION;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
<T::Chain as Chain>::max_extrinsic_size()
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
<T::Chain as Chain>::max_extrinsic_weight()
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal teyrchain representation that may be used from no_std environment.
|
||||
pub trait Teyrchain: Chain {
|
||||
/// Teyrchain identifier.
|
||||
const TEYRCHAIN_ID: u32;
|
||||
/// Maximal size of the teyrchain header.
|
||||
///
|
||||
/// This isn't a strict limit. The relayer may submit larger headers and the
|
||||
/// pezpallet will accept the call. The limit is only used to compute whether
|
||||
/// the refund can be made.
|
||||
const MAX_HEADER_SIZE: u32;
|
||||
}
|
||||
|
||||
impl<T> Teyrchain for T
|
||||
where
|
||||
T: Chain + UnderlyingChainProvider,
|
||||
<T as UnderlyingChainProvider>::Chain: Teyrchain,
|
||||
{
|
||||
const TEYRCHAIN_ID: u32 = <<T as UnderlyingChainProvider>::Chain as Teyrchain>::TEYRCHAIN_ID;
|
||||
const MAX_HEADER_SIZE: u32 =
|
||||
<<T as UnderlyingChainProvider>::Chain as Teyrchain>::MAX_HEADER_SIZE;
|
||||
}
|
||||
|
||||
/// Adapter for `Get<u32>` to access `TEYRCHAIN_ID` from `trait Teyrchain`
|
||||
pub struct TeyrchainIdOf<Para>(pezsp_std::marker::PhantomData<Para>);
|
||||
impl<Para: Teyrchain> pezframe_support::traits::Get<u32> for TeyrchainIdOf<Para> {
|
||||
fn get() -> u32 {
|
||||
Para::TEYRCHAIN_ID
|
||||
}
|
||||
}
|
||||
|
||||
/// Underlying chain type.
|
||||
pub type UnderlyingChainOf<C> = <C as UnderlyingChainProvider>::Chain;
|
||||
|
||||
/// Block number used by the chain.
|
||||
pub type BlockNumberOf<C> = <C as Chain>::BlockNumber;
|
||||
|
||||
/// Hash type used by the chain.
|
||||
pub type HashOf<C> = <C as Chain>::Hash;
|
||||
|
||||
/// Hasher type used by the chain.
|
||||
pub type HasherOf<C> = <C as Chain>::Hasher;
|
||||
|
||||
/// Header type used by the chain.
|
||||
pub type HeaderOf<C> = <C as Chain>::Header;
|
||||
|
||||
/// Account id type used by the chain.
|
||||
pub type AccountIdOf<C> = <C as Chain>::AccountId;
|
||||
|
||||
/// Balance type used by the chain.
|
||||
pub type BalanceOf<C> = <C as Chain>::Balance;
|
||||
|
||||
/// Transaction nonce type used by the chain.
|
||||
pub type NonceOf<C> = <C as Chain>::Nonce;
|
||||
|
||||
/// Signature type used by the chain.
|
||||
pub type SignatureOf<C> = <C as Chain>::Signature;
|
||||
|
||||
/// Account public type used by the chain.
|
||||
pub type AccountPublicOf<C> = <SignatureOf<C> as Verify>::Signer;
|
||||
|
||||
/// Transaction era used by the chain.
|
||||
pub type TransactionEraOf<C> = crate::TransactionEra<BlockNumberOf<C>, HashOf<C>>;
|
||||
|
||||
/// Convenience macro that declares bridge finality runtime apis and related constants for a chain.
|
||||
/// This includes:
|
||||
/// - chain-specific bridge runtime APIs:
|
||||
/// - `<ThisChain>FinalityApi`
|
||||
/// - constants that are stringified names of runtime API methods:
|
||||
/// - `BEST_FINALIZED_<THIS_CHAIN>_HEADER_METHOD`
|
||||
/// - `<THIS_CHAIN>_ACCEPTED_<CONSENSUS>_FINALITY_PROOFS_METHOD`
|
||||
/// The name of the chain has to be specified in snake case (e.g. `bridge_hub_pezkuwi`).
|
||||
#[macro_export]
|
||||
macro_rules! decl_bridge_finality_runtime_apis {
|
||||
($chain: ident $(, $consensus: ident => $justification_type: ty)?) => {
|
||||
pezbp_runtime::paste::item! {
|
||||
mod [<$chain _finality_api>] {
|
||||
use super::*;
|
||||
|
||||
/// Name of the `<ThisChain>FinalityApi::best_finalized` runtime method.
|
||||
pub const [<BEST_FINALIZED_ $chain:upper _HEADER_METHOD>]: &str =
|
||||
stringify!([<$chain:camel FinalityApi_best_finalized>]);
|
||||
|
||||
/// Name of the `<ThisChain>FinalityApi::free_headers_interval` runtime method.
|
||||
pub const [<FREE_HEADERS_INTERVAL_FOR_ $chain:upper _METHOD>]: &str =
|
||||
stringify!([<$chain:camel FinalityApi_free_headers_interval>]);
|
||||
|
||||
|
||||
$(
|
||||
/// Name of the `<ThisChain>FinalityApi::accepted_<consensus>_finality_proofs`
|
||||
/// runtime method.
|
||||
pub const [<$chain:upper _SYNCED_HEADERS_ $consensus:upper _INFO_METHOD>]: &str =
|
||||
stringify!([<$chain:camel FinalityApi_synced_headers_ $consensus:lower _info>]);
|
||||
)?
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// API for querying information about the finalized chain headers.
|
||||
///
|
||||
/// This API is implemented by runtimes that are receiving messages from this chain, not by this
|
||||
/// chain's runtime itself.
|
||||
pub trait [<$chain:camel FinalityApi>] {
|
||||
/// Returns number and hash of the best finalized header known to the bridge module.
|
||||
fn best_finalized() -> Option<pezbp_runtime::HeaderId<Hash, BlockNumber>>;
|
||||
|
||||
/// Returns free headers interval, if it is configured in the runtime.
|
||||
/// The caller expects that if his transaction improves best known header
|
||||
/// at least by the free_headers_interval`, it will be fee-free.
|
||||
///
|
||||
/// See [`pezpallet_bridge_grandpa::Config::FreeHeadersInterval`] for details.
|
||||
fn free_headers_interval() -> Option<BlockNumber>;
|
||||
|
||||
$(
|
||||
/// Returns the justifications accepted in the current block.
|
||||
fn [<synced_headers_ $consensus:lower _info>](
|
||||
) -> $crate::private::Vec<$justification_type>;
|
||||
)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use [<$chain _finality_api>]::*;
|
||||
}
|
||||
};
|
||||
($chain: ident, grandpa) => {
|
||||
decl_bridge_finality_runtime_apis!($chain, grandpa => bp_header_pez_chain::StoredHeaderGrandpaInfo<Header>);
|
||||
};
|
||||
}
|
||||
|
||||
// Re-export to avoid include tuplex dependency everywhere.
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
pub use codec;
|
||||
}
|
||||
|
||||
/// Convenience macro that declares bridge messages runtime apis and related constants for a chain.
|
||||
/// This includes:
|
||||
/// - chain-specific bridge runtime APIs:
|
||||
/// - `To<ThisChain>OutboundLaneApi<LaneIdType>`
|
||||
/// - `From<ThisChain>InboundLaneApi<LaneIdType>`
|
||||
/// - constants that are stringified names of runtime API methods:
|
||||
/// - `FROM_<THIS_CHAIN>_MESSAGE_DETAILS_METHOD`,
|
||||
/// The name of the chain has to be specified in snake case (e.g. `bridge_hub_pezkuwi`).
|
||||
#[macro_export]
|
||||
macro_rules! decl_bridge_messages_runtime_apis {
|
||||
($chain: ident, $lane_id_type:ty) => {
|
||||
pezbp_runtime::paste::item! {
|
||||
mod [<$chain _messages_api>] {
|
||||
use super::*;
|
||||
|
||||
/// Name of the `To<ThisChain>OutboundLaneApi::message_details` runtime method.
|
||||
pub const [<TO_ $chain:upper _MESSAGE_DETAILS_METHOD>]: &str =
|
||||
stringify!([<To $chain:camel OutboundLaneApi_message_details>]);
|
||||
|
||||
/// Name of the `From<ThisChain>InboundLaneApi::message_details` runtime method.
|
||||
pub const [<FROM_ $chain:upper _MESSAGE_DETAILS_METHOD>]: &str =
|
||||
stringify!([<From $chain:camel InboundLaneApi_message_details>]);
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// Outbound message lane API for messages that are sent to this chain.
|
||||
///
|
||||
/// This API is implemented by runtimes that are receiving messages from this chain, not by this
|
||||
/// chain's runtime itself.
|
||||
pub trait [<To $chain:camel OutboundLaneApi>] {
|
||||
/// Returns dispatch weight, encoded payload size and delivery+dispatch fee of all
|
||||
/// messages in given inclusive range.
|
||||
///
|
||||
/// If some (or all) messages are missing from the storage, they'll also will
|
||||
/// be missing from the resulting vector. The vector is ordered by the nonce.
|
||||
fn message_details(
|
||||
lane: $lane_id_type,
|
||||
begin: bp_messages::MessageNonce,
|
||||
end: bp_messages::MessageNonce,
|
||||
) -> $crate::private::Vec<bp_messages::OutboundMessageDetails>;
|
||||
}
|
||||
|
||||
/// Inbound message lane API for messages sent by this chain.
|
||||
///
|
||||
/// This API is implemented by runtimes that are receiving messages from this chain, not by this
|
||||
/// chain's runtime itself.
|
||||
///
|
||||
/// Entries of the resulting vector are matching entries of the `messages` vector. Entries of the
|
||||
/// `messages` vector may (and need to) be read using `To<ThisChain>OutboundLaneApi::message_details`.
|
||||
pub trait [<From $chain:camel InboundLaneApi>] {
|
||||
/// Return details of given inbound messages.
|
||||
fn message_details(
|
||||
lane: $lane_id_type,
|
||||
messages: $crate::private::Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>,
|
||||
) -> $crate::private::Vec<bp_messages::InboundMessageDetails>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use [<$chain _messages_api>]::*;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Convenience macro that declares bridge finality runtime apis, bridge messages runtime apis
|
||||
/// and related constants for a chain.
|
||||
/// The name of the chain has to be specified in snake case (e.g. `bridge_hub_pezkuwi`).
|
||||
#[macro_export]
|
||||
macro_rules! decl_bridge_runtime_apis {
|
||||
($chain: ident $(, $consensus: ident, $lane_id_type:ident)?) => {
|
||||
pezbp_runtime::decl_bridge_finality_runtime_apis!($chain $(, $consensus)?);
|
||||
pezbp_runtime::decl_bridge_messages_runtime_apis!($chain, $lane_id_type);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives that may be used for creating signed extensions for indirect runtimes.
|
||||
|
||||
use codec::{Compact, Decode, DecodeWithMemTracking, Encode};
|
||||
use impl_trait_for_tuples::impl_for_tuples;
|
||||
use scale_info::{StaticTypeInfo, TypeInfo};
|
||||
use pezsp_runtime::{
|
||||
impl_tx_ext_default,
|
||||
traits::{Dispatchable, TransactionExtension},
|
||||
transaction_validity::TransactionValidityError,
|
||||
};
|
||||
use pezsp_std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
/// Trait that describes some properties of a `TransactionExtension` that are needed in order to
|
||||
/// send a transaction to the chain.
|
||||
pub trait TransactionExtensionSchema:
|
||||
Encode + Decode + DecodeWithMemTracking + Debug + Eq + Clone + StaticTypeInfo
|
||||
{
|
||||
/// A type of the data encoded as part of the transaction.
|
||||
type Payload: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo;
|
||||
/// Parameters which are part of the payload used to produce transaction signature,
|
||||
/// but don't end up in the transaction itself (i.e. inherent part of the runtime).
|
||||
type Implicit: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo;
|
||||
}
|
||||
|
||||
impl TransactionExtensionSchema for () {
|
||||
type Payload = ();
|
||||
type Implicit = ();
|
||||
}
|
||||
|
||||
/// An implementation of `TransactionExtensionSchema` using generic params.
|
||||
#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Debug, PartialEq, Eq, TypeInfo)]
|
||||
pub struct GenericTransactionExtensionSchema<P, S>(PhantomData<(P, S)>);
|
||||
|
||||
impl<P, S> TransactionExtensionSchema for GenericTransactionExtensionSchema<P, S>
|
||||
where
|
||||
P: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo,
|
||||
S: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo,
|
||||
{
|
||||
type Payload = P;
|
||||
type Implicit = S;
|
||||
}
|
||||
|
||||
/// The `TransactionExtensionSchema` for `pezframe_system::CheckNonZeroSender`.
|
||||
pub type CheckNonZeroSender = GenericTransactionExtensionSchema<(), ()>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `pezframe_system::CheckSpecVersion`.
|
||||
pub type CheckSpecVersion = GenericTransactionExtensionSchema<(), u32>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `pezframe_system::CheckTxVersion`.
|
||||
pub type CheckTxVersion = GenericTransactionExtensionSchema<(), u32>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `pezframe_system::CheckGenesis`.
|
||||
pub type CheckGenesis<Hash> = GenericTransactionExtensionSchema<(), Hash>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `pezframe_system::CheckEra`.
|
||||
pub type CheckEra<Hash> = GenericTransactionExtensionSchema<pezsp_runtime::generic::Era, Hash>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `pezframe_system::CheckNonce`.
|
||||
pub type CheckNonce<TxNonce> = GenericTransactionExtensionSchema<Compact<TxNonce>, ()>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `pezframe_system::CheckWeight`.
|
||||
pub type CheckWeight = GenericTransactionExtensionSchema<(), ()>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `pezpallet_transaction_payment::ChargeTransactionPayment`.
|
||||
pub type ChargeTransactionPayment<Balance> =
|
||||
GenericTransactionExtensionSchema<Compact<Balance>, ()>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `pezkuwi-runtime-common::PrevalidateAttests`.
|
||||
pub type PrevalidateAttests = GenericTransactionExtensionSchema<(), ()>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `BridgeRejectObsoleteHeadersAndMessages`.
|
||||
pub type BridgeRejectObsoleteHeadersAndMessages = GenericTransactionExtensionSchema<(), ()>;
|
||||
|
||||
/// The `TransactionExtensionSchema` for `RefundBridgedTeyrchainMessages`.
|
||||
/// This schema is dedicated for `RefundBridgedTeyrchainMessages` signed extension as
|
||||
/// wildcard/placeholder, which relies on the scale encoding for `()` or `((), ())`, or `((), (),
|
||||
/// ())` is the same. So runtime can contains any kind of tuple:
|
||||
/// `(BridgeRefundBridgeHubPezkuwichainMessages)`
|
||||
/// `(BridgeRefundBridgeHubPezkuwichainMessages, BridgeRefundBridgeHubZagrosMessages)`
|
||||
/// `(BridgeRefundTeyrchainMessages1, ..., BridgeRefundTeyrchainMessagesN)`
|
||||
pub type RefundBridgedTeyrchainMessagesSchema = GenericTransactionExtensionSchema<(), ()>;
|
||||
|
||||
#[impl_for_tuples(1, 12)]
|
||||
impl TransactionExtensionSchema for Tuple {
|
||||
for_tuples!( type Payload = ( #( Tuple::Payload ),* ); );
|
||||
for_tuples!( type Implicit = ( #( Tuple::Implicit ),* ); );
|
||||
}
|
||||
|
||||
/// A simplified version of signed extensions meant for producing signed transactions
|
||||
/// and signed payloads in the client code.
|
||||
#[derive(Encode, Decode, DecodeWithMemTracking, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
pub struct GenericTransactionExtension<S: TransactionExtensionSchema> {
|
||||
/// A payload that is included in the transaction.
|
||||
pub payload: S::Payload,
|
||||
#[codec(skip)]
|
||||
// It may be set to `None` if extensions are decoded. We are never reconstructing transactions
|
||||
// (and it makes no sense to do that) => decoded version of `TransactionExtensions` is only
|
||||
// used to read fields of the `payload`. And when resigning transaction, we're reconstructing
|
||||
// `TransactionExtensions` from scratch.
|
||||
implicit: Option<S::Implicit>,
|
||||
}
|
||||
|
||||
impl<S: TransactionExtensionSchema> GenericTransactionExtension<S> {
|
||||
/// Create new `GenericTransactionExtension` object.
|
||||
pub fn new(payload: S::Payload, implicit: Option<S::Implicit>) -> Self {
|
||||
Self { payload, implicit }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, C> TransactionExtension<C> for GenericTransactionExtension<S>
|
||||
where
|
||||
C: Dispatchable,
|
||||
S: TransactionExtensionSchema,
|
||||
S::Payload: DecodeWithMemTracking + Send + Sync,
|
||||
S::Implicit: Send + Sync,
|
||||
{
|
||||
const IDENTIFIER: &'static str = "Not needed.";
|
||||
type Implicit = S::Implicit;
|
||||
|
||||
fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
|
||||
// we shall not ever see this error in relay, because we are never signing decoded
|
||||
// transactions. Instead we're constructing and signing new transactions. So the error code
|
||||
// is kinda random here
|
||||
self.implicit
|
||||
.clone()
|
||||
.ok_or(pezframe_support::unsigned::TransactionValidityError::Unknown(
|
||||
pezframe_support::unsigned::UnknownTransaction::Custom(0xFF),
|
||||
))
|
||||
}
|
||||
type Pre = ();
|
||||
type Val = ();
|
||||
|
||||
impl_tx_ext_default!(C; weight validate prepare);
|
||||
}
|
||||
@@ -0,0 +1,542 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives that may be used at (bridges) runtime level.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, FullCodec, MaxEncodedLen};
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::DispatchResult, weights::Weight, PalletError, StorageHasher, StorageValue,
|
||||
};
|
||||
use pezframe_system::RawOrigin;
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pezsp_core::storage::StorageKey;
|
||||
use pezsp_runtime::{
|
||||
traits::{BadOrigin, Header as HeaderT, UniqueSaturatedInto},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use pezsp_std::{fmt::Debug, ops::RangeInclusive, vec, vec::Vec};
|
||||
|
||||
pub use chain::{
|
||||
AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain, EncodedOrDecodedCall, HashOf,
|
||||
HasherOf, HeaderOf, NonceOf, SignatureOf, Teyrchain, TeyrchainIdOf, TransactionEraOf,
|
||||
UnderlyingChainOf, UnderlyingChainProvider, __private,
|
||||
};
|
||||
pub use pezframe_support::storage::storage_prefix as storage_value_final_key;
|
||||
use num_traits::{CheckedAdd, CheckedSub, One, SaturatingAdd, Zero};
|
||||
#[cfg(feature = "std")]
|
||||
pub use storage_proof::craft_valid_storage_proof;
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub use storage_proof::{
|
||||
grow_storage_proof, grow_storage_value, record_all_keys as record_all_trie_keys,
|
||||
UnverifiedStorageProofParams,
|
||||
};
|
||||
pub use storage_proof::{
|
||||
raw_storage_proof_size, RawStorageProof, StorageProofChecker, StorageProofError,
|
||||
};
|
||||
pub use storage_types::BoundedStorageValue;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod extensions;
|
||||
pub mod messages;
|
||||
|
||||
mod chain;
|
||||
mod storage_proof;
|
||||
mod storage_types;
|
||||
|
||||
// Re-export macro to avoid include paste dependency everywhere
|
||||
pub use pezsp_runtime::paste;
|
||||
|
||||
// Re-export for usage in macro.
|
||||
#[doc(hidden)]
|
||||
pub mod private {
|
||||
#[doc(hidden)]
|
||||
pub use alloc::vec::Vec;
|
||||
}
|
||||
|
||||
/// Use this when something must be shared among all instances.
|
||||
pub const NO_INSTANCE_ID: ChainId = [0, 0, 0, 0];
|
||||
|
||||
/// Generic header Id.
|
||||
#[derive(
|
||||
RuntimeDebug,
|
||||
Default,
|
||||
Clone,
|
||||
Encode,
|
||||
Decode,
|
||||
Copy,
|
||||
Eq,
|
||||
Hash,
|
||||
MaxEncodedLen,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub struct HeaderId<Hash, Number>(pub Number, pub Hash);
|
||||
|
||||
impl<Hash: Copy, Number: Copy> HeaderId<Hash, Number> {
|
||||
/// Return header number.
|
||||
pub fn number(&self) -> Number {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Return header hash.
|
||||
pub fn hash(&self) -> Hash {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
/// Header id used by the chain.
|
||||
pub type HeaderIdOf<C> = HeaderId<HashOf<C>, BlockNumberOf<C>>;
|
||||
|
||||
/// Generic header id provider.
|
||||
pub trait HeaderIdProvider<Header: HeaderT> {
|
||||
/// Get the header id.
|
||||
fn id(&self) -> HeaderId<Header::Hash, Header::Number>;
|
||||
|
||||
/// Get the header id for the parent block.
|
||||
fn parent_id(&self) -> Option<HeaderId<Header::Hash, Header::Number>>;
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> HeaderIdProvider<Header> for Header {
|
||||
fn id(&self) -> HeaderId<Header::Hash, Header::Number> {
|
||||
HeaderId(*self.number(), self.hash())
|
||||
}
|
||||
|
||||
fn parent_id(&self) -> Option<HeaderId<Header::Hash, Header::Number>> {
|
||||
self.number()
|
||||
.checked_sub(&One::one())
|
||||
.map(|parent_number| HeaderId(parent_number, *self.parent_hash()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique identifier of the chain.
|
||||
///
|
||||
/// In addition to its main function (identifying the chain), this type may also be used to
|
||||
/// identify module instance. We have a bunch of pallets that may be used in different bridges. E.g.
|
||||
/// messages pezpallet may be deployed twice in the same runtime to bridge ThisChain with Chain1 and
|
||||
/// Chain2. Sometimes we need to be able to identify deployed instance dynamically. This type may be
|
||||
/// used for that.
|
||||
pub type ChainId = [u8; 4];
|
||||
|
||||
/// Anything that has size.
|
||||
pub trait Size {
|
||||
/// Return size of this object (in bytes).
|
||||
fn size(&self) -> u32;
|
||||
}
|
||||
|
||||
impl Size for () {
|
||||
fn size(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl Size for Vec<u8> {
|
||||
fn size(&self) -> u32 {
|
||||
self.len() as _
|
||||
}
|
||||
}
|
||||
|
||||
/// Pre-computed size.
|
||||
pub struct PreComputedSize(pub usize);
|
||||
|
||||
impl Size for PreComputedSize {
|
||||
fn size(&self) -> u32 {
|
||||
u32::try_from(self.0).unwrap_or(u32::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
/// Era of specific transaction.
|
||||
#[derive(RuntimeDebug, Clone, Copy, PartialEq)]
|
||||
pub enum TransactionEra<BlockNumber, BlockHash> {
|
||||
/// Transaction is immortal.
|
||||
Immortal,
|
||||
/// Transaction is valid for a given number of blocks, starting from given block.
|
||||
Mortal(HeaderId<BlockHash, BlockNumber>, u32),
|
||||
}
|
||||
|
||||
impl<BlockNumber: Copy + UniqueSaturatedInto<u64>, BlockHash: Copy>
|
||||
TransactionEra<BlockNumber, BlockHash>
|
||||
{
|
||||
/// Prepare transaction era, based on mortality period and current best block number.
|
||||
pub fn new(
|
||||
best_block_id: HeaderId<BlockHash, BlockNumber>,
|
||||
mortality_period: Option<u32>,
|
||||
) -> Self {
|
||||
mortality_period
|
||||
.map(|mortality_period| TransactionEra::Mortal(best_block_id, mortality_period))
|
||||
.unwrap_or(TransactionEra::Immortal)
|
||||
}
|
||||
|
||||
/// Create new immortal transaction era.
|
||||
pub fn immortal() -> Self {
|
||||
TransactionEra::Immortal
|
||||
}
|
||||
|
||||
/// Returns mortality period if transaction is mortal.
|
||||
pub fn mortality_period(&self) -> Option<u32> {
|
||||
match *self {
|
||||
TransactionEra::Immortal => None,
|
||||
TransactionEra::Mortal(_, period) => Some(period),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns era that is used by FRAME-based runtimes.
|
||||
pub fn frame_era(&self) -> pezsp_runtime::generic::Era {
|
||||
match *self {
|
||||
TransactionEra::Immortal => pezsp_runtime::generic::Era::immortal(),
|
||||
// `unique_saturated_into` is fine here - mortality `u64::MAX` is not something we
|
||||
// expect to see on any chain
|
||||
TransactionEra::Mortal(header_id, period) =>
|
||||
pezsp_runtime::generic::Era::mortal(period as _, header_id.0.unique_saturated_into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns header hash that needs to be included in the signature payload.
|
||||
pub fn signed_payload(&self, genesis_hash: BlockHash) -> BlockHash {
|
||||
match *self {
|
||||
TransactionEra::Immortal => genesis_hash,
|
||||
TransactionEra::Mortal(header_id, _) => header_id.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a copy of the
|
||||
/// `pezframe_support::storage::generator::StorageMap::storage_map_final_key` for maps based
|
||||
/// on selected hasher.
|
||||
///
|
||||
/// We're using it because to call `storage_map_final_key` directly, we need access to the runtime
|
||||
/// and pezpallet instance, which (sometimes) is impossible.
|
||||
pub fn storage_map_final_key<H: StorageHasher>(
|
||||
pezpallet_prefix: &str,
|
||||
map_name: &str,
|
||||
key: &[u8],
|
||||
) -> StorageKey {
|
||||
let key_hashed = H::hash(key);
|
||||
let pezpallet_prefix_hashed = pezframe_support::Twox128::hash(pezpallet_prefix.as_bytes());
|
||||
let storage_prefix_hashed = pezframe_support::Twox128::hash(map_name.as_bytes());
|
||||
|
||||
let mut final_key = Vec::with_capacity(
|
||||
pezpallet_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.as_ref().len(),
|
||||
);
|
||||
|
||||
final_key.extend_from_slice(&pezpallet_prefix_hashed[..]);
|
||||
final_key.extend_from_slice(&storage_prefix_hashed[..]);
|
||||
final_key.extend_from_slice(key_hashed.as_ref());
|
||||
|
||||
StorageKey(final_key)
|
||||
}
|
||||
|
||||
/// This is how a storage key of storage value is computed.
|
||||
///
|
||||
/// Copied from `pezframe_support::storage::storage_prefix`.
|
||||
pub fn storage_value_key(pezpallet_prefix: &str, value_name: &str) -> StorageKey {
|
||||
let pezpallet_hash = pezsp_io::hashing::twox_128(pezpallet_prefix.as_bytes());
|
||||
let storage_hash = pezsp_io::hashing::twox_128(value_name.as_bytes());
|
||||
|
||||
let mut final_key = vec![0u8; 32];
|
||||
final_key[..16].copy_from_slice(&pezpallet_hash);
|
||||
final_key[16..].copy_from_slice(&storage_hash);
|
||||
|
||||
StorageKey(final_key)
|
||||
}
|
||||
|
||||
/// Can be use to access the runtime storage key of a `StorageMap`.
|
||||
pub trait StorageMapKeyProvider {
|
||||
/// The name of the variable that holds the `StorageMap`.
|
||||
const MAP_NAME: &'static str;
|
||||
|
||||
/// The same as `StorageMap::Hasher1`.
|
||||
type Hasher: StorageHasher;
|
||||
/// The same as `StorageMap::Key1`.
|
||||
type Key: FullCodec + Send + Sync;
|
||||
/// The same as `StorageMap::Value`.
|
||||
type Value: 'static + FullCodec;
|
||||
|
||||
/// This is a copy of the
|
||||
/// `pezframe_support::storage::generator::StorageMap::storage_map_final_key`.
|
||||
///
|
||||
/// We're using it because to call `storage_map_final_key` directly, we need access
|
||||
/// to the runtime and pezpallet instance, which (sometimes) is impossible.
|
||||
fn final_key(pezpallet_prefix: &str, key: &Self::Key) -> StorageKey {
|
||||
storage_map_final_key::<Self::Hasher>(pezpallet_prefix, Self::MAP_NAME, &key.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Can be used to access the runtime storage key of a `StorageDoubleMap`.
|
||||
pub trait StorageDoubleMapKeyProvider {
|
||||
/// The name of the variable that holds the `StorageDoubleMap`.
|
||||
const MAP_NAME: &'static str;
|
||||
|
||||
/// The same as `StorageDoubleMap::Hasher1`.
|
||||
type Hasher1: StorageHasher;
|
||||
/// The same as `StorageDoubleMap::Key1`.
|
||||
type Key1: FullCodec + Send + Sync;
|
||||
/// The same as `StorageDoubleMap::Hasher2`.
|
||||
type Hasher2: StorageHasher;
|
||||
/// The same as `StorageDoubleMap::Key2`.
|
||||
type Key2: FullCodec + Send + Sync;
|
||||
/// The same as `StorageDoubleMap::Value`.
|
||||
type Value: 'static + FullCodec;
|
||||
|
||||
/// This is a copy of the
|
||||
/// `pezframe_support::storage::generator::StorageDoubleMap::storage_double_map_final_key`.
|
||||
///
|
||||
/// We're using it because to call `storage_double_map_final_key` directly, we need access
|
||||
/// to the runtime and pezpallet instance, which (sometimes) is impossible.
|
||||
fn final_key(pezpallet_prefix: &str, key1: &Self::Key1, key2: &Self::Key2) -> StorageKey {
|
||||
let key1_hashed = Self::Hasher1::hash(&key1.encode());
|
||||
let key2_hashed = Self::Hasher2::hash(&key2.encode());
|
||||
let pezpallet_prefix_hashed = pezframe_support::Twox128::hash(pezpallet_prefix.as_bytes());
|
||||
let storage_prefix_hashed = pezframe_support::Twox128::hash(Self::MAP_NAME.as_bytes());
|
||||
|
||||
let mut final_key = Vec::with_capacity(
|
||||
pezpallet_prefix_hashed.len() +
|
||||
storage_prefix_hashed.len() +
|
||||
key1_hashed.as_ref().len() +
|
||||
key2_hashed.as_ref().len(),
|
||||
);
|
||||
|
||||
final_key.extend_from_slice(&pezpallet_prefix_hashed[..]);
|
||||
final_key.extend_from_slice(&storage_prefix_hashed[..]);
|
||||
final_key.extend_from_slice(key1_hashed.as_ref());
|
||||
final_key.extend_from_slice(key2_hashed.as_ref());
|
||||
|
||||
StorageKey(final_key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error generated by the `OwnedBridgeModule` trait.
|
||||
#[derive(Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, TypeInfo, PalletError)]
|
||||
pub enum OwnedBridgeModuleError {
|
||||
/// All pezpallet operations are halted.
|
||||
Halted,
|
||||
}
|
||||
|
||||
/// Operating mode for a bridge module.
|
||||
pub trait OperatingMode: Send + Copy + Debug + FullCodec {
|
||||
/// Returns true if the bridge module is halted.
|
||||
fn is_halted(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Basic operating modes for a bridges module (Normal/Halted).
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub enum BasicOperatingMode {
|
||||
/// Normal mode, when all operations are allowed.
|
||||
Normal,
|
||||
/// The pezpallet is halted. All operations (except operating mode change) are prohibited.
|
||||
Halted,
|
||||
}
|
||||
|
||||
impl Default for BasicOperatingMode {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
impl OperatingMode for BasicOperatingMode {
|
||||
fn is_halted(&self) -> bool {
|
||||
*self == BasicOperatingMode::Halted
|
||||
}
|
||||
}
|
||||
|
||||
const COMMON_LOG_TARGET: &'static str = "runtime::bridge-module";
|
||||
|
||||
/// Bridge module that has owner and operating mode
|
||||
pub trait OwnedBridgeModule<T: pezframe_system::Config> {
|
||||
/// The target that will be used when publishing logs related to this module.
|
||||
const LOG_TARGET: &'static str;
|
||||
|
||||
/// A storage entry that holds the module `Owner` account.
|
||||
type OwnerStorage: StorageValue<T::AccountId, Query = Option<T::AccountId>>;
|
||||
/// Operating mode type of the pezpallet.
|
||||
type OperatingMode: OperatingMode;
|
||||
/// A storage value that holds the pezpallet operating mode.
|
||||
type OperatingModeStorage: StorageValue<Self::OperatingMode, Query = Self::OperatingMode>;
|
||||
|
||||
/// Check if the module is halted.
|
||||
fn is_halted() -> bool {
|
||||
Self::OperatingModeStorage::get().is_halted()
|
||||
}
|
||||
|
||||
/// Ensure that the origin is either root, or `PalletOwner`.
|
||||
fn ensure_owner_or_root(origin: T::RuntimeOrigin) -> Result<(), BadOrigin> {
|
||||
match origin.into() {
|
||||
Ok(RawOrigin::Root) => Ok(()),
|
||||
Ok(RawOrigin::Signed(ref signer))
|
||||
if Self::OwnerStorage::get().as_ref() == Some(signer) =>
|
||||
Ok(()),
|
||||
_ => Err(BadOrigin),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that the module is not halted.
|
||||
fn ensure_not_halted() -> Result<(), OwnedBridgeModuleError> {
|
||||
match Self::is_halted() {
|
||||
true => Err(OwnedBridgeModuleError::Halted),
|
||||
false => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the owner of the module.
|
||||
fn set_owner(origin: T::RuntimeOrigin, maybe_owner: Option<T::AccountId>) -> DispatchResult {
|
||||
Self::ensure_owner_or_root(origin)?;
|
||||
match maybe_owner {
|
||||
Some(owner) => {
|
||||
Self::OwnerStorage::put(&owner);
|
||||
tracing::info!(target: COMMON_LOG_TARGET, module=%Self::LOG_TARGET, ?owner, "Setting pezpallet.");
|
||||
},
|
||||
None => {
|
||||
Self::OwnerStorage::kill();
|
||||
tracing::info!(target: COMMON_LOG_TARGET, module=%Self::LOG_TARGET, "Removed Owner of pezpallet.");
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Halt or resume all/some module operations.
|
||||
fn set_operating_mode(
|
||||
origin: T::RuntimeOrigin,
|
||||
operating_mode: Self::OperatingMode,
|
||||
) -> DispatchResult {
|
||||
Self::ensure_owner_or_root(origin)?;
|
||||
Self::OperatingModeStorage::put(operating_mode);
|
||||
tracing::info!(target: COMMON_LOG_TARGET, module=%Self::LOG_TARGET, ?operating_mode, "Setting operating mode.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pezpallet owner has a right to halt all module operations and then resume it. If it is `None`,
|
||||
/// then there are no direct ways to halt/resume module operations, but other runtime methods
|
||||
/// may still be used to do that (i.e. democracy::referendum to update halt flag directly
|
||||
/// or call the `set_operating_mode`).
|
||||
fn module_owner() -> Option<T::AccountId> {
|
||||
Self::OwnerStorage::get()
|
||||
}
|
||||
|
||||
/// The current operating mode of the module.
|
||||
/// Depending on the mode either all, some, or no transactions will be allowed.
|
||||
fn operating_mode() -> Self::OperatingMode {
|
||||
Self::OperatingModeStorage::get()
|
||||
}
|
||||
}
|
||||
|
||||
/// All extra operations with weights that we need in bridges.
|
||||
pub trait WeightExtraOps {
|
||||
/// Checked division of individual components of two weights.
|
||||
///
|
||||
/// Divides components and returns minimal division result. Returns `None` if one
|
||||
/// of `other` weight components is zero.
|
||||
fn min_components_checked_div(&self, other: Weight) -> Option<u64>;
|
||||
}
|
||||
|
||||
impl WeightExtraOps for Weight {
|
||||
fn min_components_checked_div(&self, other: Weight) -> Option<u64> {
|
||||
Some(pezsp_std::cmp::min(
|
||||
self.ref_time().checked_div(other.ref_time())?,
|
||||
self.proof_size().checked_div(other.proof_size())?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait that provides a static `str`.
|
||||
pub trait StaticStrProvider {
|
||||
/// Static string.
|
||||
const STR: &'static str;
|
||||
}
|
||||
|
||||
/// A macro that generates `StaticStrProvider` with the string set to its stringified argument.
|
||||
#[macro_export]
|
||||
macro_rules! generate_static_str_provider {
|
||||
($str:expr) => {
|
||||
$crate::paste::item! {
|
||||
pub struct [<Str $str>];
|
||||
|
||||
impl $crate::StaticStrProvider for [<Str $str>] {
|
||||
const STR: &'static str = stringify!($str);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A trait defining helper methods for `RangeInclusive` (start..=end)
|
||||
pub trait RangeInclusiveExt<Idx> {
|
||||
/// Computes the length of the `RangeInclusive`, checking for underflow and overflow.
|
||||
fn checked_len(&self) -> Option<Idx>;
|
||||
/// Computes the length of the `RangeInclusive`, saturating in case of underflow or overflow.
|
||||
fn saturating_len(&self) -> Idx;
|
||||
}
|
||||
|
||||
impl<Idx> RangeInclusiveExt<Idx> for RangeInclusive<Idx>
|
||||
where
|
||||
Idx: CheckedSub + CheckedAdd + SaturatingAdd + One + Zero,
|
||||
{
|
||||
fn checked_len(&self) -> Option<Idx> {
|
||||
self.end()
|
||||
.checked_sub(self.start())
|
||||
.and_then(|len| len.checked_add(&Idx::one()))
|
||||
}
|
||||
|
||||
fn saturating_len(&self) -> Idx {
|
||||
let len = match self.end().checked_sub(self.start()) {
|
||||
Some(len) => len,
|
||||
None => return Idx::zero(),
|
||||
};
|
||||
len.saturating_add(&Idx::one())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn storage_value_key_works() {
|
||||
assert_eq!(
|
||||
storage_value_key("PalletTransactionPayment", "NextFeeMultiplier"),
|
||||
StorageKey(
|
||||
hex_literal::hex!(
|
||||
"f0e954dfcca51a255ab12c60c789256a3f2edf3bdf381debe331ab7446addfdc"
|
||||
)
|
||||
.to_vec()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_static_str_provider_works() {
|
||||
generate_static_str_provider!(Test);
|
||||
assert_eq!(StrTest::STR, "Test");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives that may be used by different message delivery and dispatch mechanisms.
|
||||
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_support::weights::Weight;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
|
||||
/// Message dispatch result.
|
||||
#[derive(Encode, Decode, DecodeWithMemTracking, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)]
|
||||
pub struct MessageDispatchResult<DispatchLevelResult> {
|
||||
/// Unspent dispatch weight. This weight that will be deducted from total delivery transaction
|
||||
/// weight, thus reducing the transaction cost. This shall not be zero in (at least) two cases:
|
||||
///
|
||||
/// 1) if message has been dispatched successfully, but post-dispatch weight is less than the
|
||||
/// weight, declared by the message sender;
|
||||
/// 2) if message has not been dispatched at all.
|
||||
pub unspent_weight: Weight,
|
||||
/// Fine-grained result of single message dispatch (for better diagnostic purposes)
|
||||
pub dispatch_level_result: DispatchLevelResult,
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for working with storage proofs.
|
||||
|
||||
use pezframe_support::PalletError;
|
||||
use pezsp_core::RuntimeDebug;
|
||||
use pezsp_std::vec::Vec;
|
||||
use pezsp_trie::{
|
||||
accessed_nodes_tracker::AccessedNodesTracker, read_trie_value, LayoutV1, MemoryDB, StorageProof,
|
||||
};
|
||||
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use hash_db::{HashDB, Hasher, EMPTY_PREFIX};
|
||||
use scale_info::TypeInfo;
|
||||
#[cfg(feature = "test-helpers")]
|
||||
use pezsp_trie::{recorder_ext::RecorderExt, Recorder, TrieDBBuilder, TrieError, TrieHash};
|
||||
#[cfg(feature = "test-helpers")]
|
||||
use trie_db::{Trie, TrieConfiguration, TrieDBMut};
|
||||
|
||||
/// Errors that can occur when interacting with `UnverifiedStorageProof` and `VerifiedStorageProof`.
|
||||
#[derive(
|
||||
Clone, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
|
||||
)]
|
||||
pub enum StorageProofError {
|
||||
/// Call to `generate_trie_proof()` failed.
|
||||
UnableToGenerateTrieProof,
|
||||
/// Call to `verify_trie_proof()` failed.
|
||||
InvalidProof,
|
||||
/// The `Vec` entries weren't sorted as expected.
|
||||
UnsortedEntries,
|
||||
/// The provided key wasn't found.
|
||||
UnavailableKey,
|
||||
/// The value associated to the provided key is `None`.
|
||||
EmptyVal,
|
||||
/// Error decoding value associated to a provided key.
|
||||
DecodeError,
|
||||
/// At least one key or node wasn't read.
|
||||
UnusedKey,
|
||||
|
||||
/// Expected storage root is missing from the proof. (for non-compact proofs)
|
||||
StorageRootMismatch,
|
||||
/// Unable to reach expected storage value using provided trie nodes. (for non-compact proofs)
|
||||
StorageValueUnavailable,
|
||||
/// The proof contains duplicate nodes. (for non-compact proofs)
|
||||
DuplicateNodes,
|
||||
}
|
||||
|
||||
impl From<pezsp_trie::StorageProofError> for StorageProofError {
|
||||
fn from(e: pezsp_trie::StorageProofError) -> Self {
|
||||
match e {
|
||||
pezsp_trie::StorageProofError::DuplicateNodes => StorageProofError::DuplicateNodes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pezsp_trie::accessed_nodes_tracker::Error> for StorageProofError {
|
||||
fn from(e: pezsp_trie::accessed_nodes_tracker::Error) -> Self {
|
||||
match e {
|
||||
pezsp_trie::accessed_nodes_tracker::Error::UnusedNodes => StorageProofError::UnusedKey,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw storage proof type (just raw trie nodes).
|
||||
pub type RawStorageProof = pezsp_trie::RawStorageProof;
|
||||
|
||||
/// Calculates size for `RawStorageProof`.
|
||||
pub fn raw_storage_proof_size(raw_storage_proof: &RawStorageProof) -> usize {
|
||||
raw_storage_proof
|
||||
.iter()
|
||||
.fold(0usize, |sum, node| sum.saturating_add(node.len()))
|
||||
}
|
||||
|
||||
/// Storage values size requirements.
|
||||
///
|
||||
/// This is currently used by benchmarks when generating storage proofs.
|
||||
#[cfg(feature = "test-helpers")]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct UnverifiedStorageProofParams {
|
||||
/// Expected storage proof size in bytes.
|
||||
pub db_size: Option<u32>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-helpers")]
|
||||
impl UnverifiedStorageProofParams {
|
||||
/// Make storage proof parameters that require proof of at least `db_size` bytes.
|
||||
pub fn from_db_size(db_size: u32) -> Self {
|
||||
Self { db_size: Some(db_size) }
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct is used to read storage values from a subset of a Merklized database. The "proof"
|
||||
/// is a subset of the nodes in the Merkle structure of the database, so that it provides
|
||||
/// authentication against a known Merkle root as well as the values in the
|
||||
/// database themselves.
|
||||
pub struct StorageProofChecker<H>
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
root: H::Out,
|
||||
db: MemoryDB<H>,
|
||||
accessed_nodes_tracker: AccessedNodesTracker<H::Out>,
|
||||
}
|
||||
|
||||
impl<H> StorageProofChecker<H>
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
/// Constructs a new storage proof checker.
|
||||
///
|
||||
/// This returns an error if the given proof is invalid with respect to the given root.
|
||||
pub fn new(root: H::Out, proof: RawStorageProof) -> Result<Self, StorageProofError> {
|
||||
let proof = StorageProof::new_with_duplicate_nodes_check(proof)?;
|
||||
|
||||
let recorder = AccessedNodesTracker::new(proof.len());
|
||||
|
||||
let db = proof.into_memory_db();
|
||||
if !db.contains(&root, EMPTY_PREFIX) {
|
||||
return Err(StorageProofError::StorageRootMismatch);
|
||||
}
|
||||
|
||||
Ok(StorageProofChecker { root, db, accessed_nodes_tracker: recorder })
|
||||
}
|
||||
|
||||
/// Returns error if the proof has some nodes that are left intact by previous `read_value`
|
||||
/// calls.
|
||||
pub fn ensure_no_unused_nodes(self) -> Result<(), StorageProofError> {
|
||||
self.accessed_nodes_tracker.ensure_no_unused_nodes().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Reads a value from the available subset of storage. If the value cannot be read due to an
|
||||
/// incomplete or otherwise invalid proof, this function returns an error.
|
||||
pub fn read_value(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>, StorageProofError> {
|
||||
// LayoutV1 or LayoutV0 is identical for proof that only read values.
|
||||
read_trie_value::<LayoutV1<H>, _>(
|
||||
&self.db,
|
||||
&self.root,
|
||||
key,
|
||||
Some(&mut self.accessed_nodes_tracker),
|
||||
None,
|
||||
)
|
||||
.map_err(|_| StorageProofError::StorageValueUnavailable)
|
||||
}
|
||||
|
||||
/// Reads and decodes a value from the available subset of storage. If the value cannot be read
|
||||
/// due to an incomplete or otherwise invalid proof, this function returns an error. If value is
|
||||
/// read, but decoding fails, this function returns an error.
|
||||
pub fn read_and_decode_value<T: Decode>(
|
||||
&mut self,
|
||||
key: &[u8],
|
||||
) -> Result<Option<T>, StorageProofError> {
|
||||
self.read_value(key).and_then(|v| {
|
||||
v.map(|v| {
|
||||
T::decode(&mut &v[..]).map_err(|e| {
|
||||
tracing::warn!(target: "bridge-storage-proofs", error=?e, "read_and_decode_value");
|
||||
StorageProofError::DecodeError
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads and decodes a value from the available subset of storage. If the value cannot be read
|
||||
/// due to an incomplete or otherwise invalid proof, or if the value is `None`, this function
|
||||
/// returns an error. If value is read, but decoding fails, this function returns an error.
|
||||
pub fn read_and_decode_mandatory_value<T: Decode>(
|
||||
&mut self,
|
||||
key: &[u8],
|
||||
) -> Result<T, StorageProofError> {
|
||||
self.read_and_decode_value(key)?.ok_or(StorageProofError::EmptyVal)
|
||||
}
|
||||
|
||||
/// Reads and decodes a value from the available subset of storage. If the value cannot be read
|
||||
/// due to an incomplete or otherwise invalid proof, this function returns `Ok(None)`.
|
||||
/// If value is read, but decoding fails, this function returns an error.
|
||||
pub fn read_and_decode_opt_value<T: Decode>(
|
||||
&mut self,
|
||||
key: &[u8],
|
||||
) -> Result<Option<T>, StorageProofError> {
|
||||
match self.read_and_decode_value(key) {
|
||||
Ok(outbound_lane_data) => Ok(outbound_lane_data),
|
||||
Err(StorageProofError::StorageValueUnavailable) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add extra data to the storage value so that it'll be of given size.
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub fn grow_storage_value(mut value: Vec<u8>, params: &UnverifiedStorageProofParams) -> Vec<u8> {
|
||||
if let Some(db_size) = params.db_size {
|
||||
if db_size as usize > value.len() {
|
||||
value.extend(pezsp_std::iter::repeat(42u8).take(db_size as usize - value.len()));
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
/// Insert values in the provided trie at common-prefix keys in order to inflate the resulting
|
||||
/// storage proof.
|
||||
///
|
||||
/// This function can add at most 15 common-prefix keys per prefix nibble (4 bits).
|
||||
/// Each such key adds about 33 bytes (a node) to the proof.
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub fn grow_storage_proof<L: TrieConfiguration>(
|
||||
trie: &mut TrieDBMut<L>,
|
||||
prefix: Vec<u8>,
|
||||
num_extra_nodes: usize,
|
||||
) {
|
||||
use pezsp_trie::TrieMut;
|
||||
|
||||
let mut added_nodes = 0;
|
||||
for i in 0..prefix.len() {
|
||||
let mut prefix = prefix[0..=i].to_vec();
|
||||
// 1 byte has 2 nibbles (4 bits each)
|
||||
let first_nibble = (prefix[i] & 0xf0) >> 4;
|
||||
let second_nibble = prefix[i] & 0x0f;
|
||||
|
||||
// create branches at the 1st nibble
|
||||
for branch in 1..=15 {
|
||||
if added_nodes >= num_extra_nodes {
|
||||
return;
|
||||
}
|
||||
|
||||
// create branches at the 1st nibble
|
||||
prefix[i] = (first_nibble.wrapping_add(branch) % 16) << 4;
|
||||
trie.insert(&prefix, &[0; 32])
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
added_nodes += 1;
|
||||
}
|
||||
|
||||
// create branches at the 2nd nibble
|
||||
for branch in 1..=15 {
|
||||
if added_nodes >= num_extra_nodes {
|
||||
return;
|
||||
}
|
||||
|
||||
prefix[i] = (first_nibble << 4) | (second_nibble.wrapping_add(branch) % 16);
|
||||
trie.insert(&prefix, &[0; 32])
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in benchmarks");
|
||||
added_nodes += 1;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(added_nodes, num_extra_nodes)
|
||||
}
|
||||
|
||||
/// Record all keys for a given root.
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub fn record_all_keys<L: TrieConfiguration, DB>(
|
||||
db: &DB,
|
||||
root: &TrieHash<L>,
|
||||
) -> Result<RawStorageProof, pezsp_std::boxed::Box<TrieError<L>>>
|
||||
where
|
||||
DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
|
||||
{
|
||||
let mut recorder = Recorder::<L>::new();
|
||||
let trie = TrieDBBuilder::<L>::new(db, root).with_recorder(&mut recorder).build();
|
||||
for x in trie.iter()? {
|
||||
let (key, _) = x?;
|
||||
trie.get(&key)?;
|
||||
}
|
||||
|
||||
Ok(recorder.into_raw_storage_proof())
|
||||
}
|
||||
|
||||
/// Return valid storage proof and state root.
|
||||
///
|
||||
/// Note: This should only be used for **testing**.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn craft_valid_storage_proof() -> (pezsp_core::H256, RawStorageProof) {
|
||||
use pezsp_state_machine::{backend::Backend, prove_read, InMemoryBackend};
|
||||
|
||||
let state_version = pezsp_runtime::StateVersion::default();
|
||||
|
||||
// construct storage proof
|
||||
let backend = <InMemoryBackend<pezsp_core::Blake2Hasher>>::from((
|
||||
pezsp_std::vec![
|
||||
(None, vec![(b"key1".to_vec(), Some(b"value1".to_vec()))]),
|
||||
(None, vec![(b"key2".to_vec(), Some(b"value2".to_vec()))]),
|
||||
(None, vec![(b"key3".to_vec(), Some(b"value3".to_vec()))]),
|
||||
(None, vec![(b"key4".to_vec(), Some((42u64, 42u32, 42u16, 42u8).encode()))]),
|
||||
// Value is too big to fit in a branch node
|
||||
(None, vec![(b"key11".to_vec(), Some(vec![0u8; 32]))]),
|
||||
],
|
||||
state_version,
|
||||
));
|
||||
let root = backend.storage_root(pezsp_std::iter::empty(), state_version).0;
|
||||
let proof =
|
||||
prove_read(backend, &[&b"key1"[..], &b"key2"[..], &b"key4"[..], &b"key22"[..]]).unwrap();
|
||||
|
||||
(root, proof.into_nodes().into_iter().collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests_for_storage_proof_checker {
|
||||
use super::*;
|
||||
use codec::Encode;
|
||||
|
||||
#[test]
|
||||
fn storage_proof_check() {
|
||||
let (root, proof) = craft_valid_storage_proof();
|
||||
|
||||
// check proof in runtime
|
||||
let mut checker =
|
||||
<StorageProofChecker<pezsp_core::Blake2Hasher>>::new(root, proof.clone()).unwrap();
|
||||
assert_eq!(checker.read_value(b"key1"), Ok(Some(b"value1".to_vec())));
|
||||
assert_eq!(checker.read_value(b"key2"), Ok(Some(b"value2".to_vec())));
|
||||
assert_eq!(checker.read_value(b"key4"), Ok(Some((42u64, 42u32, 42u16, 42u8).encode())));
|
||||
assert_eq!(
|
||||
checker.read_value(b"key11111"),
|
||||
Err(StorageProofError::StorageValueUnavailable)
|
||||
);
|
||||
assert_eq!(checker.read_value(b"key22"), Ok(None));
|
||||
assert_eq!(checker.read_and_decode_value(b"key4"), Ok(Some((42u64, 42u32, 42u16, 42u8))),);
|
||||
assert!(matches!(
|
||||
checker.read_and_decode_value::<[u8; 64]>(b"key4"),
|
||||
Err(StorageProofError::DecodeError),
|
||||
));
|
||||
|
||||
// checking proof against invalid commitment fails
|
||||
assert_eq!(
|
||||
<StorageProofChecker<pezsp_core::Blake2Hasher>>::new(pezsp_core::H256::random(), proof).err(),
|
||||
Some(StorageProofError::StorageRootMismatch)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_with_unused_items_is_rejected() {
|
||||
let (root, proof) = craft_valid_storage_proof();
|
||||
|
||||
let mut checker =
|
||||
StorageProofChecker::<pezsp_core::Blake2Hasher>::new(root, proof.clone()).unwrap();
|
||||
checker.read_value(b"key1").unwrap().unwrap();
|
||||
checker.read_value(b"key2").unwrap();
|
||||
checker.read_value(b"key4").unwrap();
|
||||
checker.read_value(b"key22").unwrap();
|
||||
assert_eq!(checker.ensure_no_unused_nodes(), Ok(()));
|
||||
|
||||
let checker = StorageProofChecker::<pezsp_core::Blake2Hasher>::new(root, proof).unwrap();
|
||||
assert_eq!(checker.ensure_no_unused_nodes(), Err(StorageProofError::UnusedKey));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Wrapper for a runtime storage value that checks if value exceeds given maximum
|
||||
//! during conversion.
|
||||
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::traits::Get;
|
||||
use scale_info::{Type, TypeInfo};
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
use pezsp_std::{marker::PhantomData, ops::Deref};
|
||||
|
||||
/// Error that is returned when the value size exceeds maximal configured size.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct MaximalSizeExceededError {
|
||||
/// Size of the value.
|
||||
pub value_size: usize,
|
||||
/// Maximal configured size.
|
||||
pub maximal_size: usize,
|
||||
}
|
||||
|
||||
/// A bounded runtime storage value.
|
||||
#[derive(Clone, Decode, Encode, Eq, PartialEq)]
|
||||
pub struct BoundedStorageValue<B, V> {
|
||||
value: V,
|
||||
_phantom: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<B, V: pezsp_std::fmt::Debug> pezsp_std::fmt::Debug for BoundedStorageValue<B, V> {
|
||||
fn fmt(&self, fmt: &mut pezsp_std::fmt::Formatter) -> pezsp_std::fmt::Result {
|
||||
self.value.fmt(fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Get<u32>, V: Encode> BoundedStorageValue<B, V> {
|
||||
/// Construct `BoundedStorageValue` from the underlying `value` with all required checks.
|
||||
///
|
||||
/// Returns error if value size exceeds given bounds.
|
||||
pub fn try_from_inner(value: V) -> Result<Self, MaximalSizeExceededError> {
|
||||
// this conversion is heavy (since we do encoding here), so we may want to optimize it later
|
||||
// (e.g. by introducing custom Encode implementation, and turning `BoundedStorageValue` into
|
||||
// `enum BoundedStorageValue { Decoded(V), Encoded(Vec<u8>) }`)
|
||||
let value_size = value.encoded_size();
|
||||
let maximal_size = B::get() as usize;
|
||||
if value_size > maximal_size {
|
||||
Err(MaximalSizeExceededError { value_size, maximal_size })
|
||||
} else {
|
||||
Ok(BoundedStorageValue { value, _phantom: Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert into the inner type
|
||||
pub fn into_inner(self) -> V {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, V> Deref for BoundedStorageValue<B, V> {
|
||||
type Target = V;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: 'static, V: TypeInfo + 'static> TypeInfo for BoundedStorageValue<B, V> {
|
||||
type Identity = Self;
|
||||
|
||||
fn type_info() -> Type {
|
||||
V::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Get<u32>, V: Encode> MaxEncodedLen for BoundedStorageValue<B, V> {
|
||||
fn max_encoded_len() -> usize {
|
||||
B::get() as usize
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
[package]
|
||||
name = "bp-test-utils"
|
||||
version = "0.7.0"
|
||||
description = "Utilities for testing bizinikiwi-based runtime bridge code"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-test-utils"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-pezkuwi-core = { workspace = true }
|
||||
pezbp-runtime = { features = ["test-helpers"], workspace = true }
|
||||
bp-teyrchains = { workspace = true }
|
||||
codec = { workspace = true }
|
||||
ed25519-dalek = { workspace = true }
|
||||
finality-grandpa = { workspace = true }
|
||||
pezsp-application-crypto = { workspace = true }
|
||||
pezsp-consensus-grandpa = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
pezsp-trie = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-pezkuwi-core/std",
|
||||
"pezbp-runtime/std",
|
||||
"bp-teyrchains/std",
|
||||
"codec/std",
|
||||
"ed25519-dalek/std",
|
||||
"finality-grandpa/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-consensus-grandpa/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
"pezsp-trie/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-teyrchains/runtime-benchmarks",
|
||||
"pezsp-consensus-grandpa/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-trie/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities for working with test accounts.
|
||||
|
||||
use bp_header_pez_chain::{justification::JustificationVerificationContext, AuthoritySet};
|
||||
use codec::Encode;
|
||||
use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
use pezsp_std::prelude::*;
|
||||
|
||||
/// Set of test accounts with friendly names: Alice.
|
||||
pub const ALICE: Account = Account(0);
|
||||
/// Set of test accounts with friendly names: Bob.
|
||||
pub const BOB: Account = Account(1);
|
||||
/// Set of test accounts with friendly names: Charlie.
|
||||
pub const CHARLIE: Account = Account(2);
|
||||
/// Set of test accounts with friendly names: Dave.
|
||||
pub const DAVE: Account = Account(3);
|
||||
/// Set of test accounts with friendly names: Eve.
|
||||
pub const EVE: Account = Account(4);
|
||||
/// Set of test accounts with friendly names: Ferdie.
|
||||
pub const FERDIE: Account = Account(5);
|
||||
|
||||
/// A test account which can be used to sign messages.
|
||||
#[derive(RuntimeDebug, Clone, Copy)]
|
||||
pub struct Account(pub u16);
|
||||
|
||||
impl Account {
|
||||
/// Returns public key of this account.
|
||||
pub fn public(&self) -> VerifyingKey {
|
||||
self.pair().verifying_key()
|
||||
}
|
||||
|
||||
/// Returns key pair, used to sign data on behalf of this account.
|
||||
pub fn pair(&self) -> SigningKey {
|
||||
let data = self.0.encode();
|
||||
let mut bytes = [0_u8; 32];
|
||||
bytes[0..data.len()].copy_from_slice(&data);
|
||||
SigningKey::from_bytes(&bytes)
|
||||
}
|
||||
|
||||
/// Generate a signature of given message.
|
||||
pub fn sign(&self, msg: &[u8]) -> Signature {
|
||||
use ed25519_dalek::Signer;
|
||||
self.pair().sign(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Account> for AuthorityId {
|
||||
fn from(p: Account) -> Self {
|
||||
pezsp_application_crypto::UncheckedFrom::unchecked_from(p.public().to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a valid set of voters for a Grandpa round.
|
||||
pub fn voter_set() -> VoterSet<AuthorityId> {
|
||||
VoterSet::new(authority_list()).unwrap()
|
||||
}
|
||||
|
||||
/// Get a valid justification verification context for a GRANDPA round.
|
||||
pub fn verification_context(set_id: SetId) -> JustificationVerificationContext {
|
||||
AuthoritySet { authorities: authority_list(), set_id }.try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Convenience function to get a list of Grandpa authorities.
|
||||
pub fn authority_list() -> AuthorityList {
|
||||
test_keyring().iter().map(|(id, w)| (AuthorityId::from(*id), *w)).collect()
|
||||
}
|
||||
|
||||
/// Get the corresponding identities from the keyring for the "standard" authority set.
|
||||
pub fn test_keyring() -> Vec<(Account, AuthorityWeight)> {
|
||||
vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1)]
|
||||
}
|
||||
|
||||
/// Get a list of "unique" accounts.
|
||||
pub fn accounts(len: u16) -> Vec<Account> {
|
||||
(0..len).map(Account).collect()
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities for testing runtime code.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_header_pez_chain::justification::{required_justification_precommits, GrandpaJustification};
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
|
||||
use pezbp_runtime::record_all_trie_keys;
|
||||
use bp_teyrchains::teyrchain_head_storage_key_at_source;
|
||||
use codec::Encode;
|
||||
use pezsp_consensus_grandpa::{AuthorityId, AuthoritySignature, AuthorityWeight, SetId};
|
||||
use pezsp_runtime::traits::{Header as HeaderT, One, Zero};
|
||||
use pezsp_std::prelude::*;
|
||||
use pezsp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
|
||||
|
||||
// Re-export all our test account utilities
|
||||
pub use keyring::*;
|
||||
|
||||
mod keyring;
|
||||
|
||||
/// GRANDPA round number used across tests.
|
||||
pub const TEST_GRANDPA_ROUND: u64 = 1;
|
||||
/// GRANDPA validators set id used across tests.
|
||||
pub const TEST_GRANDPA_SET_ID: SetId = 1;
|
||||
/// Name of the `Paras` pezpallet used across tests.
|
||||
pub const PARAS_PALLET_NAME: &str = "Paras";
|
||||
|
||||
/// Configuration parameters when generating test GRANDPA justifications.
|
||||
#[derive(Clone)]
|
||||
pub struct JustificationGeneratorParams<H> {
|
||||
/// The header which we want to finalize.
|
||||
pub header: H,
|
||||
/// The GRANDPA round number for the current authority set.
|
||||
pub round: u64,
|
||||
/// The current authority set ID.
|
||||
pub set_id: SetId,
|
||||
/// The current GRANDPA authority set.
|
||||
///
|
||||
/// The size of the set will determine the number of pre-commits in our justification.
|
||||
pub authorities: Vec<(Account, AuthorityWeight)>,
|
||||
/// The total number of precommit ancestors in the `votes_ancestries` field our justification.
|
||||
///
|
||||
/// These may be distributed among many forks.
|
||||
pub ancestors: u32,
|
||||
/// The number of forks.
|
||||
///
|
||||
/// Useful for creating a "worst-case" scenario in which each authority is on its own fork.
|
||||
pub forks: u32,
|
||||
}
|
||||
|
||||
impl<H: HeaderT> Default for JustificationGeneratorParams<H> {
|
||||
fn default() -> Self {
|
||||
let required_signatures = required_justification_precommits(test_keyring().len() as _);
|
||||
Self {
|
||||
header: test_header(One::one()),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: test_keyring().into_iter().take(required_signatures as _).collect(),
|
||||
ancestors: 2,
|
||||
forks: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a valid GRANDPA justification with sensible defaults
|
||||
pub fn make_default_justification<H: HeaderT>(header: &H) -> GrandpaJustification<H> {
|
||||
let params = JustificationGeneratorParams::<H> { header: header.clone(), ..Default::default() };
|
||||
|
||||
make_justification_for_header(params)
|
||||
}
|
||||
|
||||
/// Generate justifications in a way where we are able to tune the number of pre-commits
|
||||
/// and vote ancestries which are included in the justification.
|
||||
///
|
||||
/// This is useful for benchmarks where we want to generate valid justifications with
|
||||
/// a specific number of pre-commits (tuned with the number of "authorities") and/or a specific
|
||||
/// number of vote ancestries (tuned with the "votes" parameter).
|
||||
///
|
||||
/// Note: This needs at least three authorities or else the verifier will complain about
|
||||
/// being given an invalid commit.
|
||||
pub fn make_justification_for_header<H: HeaderT>(
|
||||
params: JustificationGeneratorParams<H>,
|
||||
) -> GrandpaJustification<H> {
|
||||
let JustificationGeneratorParams { header, round, set_id, authorities, mut ancestors, forks } =
|
||||
params;
|
||||
let (target_hash, target_number) = (header.hash(), *header.number());
|
||||
let mut votes_ancestries = vec![];
|
||||
let mut precommits = vec![];
|
||||
|
||||
assert!(forks != 0, "Need at least one fork to have a chain..");
|
||||
assert!(
|
||||
forks as usize <= authorities.len(),
|
||||
"If we have more forks than authorities we can't create valid pre-commits for all the forks."
|
||||
);
|
||||
|
||||
// Roughly, how many vote ancestries do we want per fork
|
||||
let target_depth = ancestors.div_ceil(forks);
|
||||
|
||||
let mut unsigned_precommits = vec![];
|
||||
for i in 0..forks {
|
||||
let depth = if ancestors >= target_depth {
|
||||
ancestors -= target_depth;
|
||||
target_depth
|
||||
} else {
|
||||
ancestors
|
||||
};
|
||||
|
||||
// Note: Adding 1 to account for the target header
|
||||
let chain = generate_chain(i, depth + 1, &header);
|
||||
|
||||
// We don't include our finality target header in the vote ancestries
|
||||
for child in &chain[1..] {
|
||||
votes_ancestries.push(child.clone());
|
||||
}
|
||||
|
||||
// The header we need to use when pre-committing is the one at the highest height
|
||||
// on our chain.
|
||||
let precommit_candidate = chain.last().map(|h| (h.hash(), *h.number())).unwrap();
|
||||
unsigned_precommits.push(precommit_candidate);
|
||||
}
|
||||
|
||||
for (i, (id, _weight)) in authorities.iter().enumerate() {
|
||||
// Assign authorities to sign pre-commits in a round-robin fashion
|
||||
let target = unsigned_precommits[i % forks as usize];
|
||||
let precommit = signed_precommit::<H>(id, target, round, set_id);
|
||||
|
||||
precommits.push(precommit);
|
||||
}
|
||||
|
||||
GrandpaJustification {
|
||||
round,
|
||||
commit: finality_grandpa::Commit { target_hash, target_number, precommits },
|
||||
votes_ancestries,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_chain<H: HeaderT>(fork_id: u32, depth: u32, ancestor: &H) -> Vec<H> {
|
||||
let mut headers = vec![ancestor.clone()];
|
||||
|
||||
for i in 1..depth {
|
||||
let parent = &headers[(i - 1) as usize];
|
||||
let (hash, num) = (parent.hash(), *parent.number());
|
||||
|
||||
let mut header = test_header::<H>(num + One::one());
|
||||
header.set_parent_hash(hash);
|
||||
|
||||
// Modifying the digest so headers at the same height but in different forks have different
|
||||
// hashes
|
||||
header.digest_mut().logs.push(pezsp_runtime::DigestItem::Other(fork_id.encode()));
|
||||
|
||||
headers.push(header);
|
||||
}
|
||||
|
||||
headers
|
||||
}
|
||||
|
||||
/// Make valid proof for teyrchain `heads`
|
||||
pub fn prepare_teyrchain_heads_proof<H: HeaderT>(
|
||||
heads: Vec<(u32, ParaHead)>,
|
||||
) -> (H::Hash, ParaHeadsProof, Vec<(ParaId, ParaHash)>) {
|
||||
let mut teyrchains = Vec::with_capacity(heads.len());
|
||||
let mut root = Default::default();
|
||||
let mut mdb = MemoryDB::default();
|
||||
let mut storage_keys = vec![];
|
||||
{
|
||||
let mut trie = TrieDBMutBuilderV1::<H::Hashing>::new(&mut mdb, &mut root).build();
|
||||
for (teyrchain, head) in heads {
|
||||
let storage_key =
|
||||
teyrchain_head_storage_key_at_source(PARAS_PALLET_NAME, ParaId(teyrchain));
|
||||
trie.insert(&storage_key.0, &head.encode())
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in tests");
|
||||
storage_keys.push(storage_key.0);
|
||||
teyrchains.push((ParaId(teyrchain), head.hash()));
|
||||
}
|
||||
}
|
||||
|
||||
// generate storage proof to be delivered to this chain
|
||||
let storage_proof = record_all_trie_keys::<LayoutV1<H::Hashing>, _>(&mdb, &root)
|
||||
.map_err(|_| "record_all_trie_keys has failed")
|
||||
.expect("record_all_trie_keys should not fail in benchmarks");
|
||||
|
||||
(root, ParaHeadsProof { storage_proof }, teyrchains)
|
||||
}
|
||||
|
||||
/// Create signed precommit with given target.
|
||||
pub fn signed_precommit<H: HeaderT>(
|
||||
signer: &Account,
|
||||
target: (H::Hash, H::Number),
|
||||
round: u64,
|
||||
set_id: SetId,
|
||||
) -> finality_grandpa::SignedPrecommit<H::Hash, H::Number, AuthoritySignature, AuthorityId> {
|
||||
let precommit = finality_grandpa::Precommit { target_hash: target.0, target_number: target.1 };
|
||||
|
||||
let encoded = pezsp_consensus_grandpa::localized_payload(
|
||||
round,
|
||||
set_id,
|
||||
&finality_grandpa::Message::Precommit(precommit.clone()),
|
||||
);
|
||||
|
||||
let signature = signer.sign(&encoded);
|
||||
let raw_signature: Vec<u8> = signature.to_bytes().into();
|
||||
|
||||
// Need to wrap our signature and id types that they match what our `SignedPrecommit` is
|
||||
// expecting
|
||||
let signature = AuthoritySignature::try_from(raw_signature).expect(
|
||||
"We know our Keypair is good,
|
||||
so our signature must also be good.",
|
||||
);
|
||||
let id = (*signer).into();
|
||||
|
||||
finality_grandpa::SignedPrecommit { precommit, signature, id }
|
||||
}
|
||||
|
||||
/// Get a header for testing.
|
||||
///
|
||||
/// The correct parent hash will be used if given a non-zero header.
|
||||
pub fn test_header<H: HeaderT>(number: H::Number) -> H {
|
||||
let default = |num| {
|
||||
H::new(num, Default::default(), Default::default(), Default::default(), Default::default())
|
||||
};
|
||||
|
||||
let mut header = default(number);
|
||||
if number != Zero::zero() {
|
||||
let parent_hash = default(number - One::one()).hash();
|
||||
header.set_parent_hash(parent_hash);
|
||||
}
|
||||
|
||||
header
|
||||
}
|
||||
|
||||
/// Get a header for testing with given `state_root`.
|
||||
///
|
||||
/// The correct parent hash will be used if given a non-zero header.
|
||||
pub fn test_header_with_root<H: HeaderT>(number: H::Number, state_root: H::Hash) -> H {
|
||||
let mut header: H = test_header(number);
|
||||
header.set_state_root(state_root);
|
||||
header
|
||||
}
|
||||
|
||||
/// Convenience function for generating a Header ID at a given block number.
|
||||
pub fn header_id<H: HeaderT>(index: u8) -> (H::Hash, H::Number) {
|
||||
(test_header::<H>(index.into()).hash(), index.into())
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
/// Adds methods for testing the `set_owner()` and `set_operating_mode()` for a pezpallet.
|
||||
/// Some values are hardcoded like:
|
||||
/// - `run_test()`
|
||||
/// - `Pezpallet::<TestRuntime>`
|
||||
/// - `PalletOwner::<TestRuntime>`
|
||||
/// - `PalletOperatingMode::<TestRuntime>`
|
||||
/// While this is not ideal, all the pallets use the same names, so it works for the moment.
|
||||
/// We can revisit this in the future if anything changes.
|
||||
macro_rules! generate_owned_bridge_module_tests {
|
||||
($normal_operating_mode: expr, $halted_operating_mode: expr) => {
|
||||
#[test]
|
||||
fn test_set_owner() {
|
||||
run_test(|| {
|
||||
PalletOwner::<TestRuntime>::put(1);
|
||||
|
||||
// The root should be able to change the owner.
|
||||
assert_ok!(Pezpallet::<TestRuntime>::set_owner(RuntimeOrigin::root(), Some(2)));
|
||||
assert_eq!(PalletOwner::<TestRuntime>::get(), Some(2));
|
||||
|
||||
// The owner should be able to change the owner.
|
||||
assert_ok!(Pezpallet::<TestRuntime>::set_owner(RuntimeOrigin::signed(2), Some(3)));
|
||||
assert_eq!(PalletOwner::<TestRuntime>::get(), Some(3));
|
||||
|
||||
// Other users shouldn't be able to change the owner.
|
||||
assert_noop!(
|
||||
Pezpallet::<TestRuntime>::set_owner(RuntimeOrigin::signed(1), Some(4)),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_eq!(PalletOwner::<TestRuntime>::get(), Some(3));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_operating_mode() {
|
||||
run_test(|| {
|
||||
PalletOwner::<TestRuntime>::put(1);
|
||||
PalletOperatingMode::<TestRuntime>::put($normal_operating_mode);
|
||||
|
||||
// The root should be able to halt the pezpallet.
|
||||
assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
|
||||
RuntimeOrigin::root(),
|
||||
$halted_operating_mode
|
||||
));
|
||||
assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $halted_operating_mode);
|
||||
// The root should be able to resume the pezpallet.
|
||||
assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
|
||||
RuntimeOrigin::root(),
|
||||
$normal_operating_mode
|
||||
));
|
||||
assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $normal_operating_mode);
|
||||
|
||||
// The owner should be able to halt the pezpallet.
|
||||
assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
|
||||
RuntimeOrigin::signed(1),
|
||||
$halted_operating_mode
|
||||
));
|
||||
assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $halted_operating_mode);
|
||||
// The owner should be able to resume the pezpallet.
|
||||
assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
|
||||
RuntimeOrigin::signed(1),
|
||||
$normal_operating_mode
|
||||
));
|
||||
assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $normal_operating_mode);
|
||||
|
||||
// Other users shouldn't be able to halt the pezpallet.
|
||||
assert_noop!(
|
||||
Pezpallet::<TestRuntime>::set_operating_mode(
|
||||
RuntimeOrigin::signed(2),
|
||||
$halted_operating_mode
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $normal_operating_mode);
|
||||
// Other users shouldn't be able to resume the pezpallet.
|
||||
PalletOperatingMode::<TestRuntime>::put($halted_operating_mode);
|
||||
assert_noop!(
|
||||
Pezpallet::<TestRuntime>::set_operating_mode(
|
||||
RuntimeOrigin::signed(2),
|
||||
$normal_operating_mode
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $halted_operating_mode);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
[package]
|
||||
name = "bp-teyrchains"
|
||||
description = "Primitives of teyrchains module."
|
||||
version = "0.7.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-teyrchains"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
impl-trait-for-tuples = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-header-pez-chain = { workspace = true }
|
||||
bp-pezkuwi-core = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-pez-chain/std",
|
||||
"bp-pezkuwi-core/std",
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines structures related to calls of the `pezpallet-bridge-teyrchains` pezpallet.
|
||||
|
||||
use crate::{ParaHash, ParaId, RelayBlockHash, RelayBlockNumber};
|
||||
|
||||
use bp_pezkuwi_core::teyrchains::ParaHeadsProof;
|
||||
use pezbp_runtime::HeaderId;
|
||||
use codec::{Decode, Encode};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
use pezsp_std::vec::Vec;
|
||||
|
||||
/// A minimized version of `pezpallet-bridge-teyrchains::Call` that can be used without a runtime.
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeTeyrchainCall {
|
||||
/// `pezpallet-bridge-teyrchains::Call::submit_teyrchain_heads`
|
||||
#[codec(index = 0)]
|
||||
submit_teyrchain_heads {
|
||||
/// Relay chain block, for which we have submitted the `teyrchain_heads_proof`.
|
||||
at_relay_block: (RelayBlockNumber, RelayBlockHash),
|
||||
/// Teyrchain identifiers and their head hashes.
|
||||
teyrchains: Vec<(ParaId, ParaHash)>,
|
||||
/// Teyrchain heads proof.
|
||||
teyrchain_heads_proof: ParaHeadsProof,
|
||||
},
|
||||
}
|
||||
|
||||
/// Info about a `SubmitTeyrchainHeads` call which tries to update a single teyrchain.
|
||||
///
|
||||
/// The pezpallet supports updating multiple teyrchain heads at once,
|
||||
#[derive(PartialEq, RuntimeDebug)]
|
||||
pub struct SubmitTeyrchainHeadsInfo {
|
||||
/// Number and hash of the finalized relay block that has been used to prove teyrchain
|
||||
/// finality.
|
||||
pub at_relay_block: HeaderId<RelayBlockHash, RelayBlockNumber>,
|
||||
/// Teyrchain identifier.
|
||||
pub para_id: ParaId,
|
||||
/// Hash of the bundled teyrchain head.
|
||||
pub para_head_hash: ParaHash,
|
||||
/// If `true`, then the call must be free (assuming that everything else is valid) to
|
||||
/// be treated as valid.
|
||||
pub is_free_execution_expected: bool,
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of teyrchains module.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use bp_header_pez_chain::StoredHeaderData;
|
||||
pub use call_info::{BridgeTeyrchainCall, SubmitTeyrchainHeadsInfo};
|
||||
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHash, ParaHead, ParaId};
|
||||
use pezbp_runtime::{
|
||||
BlockNumberOf, Chain, HashOf, HeaderOf, StorageDoubleMapKeyProvider, StorageMapKeyProvider,
|
||||
Teyrchain,
|
||||
};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{weights::Weight, Blake2_128Concat, Twox64Concat};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::storage::StorageKey;
|
||||
use pezsp_runtime::{traits::Header as HeaderT, RuntimeDebug};
|
||||
use pezsp_std::{marker::PhantomData, prelude::*};
|
||||
|
||||
/// Block hash of the bridged relay chain.
|
||||
pub type RelayBlockHash = bp_pezkuwi_core::Hash;
|
||||
/// Block number of the bridged relay chain.
|
||||
pub type RelayBlockNumber = bp_pezkuwi_core::BlockNumber;
|
||||
/// Hasher of the bridged relay chain.
|
||||
pub type RelayBlockHasher = bp_pezkuwi_core::Hasher;
|
||||
|
||||
mod call_info;
|
||||
|
||||
/// Best known teyrchain head hash.
|
||||
#[derive(Clone, Decode, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct BestParaHeadHash {
|
||||
/// Number of relay block where this head has been read.
|
||||
///
|
||||
/// Teyrchain head is opaque to relay chain. So we can't simply decode it as a header of
|
||||
/// teyrchains and call `block_number()` on it. Instead, we're using the fact that teyrchain
|
||||
/// head is always built on top of previous head (because it is blockchain) and relay chain
|
||||
/// always imports teyrchain heads in order. What it means for us is that at any given
|
||||
/// **finalized** relay block `B`, head of teyrchain will be ancestor (or the same) of all
|
||||
/// teyrchain heads available at descendants of `B`.
|
||||
pub at_relay_block_number: RelayBlockNumber,
|
||||
/// Hash of teyrchain head.
|
||||
pub head_hash: ParaHash,
|
||||
}
|
||||
|
||||
/// Best known teyrchain head as it is stored in the runtime storage.
|
||||
#[derive(Decode, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct ParaInfo {
|
||||
/// Best known teyrchain head hash.
|
||||
pub best_head_hash: BestParaHeadHash,
|
||||
/// Current ring buffer position for this teyrchain.
|
||||
pub next_imported_hash_position: u32,
|
||||
}
|
||||
|
||||
/// Returns runtime storage key of given teyrchain head at the source chain.
|
||||
///
|
||||
/// The head is stored by the `paras` pezpallet in the `Heads` map.
|
||||
pub fn teyrchain_head_storage_key_at_source(
|
||||
paras_pallet_name: &str,
|
||||
para_id: ParaId,
|
||||
) -> StorageKey {
|
||||
pezbp_runtime::storage_map_final_key::<Twox64Concat>(paras_pallet_name, "Heads", ¶_id.encode())
|
||||
}
|
||||
|
||||
/// Can be use to access the runtime storage key of the teyrchains info at the target chain.
|
||||
///
|
||||
/// The info is stored by the `pezpallet-bridge-teyrchains` pezpallet in the `ParasInfo` map.
|
||||
pub struct ParasInfoKeyProvider;
|
||||
impl StorageMapKeyProvider for ParasInfoKeyProvider {
|
||||
const MAP_NAME: &'static str = "ParasInfo";
|
||||
|
||||
type Hasher = Blake2_128Concat;
|
||||
type Key = ParaId;
|
||||
type Value = ParaInfo;
|
||||
}
|
||||
|
||||
/// Can be use to access the runtime storage key of the teyrchain head at the target chain.
|
||||
///
|
||||
/// The head is stored by the `pezpallet-bridge-teyrchains` pezpallet in the `ImportedParaHeads` map.
|
||||
pub struct ImportedParaHeadsKeyProvider;
|
||||
impl StorageDoubleMapKeyProvider for ImportedParaHeadsKeyProvider {
|
||||
const MAP_NAME: &'static str = "ImportedParaHeads";
|
||||
|
||||
type Hasher1 = Blake2_128Concat;
|
||||
type Key1 = ParaId;
|
||||
type Hasher2 = Blake2_128Concat;
|
||||
type Key2 = ParaHash;
|
||||
type Value = ParaStoredHeaderData;
|
||||
}
|
||||
|
||||
/// Stored data of the teyrchain head. It is encoded version of the
|
||||
/// `pezbp_runtime::StoredHeaderData` structure.
|
||||
///
|
||||
/// We do not know exact structure of the teyrchain head, so we always store encoded version
|
||||
/// of the `pezbp_runtime::StoredHeaderData`. It is only decoded when we talk about specific teyrchain.
|
||||
#[derive(Clone, Decode, Encode, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct ParaStoredHeaderData(pub Vec<u8>);
|
||||
|
||||
impl ParaStoredHeaderData {
|
||||
/// Decode stored teyrchain head data.
|
||||
pub fn decode_teyrchain_head_data<C: Chain>(
|
||||
&self,
|
||||
) -> Result<StoredHeaderData<BlockNumberOf<C>, HashOf<C>>, codec::Error> {
|
||||
StoredHeaderData::<BlockNumberOf<C>, HashOf<C>>::decode(&mut &self.0[..])
|
||||
}
|
||||
}
|
||||
|
||||
/// Stored teyrchain head data builder.
|
||||
pub trait ParaStoredHeaderDataBuilder {
|
||||
/// Maximal teyrchain head size that we may accept for free. All heads above
|
||||
/// this limit are submitted for a regular fee.
|
||||
fn max_free_head_size() -> u32;
|
||||
|
||||
/// Return number of teyrchains that are supported by this builder.
|
||||
fn supported_teyrchains() -> u32;
|
||||
|
||||
/// Try to build head data from encoded head of teyrchain with given id.
|
||||
fn try_build(para_id: ParaId, para_head: &ParaHead) -> Option<ParaStoredHeaderData>;
|
||||
}
|
||||
|
||||
/// Helper for using single teyrchain as `ParaStoredHeaderDataBuilder`.
|
||||
pub struct SingleParaStoredHeaderDataBuilder<C: Teyrchain>(PhantomData<C>);
|
||||
|
||||
impl<C: Teyrchain> ParaStoredHeaderDataBuilder for SingleParaStoredHeaderDataBuilder<C> {
|
||||
fn max_free_head_size() -> u32 {
|
||||
C::MAX_HEADER_SIZE
|
||||
}
|
||||
|
||||
fn supported_teyrchains() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn try_build(para_id: ParaId, para_head: &ParaHead) -> Option<ParaStoredHeaderData> {
|
||||
if para_id == ParaId(C::TEYRCHAIN_ID) {
|
||||
let header = HeaderOf::<C>::decode(&mut ¶_head.0[..]).ok()?;
|
||||
return Some(ParaStoredHeaderData(
|
||||
StoredHeaderData { number: *header.number(), state_root: *header.state_root() }
|
||||
.encode(),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to build header data from each tuple member, short-circuiting on first successful one.
|
||||
#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
|
||||
#[tuple_types_custom_trait_bound(Teyrchain)]
|
||||
impl ParaStoredHeaderDataBuilder for C {
|
||||
fn max_free_head_size() -> u32 {
|
||||
let mut result = 0_u32;
|
||||
for_tuples!( #(
|
||||
result = pezsp_std::cmp::max(
|
||||
result,
|
||||
SingleParaStoredHeaderDataBuilder::<C>::max_free_head_size(),
|
||||
);
|
||||
)* );
|
||||
result
|
||||
}
|
||||
|
||||
fn supported_teyrchains() -> u32 {
|
||||
let mut result = 0;
|
||||
for_tuples!( #(
|
||||
result += SingleParaStoredHeaderDataBuilder::<C>::supported_teyrchains();
|
||||
)* );
|
||||
result
|
||||
}
|
||||
|
||||
fn try_build(para_id: ParaId, para_head: &ParaHead) -> Option<ParaStoredHeaderData> {
|
||||
for_tuples!( #(
|
||||
let maybe_para_head = SingleParaStoredHeaderDataBuilder::<C>::try_build(para_id, para_head);
|
||||
if let Some(maybe_para_head) = maybe_para_head {
|
||||
return Some(maybe_para_head);
|
||||
}
|
||||
)* );
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime hook for when a teyrchain head is updated.
|
||||
pub trait OnNewHead {
|
||||
/// Called when a teyrchain head is updated.
|
||||
/// Returns the weight consumed by this function.
|
||||
fn on_new_head(id: ParaId, head: &ParaHead) -> Weight;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(8)]
|
||||
impl OnNewHead for Tuple {
|
||||
fn on_new_head(id: ParaId, head: &ParaHead) -> Weight {
|
||||
let mut weight: Weight = Default::default();
|
||||
for_tuples!( #( weight.saturating_accrue(Tuple::on_new_head(id, head)); )* );
|
||||
weight
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "bp-xcm-bridge-hub-router"
|
||||
description = "Primitives of the xcm-bridge-hub fee pezpallet."
|
||||
version = "0.6.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-xcm-bridge-hub-router"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["bit-vec", "derive"], workspace = true }
|
||||
scale-info = { features = ["bit-vec", "derive"], workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
# Pezkuwi Dependencies
|
||||
xcm = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of the `xcm-bridge-hub-router` pezpallet.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{FixedU128, RuntimeDebug};
|
||||
use xcm::latest::prelude::Location;
|
||||
|
||||
/// Minimal delivery fee factor.
|
||||
pub const MINIMAL_DELIVERY_FEE_FACTOR: FixedU128 = FixedU128::from_u32(1);
|
||||
|
||||
/// XCM channel status provider that may report whether it is congested or not.
|
||||
///
|
||||
/// By channel we mean the physical channel that is used to deliver messages of one
|
||||
/// of the bridge queues.
|
||||
pub trait XcmChannelStatusProvider {
|
||||
/// Returns true if the channel is currently congested.
|
||||
fn is_congested(with: &Location) -> bool;
|
||||
}
|
||||
|
||||
impl XcmChannelStatusProvider for () {
|
||||
fn is_congested(_with: &Location) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Current status of the bridge.
|
||||
#[derive(Clone, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen, RuntimeDebug)]
|
||||
pub struct BridgeState {
|
||||
/// Current delivery fee factor.
|
||||
pub delivery_fee_factor: FixedU128,
|
||||
/// Bridge congestion flag.
|
||||
pub is_congested: bool,
|
||||
}
|
||||
|
||||
impl Default for BridgeState {
|
||||
fn default() -> BridgeState {
|
||||
BridgeState { delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR, is_congested: false }
|
||||
}
|
||||
}
|
||||
|
||||
/// A minimized version of `pezpallet-xcm-bridge-hub-router::Call` that can be used without a runtime.
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum XcmBridgeHubRouterCall {
|
||||
/// `pezpallet-xcm-bridge-hub-router::Call::report_bridge_status`
|
||||
#[codec(index = 0)]
|
||||
report_bridge_status { bridge_id: H256, is_congested: bool },
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
[package]
|
||||
name = "bp-xcm-bridge-hub"
|
||||
description = "Primitives of the xcm-bridge-hub pezpallet."
|
||||
version = "0.2.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/bp-xcm-bridge-hub"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], workspace = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-messages = { workspace = true }
|
||||
pezbp-runtime = { workspace = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
# Pezkuwi Dependencies
|
||||
xcm = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-messages/std",
|
||||
"pezbp-runtime/std",
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-std/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines structures related to calls of the `pezpallet-xcm-bridge-hub` pezpallet.
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use codec::{Decode, Encode};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_std::boxed::Box;
|
||||
use xcm::prelude::VersionedInteriorLocation;
|
||||
|
||||
/// A minimized version of `pezpallet_xcm_bridge_hub::Call` that can be used without a runtime.
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum XcmBridgeHubCall {
|
||||
/// `pezpallet_xcm_bridge_hub::Call::open_bridge`
|
||||
#[codec(index = 0)]
|
||||
open_bridge {
|
||||
/// Universal `InteriorLocation` from the bridged consensus.
|
||||
bridge_destination_universal_location: Box<VersionedInteriorLocation>,
|
||||
},
|
||||
/// `pezpallet_xcm_bridge_hub::Call::close_bridge`
|
||||
#[codec(index = 1)]
|
||||
close_bridge {
|
||||
/// Universal `InteriorLocation` from the bridged consensus.
|
||||
bridge_destination_universal_location: Box<VersionedInteriorLocation>,
|
||||
/// The number of messages that we may prune in a single call.
|
||||
may_prune_messages: MessageNonce,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,715 @@
|
||||
// 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 of the xcm-bridge-hub pezpallet.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_messages::LaneIdType;
|
||||
use pezbp_runtime::{AccountIdOf, BalanceOf, Chain};
|
||||
pub use call_info::XcmBridgeHubCall;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{
|
||||
ensure, pezsp_runtime::RuntimeDebug, CloneNoBound, PalletError, PartialEqNoBound,
|
||||
RuntimeDebugNoBound,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pezsp_core::H256;
|
||||
use pezsp_io::hashing::blake2_256;
|
||||
use pezsp_std::boxed::Box;
|
||||
use xcm::{
|
||||
latest::prelude::*, prelude::XcmVersion, IntoVersion, VersionedInteriorLocation,
|
||||
VersionedLocation,
|
||||
};
|
||||
|
||||
mod call_info;
|
||||
|
||||
/// Encoded XCM blob. We expect the bridge messages pezpallet to use this blob type for both inbound
|
||||
/// and outbound payloads.
|
||||
pub type XcmAsPlainPayload = pezsp_std::vec::Vec<u8>;
|
||||
|
||||
/// Bridge identifier - used **only** for communicating with sibling/parent chains in the same
|
||||
/// consensus.
|
||||
///
|
||||
/// For example, `SendXcm` implementations (which use the `latest` XCM) can use it to identify a
|
||||
/// bridge and the corresponding `LaneId` that is used for over-consensus communication between
|
||||
/// bridge hubs.
|
||||
///
|
||||
/// This identifier is constructed from the `latest` XCM, so it is expected to ensure migration to
|
||||
/// the `latest` XCM version. This could change the `BridgeId`, but it will not affect the `LaneId`.
|
||||
/// In other words, `LaneId` will never change, while `BridgeId` could change with (every) XCM
|
||||
/// upgrade.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Decode,
|
||||
Encode,
|
||||
DecodeWithMemTracking,
|
||||
Eq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
PartialEq,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct BridgeId(H256);
|
||||
|
||||
impl BridgeId {
|
||||
/// Create bridge identifier from two universal locations.
|
||||
///
|
||||
/// Note: The `BridgeId` is constructed from `latest` XCM, so if stored, you need to ensure
|
||||
/// compatibility with newer XCM versions.
|
||||
pub fn new(
|
||||
universal_source: &InteriorLocation,
|
||||
universal_destination: &InteriorLocation,
|
||||
) -> Self {
|
||||
const VALUES_SEPARATOR: [u8; 33] = *b"bridges-bridge-id-value-separator";
|
||||
|
||||
BridgeId(
|
||||
(universal_source, VALUES_SEPARATOR, universal_destination)
|
||||
.using_encoded(blake2_256)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Access the inner representation.
|
||||
pub fn inner(&self) -> H256 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for BridgeId {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
core::fmt::Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Local XCM channel manager.
|
||||
pub trait LocalXcmChannelManager {
|
||||
/// Error that may be returned when suspending/resuming the bridge.
|
||||
type Error: pezsp_std::fmt::Debug;
|
||||
|
||||
/// Returns true if the channel with given location is currently congested.
|
||||
///
|
||||
/// The `with` is guaranteed to be in the same consensus. However, it may point to something
|
||||
/// below the chain level - like the contract or pezpallet instance, for example.
|
||||
fn is_congested(with: &Location) -> bool;
|
||||
|
||||
/// Suspend the bridge, opened by given origin.
|
||||
///
|
||||
/// The `local_origin` is guaranteed to be in the same consensus. However, it may point to
|
||||
/// something below the chain level - like the contract or pezpallet instance, for example.
|
||||
fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error>;
|
||||
|
||||
/// Resume the previously suspended bridge, opened by given origin.
|
||||
///
|
||||
/// The `local_origin` is guaranteed to be in the same consensus. However, it may point to
|
||||
/// something below the chain level - like the contract or pezpallet instance, for example.
|
||||
fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl LocalXcmChannelManager for () {
|
||||
type Error = ();
|
||||
|
||||
fn is_congested(_with: &Location) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn suspend_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resume_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Bridge state.
|
||||
#[derive(Clone, Copy, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen, RuntimeDebug)]
|
||||
pub enum BridgeState {
|
||||
/// Bridge is opened. Associated lanes are also opened.
|
||||
Opened,
|
||||
/// Bridge is suspended. Associated lanes are opened.
|
||||
///
|
||||
/// We keep accepting messages to the bridge. The only difference with the `Opened` state
|
||||
/// is that we have sent the "Suspended" message/signal to the local bridge origin.
|
||||
Suspended,
|
||||
/// Bridge is closed. Associated lanes are also closed.
|
||||
/// After all outbound messages will be pruned, the bridge will vanish without any traces.
|
||||
Closed,
|
||||
}
|
||||
|
||||
/// Bridge metadata.
|
||||
#[derive(
|
||||
CloneNoBound, Decode, Encode, Eq, PartialEqNoBound, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound,
|
||||
)]
|
||||
#[scale_info(skip_type_params(ThisChain, LaneId))]
|
||||
pub struct Bridge<ThisChain: Chain, LaneId: LaneIdType> {
|
||||
/// Relative location of the bridge origin chain. This is expected to be **convertible** to the
|
||||
/// `latest` XCM, so the check and migration needs to be ensured.
|
||||
pub bridge_origin_relative_location: Box<VersionedLocation>,
|
||||
|
||||
/// See [`BridgeLocations::bridge_origin_universal_location`].
|
||||
/// Stored for `BridgeId` sanity check.
|
||||
pub bridge_origin_universal_location: Box<VersionedInteriorLocation>,
|
||||
/// See [`BridgeLocations::bridge_destination_universal_location`].
|
||||
/// Stored for `BridgeId` sanity check.
|
||||
pub bridge_destination_universal_location: Box<VersionedInteriorLocation>,
|
||||
|
||||
/// Current bridge state.
|
||||
pub state: BridgeState,
|
||||
/// Account with the reserved funds. Derived from `self.bridge_origin_relative_location`.
|
||||
pub bridge_owner_account: AccountIdOf<ThisChain>,
|
||||
/// Reserved amount on the sovereign account of the sibling bridge origin.
|
||||
pub deposit: BalanceOf<ThisChain>,
|
||||
|
||||
/// Mapping to the unique `LaneId`.
|
||||
pub lane_id: LaneId,
|
||||
}
|
||||
|
||||
/// Locations of bridge endpoints at both sides of the bridge.
|
||||
#[derive(Clone, RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct BridgeLocations {
|
||||
/// Relative (to this bridge hub) location of this side of the bridge.
|
||||
bridge_origin_relative_location: Location,
|
||||
/// Universal (unique) location of this side of the bridge.
|
||||
bridge_origin_universal_location: InteriorLocation,
|
||||
/// Universal (unique) location of other side of the bridge.
|
||||
bridge_destination_universal_location: InteriorLocation,
|
||||
/// An identifier of the dedicated bridge message lane.
|
||||
bridge_id: BridgeId,
|
||||
}
|
||||
|
||||
/// Errors that may happen when we check bridge locations.
|
||||
#[derive(
|
||||
Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
|
||||
)]
|
||||
pub enum BridgeLocationsError {
|
||||
/// Origin or destination locations are not universal.
|
||||
NonUniversalLocation,
|
||||
/// Bridge origin location is not supported.
|
||||
InvalidBridgeOrigin,
|
||||
/// Bridge destination is not supported (in general).
|
||||
InvalidBridgeDestination,
|
||||
/// Destination location is within the same global consensus.
|
||||
DestinationIsLocal,
|
||||
/// Destination network is not the network we are bridged with.
|
||||
UnreachableDestination,
|
||||
/// Destination location is unsupported. We only support bridges with relay
|
||||
/// chain or its teyrchains.
|
||||
UnsupportedDestinationLocation,
|
||||
/// The version of XCM location argument is unsupported.
|
||||
UnsupportedXcmVersion,
|
||||
/// The `LaneIdType` generator is not supported.
|
||||
UnsupportedLaneIdType,
|
||||
}
|
||||
|
||||
impl BridgeLocations {
|
||||
/// Given XCM locations, generate lane id and universal locations of bridge endpoints.
|
||||
///
|
||||
/// The `here_universal_location` is the universal location of the bridge hub runtime.
|
||||
///
|
||||
/// The `bridge_origin_relative_location` is the relative (to the `here_universal_location`)
|
||||
/// location of the bridge endpoint at this side of the bridge. It may be the parent relay
|
||||
/// chain or the sibling teyrchain. All junctions below teyrchain level are dropped.
|
||||
///
|
||||
/// The `bridge_destination_universal_location` is the universal location of the bridge
|
||||
/// destination. It may be the parent relay or the sibling teyrchain of the **bridged**
|
||||
/// bridge hub. All junctions below teyrchain level are dropped.
|
||||
///
|
||||
/// Why we drop all junctions between teyrchain level - that's because the lane is a bridge
|
||||
/// between two chains. All routing under this level happens when the message is delivered
|
||||
/// to the bridge destination. So at bridge level we don't care about low level junctions.
|
||||
///
|
||||
/// Returns error if `bridge_origin_relative_location` is outside of `here_universal_location`
|
||||
/// local consensus OR if `bridge_destination_universal_location` is not a universal location.
|
||||
pub fn bridge_locations(
|
||||
here_universal_location: InteriorLocation,
|
||||
bridge_origin_relative_location: Location,
|
||||
bridge_destination_universal_location: InteriorLocation,
|
||||
expected_remote_network: NetworkId,
|
||||
) -> Result<Box<Self>, BridgeLocationsError> {
|
||||
fn strip_low_level_junctions(
|
||||
location: InteriorLocation,
|
||||
) -> Result<InteriorLocation, BridgeLocationsError> {
|
||||
let mut junctions = location.into_iter();
|
||||
|
||||
let global_consensus = junctions
|
||||
.next()
|
||||
.filter(|junction| matches!(junction, GlobalConsensus(_)))
|
||||
.ok_or(BridgeLocationsError::NonUniversalLocation)?;
|
||||
|
||||
// we only expect `Teyrchain` junction here. There are other junctions that
|
||||
// may need to be supported (like `GeneralKey` and `OnlyChild`), but now we
|
||||
// only support bridges with relay and parachans
|
||||
//
|
||||
// if there's something other than teyrchain, let's strip it
|
||||
let maybe_teyrchain =
|
||||
junctions.next().filter(|junction| matches!(junction, Teyrchain(_)));
|
||||
Ok(match maybe_teyrchain {
|
||||
Some(teyrchain) => [global_consensus, teyrchain].into(),
|
||||
None => [global_consensus].into(),
|
||||
})
|
||||
}
|
||||
|
||||
// ensure that the `here_universal_location` and `bridge_destination_universal_location`
|
||||
// are universal locations within different consensus systems
|
||||
let local_network = here_universal_location
|
||||
.global_consensus()
|
||||
.map_err(|_| BridgeLocationsError::NonUniversalLocation)?;
|
||||
let remote_network = bridge_destination_universal_location
|
||||
.global_consensus()
|
||||
.map_err(|_| BridgeLocationsError::NonUniversalLocation)?;
|
||||
ensure!(local_network != remote_network, BridgeLocationsError::DestinationIsLocal);
|
||||
ensure!(
|
||||
remote_network == expected_remote_network,
|
||||
BridgeLocationsError::UnreachableDestination
|
||||
);
|
||||
|
||||
// get universal location of endpoint, located at this side of the bridge
|
||||
let bridge_origin_universal_location = here_universal_location
|
||||
.within_global(bridge_origin_relative_location.clone())
|
||||
.map_err(|_| BridgeLocationsError::InvalidBridgeOrigin)?;
|
||||
// strip low-level junctions within universal locations
|
||||
let bridge_origin_universal_location =
|
||||
strip_low_level_junctions(bridge_origin_universal_location)?;
|
||||
let bridge_destination_universal_location =
|
||||
strip_low_level_junctions(bridge_destination_universal_location)?;
|
||||
|
||||
// we know that the `bridge_destination_universal_location` starts from the
|
||||
// `GlobalConsensus` and we know that the `bridge_origin_universal_location`
|
||||
// is also within the `GlobalConsensus`. So we know that the lane id will be
|
||||
// the same on both ends of the bridge
|
||||
let bridge_id = BridgeId::new(
|
||||
&bridge_origin_universal_location,
|
||||
&bridge_destination_universal_location,
|
||||
);
|
||||
|
||||
Ok(Box::new(BridgeLocations {
|
||||
bridge_origin_relative_location,
|
||||
bridge_origin_universal_location,
|
||||
bridge_destination_universal_location,
|
||||
bridge_id,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Getter for `bridge_origin_relative_location`
|
||||
pub fn bridge_origin_relative_location(&self) -> &Location {
|
||||
&self.bridge_origin_relative_location
|
||||
}
|
||||
|
||||
/// Getter for `bridge_origin_universal_location`
|
||||
pub fn bridge_origin_universal_location(&self) -> &InteriorLocation {
|
||||
&self.bridge_origin_universal_location
|
||||
}
|
||||
|
||||
/// Getter for `bridge_destination_universal_location`
|
||||
pub fn bridge_destination_universal_location(&self) -> &InteriorLocation {
|
||||
&self.bridge_destination_universal_location
|
||||
}
|
||||
|
||||
/// Getter for `bridge_id`
|
||||
pub fn bridge_id(&self) -> &BridgeId {
|
||||
&self.bridge_id
|
||||
}
|
||||
|
||||
/// Generates the exact same `LaneId` on the both bridge hubs.
|
||||
///
|
||||
/// Note: Use this **only** when opening a new bridge.
|
||||
pub fn calculate_lane_id<LaneId: LaneIdType>(
|
||||
&self,
|
||||
xcm_version: XcmVersion,
|
||||
) -> Result<LaneId, BridgeLocationsError> {
|
||||
// a tricky helper struct that adds required `Ord` support for
|
||||
// `VersionedInteriorLocation`
|
||||
#[derive(Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct EncodedVersionedInteriorLocation(pezsp_std::vec::Vec<u8>);
|
||||
impl Encode for EncodedVersionedInteriorLocation {
|
||||
fn encode(&self) -> pezsp_std::vec::Vec<u8> {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
let universal_location1 =
|
||||
VersionedInteriorLocation::from(self.bridge_origin_universal_location.clone())
|
||||
.into_version(xcm_version)
|
||||
.map_err(|_| BridgeLocationsError::UnsupportedXcmVersion);
|
||||
let universal_location2 =
|
||||
VersionedInteriorLocation::from(self.bridge_destination_universal_location.clone())
|
||||
.into_version(xcm_version)
|
||||
.map_err(|_| BridgeLocationsError::UnsupportedXcmVersion);
|
||||
|
||||
LaneId::try_new(
|
||||
EncodedVersionedInteriorLocation(universal_location1.encode()),
|
||||
EncodedVersionedInteriorLocation(universal_location2.encode()),
|
||||
)
|
||||
.map_err(|_| BridgeLocationsError::UnsupportedLaneIdType)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use xcm::latest::PEZKUWICHAIN_GENESIS_HASH;
|
||||
|
||||
const LOCAL_NETWORK: NetworkId = Kusama;
|
||||
const REMOTE_NETWORK: NetworkId = Pezkuwi;
|
||||
const UNREACHABLE_NETWORK: NetworkId = NetworkId::ByGenesis(PEZKUWICHAIN_GENESIS_HASH);
|
||||
const SIBLING_TEYRCHAIN: u32 = 1000;
|
||||
const LOCAL_BRIDGE_HUB: u32 = 1001;
|
||||
const REMOTE_TEYRCHAIN: u32 = 2000;
|
||||
|
||||
struct SuccessfulTest {
|
||||
here_universal_location: InteriorLocation,
|
||||
bridge_origin_relative_location: Location,
|
||||
|
||||
bridge_origin_universal_location: InteriorLocation,
|
||||
bridge_destination_universal_location: InteriorLocation,
|
||||
|
||||
expected_remote_network: NetworkId,
|
||||
}
|
||||
|
||||
fn run_successful_test(test: SuccessfulTest) -> BridgeLocations {
|
||||
let locations = BridgeLocations::bridge_locations(
|
||||
test.here_universal_location,
|
||||
test.bridge_origin_relative_location.clone(),
|
||||
test.bridge_destination_universal_location.clone(),
|
||||
test.expected_remote_network,
|
||||
);
|
||||
assert_eq!(
|
||||
locations,
|
||||
Ok(Box::new(BridgeLocations {
|
||||
bridge_origin_relative_location: test.bridge_origin_relative_location,
|
||||
bridge_origin_universal_location: test.bridge_origin_universal_location.clone(),
|
||||
bridge_destination_universal_location: test
|
||||
.bridge_destination_universal_location
|
||||
.clone(),
|
||||
bridge_id: BridgeId::new(
|
||||
&test.bridge_origin_universal_location,
|
||||
&test.bridge_destination_universal_location,
|
||||
),
|
||||
})),
|
||||
);
|
||||
|
||||
*locations.unwrap()
|
||||
}
|
||||
|
||||
// successful tests that with various origins and destinations
|
||||
|
||||
#[test]
|
||||
fn at_relay_from_local_relay_to_remote_relay_works() {
|
||||
run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_origin_relative_location: Here.into(),
|
||||
|
||||
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_relay_from_sibling_teyrchain_to_remote_relay_works() {
|
||||
run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_origin_relative_location: [Teyrchain(SIBLING_TEYRCHAIN)].into(),
|
||||
|
||||
bridge_origin_universal_location: [
|
||||
GlobalConsensus(LOCAL_NETWORK),
|
||||
Teyrchain(SIBLING_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_relay_from_local_relay_to_remote_teyrchain_works() {
|
||||
run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_origin_relative_location: Here.into(),
|
||||
|
||||
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_destination_universal_location: [
|
||||
GlobalConsensus(REMOTE_NETWORK),
|
||||
Teyrchain(REMOTE_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_relay_from_sibling_teyrchain_to_remote_teyrchain_works() {
|
||||
run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_origin_relative_location: [Teyrchain(SIBLING_TEYRCHAIN)].into(),
|
||||
|
||||
bridge_origin_universal_location: [
|
||||
GlobalConsensus(LOCAL_NETWORK),
|
||||
Teyrchain(SIBLING_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
bridge_destination_universal_location: [
|
||||
GlobalConsensus(REMOTE_NETWORK),
|
||||
Teyrchain(REMOTE_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_bridge_hub_from_local_relay_to_remote_relay_works() {
|
||||
run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Teyrchain(LOCAL_BRIDGE_HUB)]
|
||||
.into(),
|
||||
bridge_origin_relative_location: Parent.into(),
|
||||
|
||||
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_bridge_hub_from_sibling_teyrchain_to_remote_relay_works() {
|
||||
run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Teyrchain(LOCAL_BRIDGE_HUB)]
|
||||
.into(),
|
||||
bridge_origin_relative_location: ParentThen([Teyrchain(SIBLING_TEYRCHAIN)].into())
|
||||
.into(),
|
||||
|
||||
bridge_origin_universal_location: [
|
||||
GlobalConsensus(LOCAL_NETWORK),
|
||||
Teyrchain(SIBLING_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_bridge_hub_from_local_relay_to_remote_teyrchain_works() {
|
||||
run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Teyrchain(LOCAL_BRIDGE_HUB)]
|
||||
.into(),
|
||||
bridge_origin_relative_location: Parent.into(),
|
||||
|
||||
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_destination_universal_location: [
|
||||
GlobalConsensus(REMOTE_NETWORK),
|
||||
Teyrchain(REMOTE_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_bridge_hub_from_sibling_teyrchain_to_remote_teyrchain_works() {
|
||||
run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Teyrchain(LOCAL_BRIDGE_HUB)]
|
||||
.into(),
|
||||
bridge_origin_relative_location: ParentThen([Teyrchain(SIBLING_TEYRCHAIN)].into())
|
||||
.into(),
|
||||
|
||||
bridge_origin_universal_location: [
|
||||
GlobalConsensus(LOCAL_NETWORK),
|
||||
Teyrchain(SIBLING_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
bridge_destination_universal_location: [
|
||||
GlobalConsensus(REMOTE_NETWORK),
|
||||
Teyrchain(REMOTE_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
}
|
||||
|
||||
// successful tests that show that we are ignoring low-level junctions of bridge origins
|
||||
|
||||
#[test]
|
||||
fn low_level_junctions_at_bridge_origin_are_stripped() {
|
||||
let locations1 = run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_origin_relative_location: Here.into(),
|
||||
|
||||
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
let locations2 = run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_origin_relative_location: [PalletInstance(0)].into(),
|
||||
|
||||
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
|
||||
assert_eq!(locations1.bridge_id, locations2.bridge_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_level_junctions_at_bridge_destination_are_stripped() {
|
||||
let locations1 = run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_origin_relative_location: Here.into(),
|
||||
|
||||
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
let locations2 = run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_origin_relative_location: Here.into(),
|
||||
|
||||
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
|
||||
assert_eq!(locations1.bridge_id, locations2.bridge_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculate_lane_id_works() {
|
||||
type TestLaneId = bp_messages::HashedLaneId;
|
||||
|
||||
let from_local_to_remote = run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Teyrchain(LOCAL_BRIDGE_HUB)]
|
||||
.into(),
|
||||
bridge_origin_relative_location: ParentThen([Teyrchain(SIBLING_TEYRCHAIN)].into())
|
||||
.into(),
|
||||
|
||||
bridge_origin_universal_location: [
|
||||
GlobalConsensus(LOCAL_NETWORK),
|
||||
Teyrchain(SIBLING_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
bridge_destination_universal_location: [
|
||||
GlobalConsensus(REMOTE_NETWORK),
|
||||
Teyrchain(REMOTE_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
|
||||
expected_remote_network: REMOTE_NETWORK,
|
||||
});
|
||||
|
||||
let from_remote_to_local = run_successful_test(SuccessfulTest {
|
||||
here_universal_location: [GlobalConsensus(REMOTE_NETWORK), Teyrchain(LOCAL_BRIDGE_HUB)]
|
||||
.into(),
|
||||
bridge_origin_relative_location: ParentThen([Teyrchain(REMOTE_TEYRCHAIN)].into())
|
||||
.into(),
|
||||
|
||||
bridge_origin_universal_location: [
|
||||
GlobalConsensus(REMOTE_NETWORK),
|
||||
Teyrchain(REMOTE_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
bridge_destination_universal_location: [
|
||||
GlobalConsensus(LOCAL_NETWORK),
|
||||
Teyrchain(SIBLING_TEYRCHAIN),
|
||||
]
|
||||
.into(),
|
||||
|
||||
expected_remote_network: LOCAL_NETWORK,
|
||||
});
|
||||
|
||||
assert_ne!(
|
||||
from_local_to_remote.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION),
|
||||
from_remote_to_local.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION - 1),
|
||||
);
|
||||
assert_eq!(
|
||||
from_local_to_remote.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION),
|
||||
from_remote_to_local.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION),
|
||||
);
|
||||
}
|
||||
|
||||
// negative tests
|
||||
|
||||
#[test]
|
||||
fn bridge_locations_fails_when_here_is_not_universal_location() {
|
||||
assert_eq!(
|
||||
BridgeLocations::bridge_locations(
|
||||
[Teyrchain(1000)].into(),
|
||||
Here.into(),
|
||||
[GlobalConsensus(REMOTE_NETWORK)].into(),
|
||||
REMOTE_NETWORK,
|
||||
),
|
||||
Err(BridgeLocationsError::NonUniversalLocation),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_locations_fails_when_computed_destination_is_not_universal_location() {
|
||||
assert_eq!(
|
||||
BridgeLocations::bridge_locations(
|
||||
[GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
Here.into(),
|
||||
[OnlyChild].into(),
|
||||
REMOTE_NETWORK,
|
||||
),
|
||||
Err(BridgeLocationsError::NonUniversalLocation),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_locations_fails_when_computed_destination_is_local() {
|
||||
assert_eq!(
|
||||
BridgeLocations::bridge_locations(
|
||||
[GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
Here.into(),
|
||||
[GlobalConsensus(LOCAL_NETWORK), OnlyChild].into(),
|
||||
REMOTE_NETWORK,
|
||||
),
|
||||
Err(BridgeLocationsError::DestinationIsLocal),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridge_locations_fails_when_computed_destination_is_unreachable() {
|
||||
assert_eq!(
|
||||
BridgeLocations::bridge_locations(
|
||||
[GlobalConsensus(LOCAL_NETWORK)].into(),
|
||||
Here.into(),
|
||||
[GlobalConsensus(UNREACHABLE_NETWORK)].into(),
|
||||
REMOTE_NETWORK,
|
||||
),
|
||||
Err(BridgeLocationsError::UnreachableDestination),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user