mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 09:21:04 +00:00
Finality Verifier Pallet (#629)
* Add skeleton for `pallet-finality-verifier` * Sketch out implementation for importing finality proofs * Get pallet compiling * Introduce skeleton for mock runtime * Start using real Grandpa types in finality pallet * Redefine types in header chain primitives crate * Implement HeaderChain for Substrate bridge pallet * Plug Substrate Bridge Pallet into verifier mock * Fix compilation of `header-chain` primitives * Start writing to base pallet storage * Add first "cross-pallet" test * Move keyring primitives used in tests to shared crate * Stop pulling `std` deps into `no_std` builds * Revert "Stop pulling `std` deps into `no_std` builds" This reverts commit f74dd660652f98b7336936d1534a4e63cc9169a5. * Revert "Move keyring primitives used in tests to shared crate" This reverts commit b774fa730b2cdc40545afff308a66b0840266001. * Use new SS58Prefix type in mock * Start using `bp-test-utils` in finality pallet * Start using real justification code * Get a test working with real justification verification * Add basic tests for invalid proofs * Get rid of AncestryProof config type * Add error types to transaction outcome * Bound number of headers allowed in a single ancestry proof * Disallow invalid authority sets * Remove unused items * Add some documentation * Get rid of Clippy warnings * Rename BaseHeaderChain to TransactionVerifier * Remove unused code * Make dummy trait implementations more generic * Fix more Clippy complaints * Update tests to use fix for duplicate headers * Fix benchmarking compilation * Rename TransactionVerifier to InclusionProofVerifier
This commit is contained in:
committed by
Bastian Köcher
parent
5e38b126f2
commit
ea5d8662be
@@ -17,7 +17,7 @@
|
||||
use crate::exchange::EthereumTransactionInclusionProof;
|
||||
|
||||
use bp_eth_poa::{Address, AuraHeader, RawTransaction, U256};
|
||||
use bp_header_chain::BaseHeaderChain;
|
||||
use bp_header_chain::InclusionProofVerifier;
|
||||
use frame_support::RuntimeDebug;
|
||||
use hex_literal::hex;
|
||||
use pallet_bridge_eth_poa::{
|
||||
@@ -149,7 +149,7 @@ impl TChainTime for ChainTime {
|
||||
/// The Kovan Blockchain as seen by the runtime.
|
||||
pub struct KovanBlockchain;
|
||||
|
||||
impl BaseHeaderChain for KovanBlockchain {
|
||||
impl InclusionProofVerifier for KovanBlockchain {
|
||||
type Transaction = RawTransaction;
|
||||
type TransactionInclusionProof = EthereumTransactionInclusionProof;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
use crate::exchange::EthereumTransactionInclusionProof;
|
||||
|
||||
use bp_eth_poa::{Address, AuraHeader, RawTransaction, U256};
|
||||
use bp_header_chain::BaseHeaderChain;
|
||||
use bp_header_chain::InclusionProofVerifier;
|
||||
use frame_support::RuntimeDebug;
|
||||
use hex_literal::hex;
|
||||
use pallet_bridge_eth_poa::{
|
||||
@@ -124,7 +124,7 @@ impl TChainTime for ChainTime {
|
||||
/// The Rialto PoA Blockchain as seen by the runtime.
|
||||
pub struct RialtoBlockchain;
|
||||
|
||||
impl BaseHeaderChain for RialtoBlockchain {
|
||||
impl InclusionProofVerifier for RialtoBlockchain {
|
||||
type Transaction = RawTransaction;
|
||||
type TransactionInclusionProof = EthereumTransactionInclusionProof;
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
//! So we are giving runtime opportunity to prepare environment and construct proof
|
||||
//! before invoking module calls.
|
||||
|
||||
use super::{BaseHeaderChain, Call, Config as CurrencyExchangeConfig, Instance, Module as CurrencyExchangeModule};
|
||||
use super::{
|
||||
Call, Config as CurrencyExchangeConfig, InclusionProofVerifier, Instance, Module as CurrencyExchangeModule,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
use frame_benchmarking::{account, benchmarks_instance};
|
||||
@@ -50,7 +52,7 @@ pub trait Config<I: Instance>: CurrencyExchangeConfig<I> {
|
||||
/// Prepare proof for importing exchange transaction.
|
||||
fn make_proof(
|
||||
proof_params: ProofParams<Self::AccountId>,
|
||||
) -> <<Self as CurrencyExchangeConfig<I>>::PeerBlockchain as BaseHeaderChain>::TransactionInclusionProof;
|
||||
) -> <<Self as CurrencyExchangeConfig<I>>::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof;
|
||||
}
|
||||
|
||||
benchmarks_instance! {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
use bp_currency_exchange::{
|
||||
CurrencyConverter, DepositInto, Error as ExchangeError, MaybeLockFundsTransaction, RecipientsMap,
|
||||
};
|
||||
use bp_header_chain::BaseHeaderChain;
|
||||
use bp_header_chain::InclusionProofVerifier;
|
||||
use frame_support::{decl_error, decl_module, decl_storage, ensure};
|
||||
use sp_runtime::DispatchResult;
|
||||
|
||||
@@ -39,10 +39,10 @@ pub trait Config<I = DefaultInstance>: frame_system::Config {
|
||||
/// Handler for transaction submission result.
|
||||
type OnTransactionSubmitted: OnTransactionSubmitted<Self::AccountId>;
|
||||
/// Represents the blockchain that we'll be exchanging currency with.
|
||||
type PeerBlockchain: BaseHeaderChain;
|
||||
type PeerBlockchain: InclusionProofVerifier;
|
||||
/// Peer blockchain transaction parser.
|
||||
type PeerMaybeLockFundsTransaction: MaybeLockFundsTransaction<
|
||||
Transaction = <Self::PeerBlockchain as BaseHeaderChain>::Transaction,
|
||||
Transaction = <Self::PeerBlockchain as InclusionProofVerifier>::Transaction,
|
||||
>;
|
||||
/// Map between blockchains recipients.
|
||||
type RecipientsMap: RecipientsMap<
|
||||
@@ -89,7 +89,7 @@ decl_module! {
|
||||
#[weight = 0] // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78)
|
||||
pub fn import_peer_transaction(
|
||||
origin,
|
||||
proof: <<T as Config<I>>::PeerBlockchain as BaseHeaderChain>::TransactionInclusionProof,
|
||||
proof: <<T as Config<I>>::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof,
|
||||
) -> DispatchResult {
|
||||
let submitter = frame_system::ensure_signed(origin)?;
|
||||
|
||||
@@ -134,7 +134,9 @@ decl_storage! {
|
||||
impl<T: Config<I>, I: Instance> Module<T, I> {
|
||||
/// Returns true if currency exchange module is able to import given transaction proof in
|
||||
/// its current state.
|
||||
pub fn filter_transaction_proof(proof: &<T::PeerBlockchain as BaseHeaderChain>::TransactionInclusionProof) -> bool {
|
||||
pub fn filter_transaction_proof(
|
||||
proof: &<T::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof,
|
||||
) -> bool {
|
||||
if let Err(err) = prepare_deposit_details::<T, I>(proof) {
|
||||
frame_support::debug::trace!(
|
||||
target: "runtime",
|
||||
@@ -180,7 +182,7 @@ struct DepositDetails<T: Config<I>, I: Instance> {
|
||||
/// Verify and parse transaction proof, preparing everything required for importing
|
||||
/// this transaction proof.
|
||||
fn prepare_deposit_details<T: Config<I>, I: Instance>(
|
||||
proof: &<<T as Config<I>>::PeerBlockchain as BaseHeaderChain>::TransactionInclusionProof,
|
||||
proof: &<<T as Config<I>>::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof,
|
||||
) -> Result<DepositDetails<T, I>, Error<T, I>> {
|
||||
// ensure that transaction is included in finalized block that we know of
|
||||
let transaction = <T as Config<I>>::PeerBlockchain::verify_transaction_inclusion_proof(proof)
|
||||
@@ -239,7 +241,7 @@ mod tests {
|
||||
|
||||
pub struct DummyBlockchain;
|
||||
|
||||
impl BaseHeaderChain for DummyBlockchain {
|
||||
impl InclusionProofVerifier for DummyBlockchain {
|
||||
type Transaction = RawTransaction;
|
||||
type TransactionInclusionProof = (bool, RawTransaction);
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "pallet-finality-verifier"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false }
|
||||
finality-grandpa = { version = "0.12.3", default-features = false }
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
|
||||
bp-runtime = { path = "../../primitives/runtime", default-features = false }
|
||||
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-test-utils = {path = "../../primitives/test-utils" }
|
||||
pallet-substrate-bridge = { path = "../../modules/substrate" }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-runtime/std",
|
||||
"bp-header-chain/std",
|
||||
"codec/std",
|
||||
"finality-grandpa/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"serde",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
@@ -0,0 +1,300 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate Finality Verifier Pallet
|
||||
//!
|
||||
//! The goal of this pallet is to provide a safe interface for writing finalized headers to an
|
||||
//! external pallet which tracks headers and finality proofs. By safe, we mean that only headers
|
||||
//! whose finality has been verified will be written to the underlying pallet.
|
||||
//!
|
||||
//! By verifying the finality of headers before writing them to storage we prevent DoS vectors in
|
||||
//! which unfinalized headers get written to storage even if they don't have a chance of being
|
||||
//! finalized in the future (such as in the case where a different fork gets finalized).
|
||||
//!
|
||||
//! The underlying pallet used for storage is assumed to be a pallet which tracks headers and
|
||||
//! GRANDPA authority set changes. This information is used during the verification of GRANDPA
|
||||
//! finality proofs.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
// Runtime-generated enums
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
use bp_header_chain::{justification::verify_justification, AncestryChecker, HeaderChain};
|
||||
use bp_runtime::{Chain, HeaderOf};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use frame_support::{decl_error, decl_module, decl_storage, dispatch::DispatchResult, ensure, traits::Get};
|
||||
use frame_system::ensure_signed;
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
/// Header of the bridged chain.
|
||||
pub(crate) type BridgedHeader<T> = HeaderOf<<T as Config>::BridgedChain>;
|
||||
|
||||
/// The module configuration trait.
|
||||
pub trait Config: frame_system::Config {
|
||||
/// The chain we are bridging to here.
|
||||
type BridgedChain: Chain;
|
||||
/// The pallet which we will use as our underlying storage mechanism.
|
||||
type HeaderChain: HeaderChain<<Self::BridgedChain as Chain>::Header>;
|
||||
/// The type through which we will verify that a given header is related to the last
|
||||
/// finalized header in our storage pallet.
|
||||
type AncestryChecker: AncestryChecker<
|
||||
<Self::BridgedChain as Chain>::Header,
|
||||
Vec<<Self::BridgedChain as Chain>::Header>,
|
||||
>;
|
||||
/// The maximum length of headers we can have in a single ancestry proof. This prevents
|
||||
/// unbounded iteration when verifying proofs.
|
||||
type MaxHeadersInSingleProof: Get<u8>;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Config> as FinalityVerifier {}
|
||||
}
|
||||
|
||||
decl_error! {
|
||||
pub enum Error for Module<T: Config> {
|
||||
/// The given justification is invalid for the given header.
|
||||
InvalidJustification,
|
||||
/// The given ancestry proof is unable to verify that the child and ancestor headers are
|
||||
/// related.
|
||||
InvalidAncestryProof,
|
||||
/// The authority set from the underlying header chain is invalid.
|
||||
InvalidAuthoritySet,
|
||||
/// Failed to write a header to the underlying header chain.
|
||||
FailedToWriteHeader,
|
||||
/// Failed to write finality proof to the underlying header chain.
|
||||
FailedToWriteFinalityProof,
|
||||
/// The given ancestry proof is too large to be verified in a single transaction.
|
||||
OversizedAncestryProof,
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Config> for enum Call where origin: T::Origin {
|
||||
type Error = Error<T>;
|
||||
|
||||
/// Verify a header is finalized according to the given finality proof.
|
||||
///
|
||||
/// Will use the underlying storage pallet to fetch information about the current
|
||||
/// authorities and best finalized header in order to verify that the header is finalized.
|
||||
///
|
||||
/// If successful in verification, it will write the headers to the underlying storage
|
||||
/// pallet as well as import the valid finality proof.
|
||||
#[weight = 0]
|
||||
pub fn submit_finality_proof(
|
||||
origin,
|
||||
finality_target: BridgedHeader<T>,
|
||||
justification: Vec<u8>,
|
||||
ancestry_proof: Vec<BridgedHeader<T>>,
|
||||
) -> DispatchResult {
|
||||
let _ = ensure_signed(origin)?;
|
||||
|
||||
ensure!(
|
||||
ancestry_proof.len() <= T::MaxHeadersInSingleProof::get() as usize,
|
||||
<Error<T>>::OversizedAncestryProof
|
||||
);
|
||||
|
||||
let authority_set = T::HeaderChain::authority_set();
|
||||
let voter_set =
|
||||
VoterSet::new(authority_set.authorities).ok_or(<Error<T>>::InvalidAuthoritySet)?;
|
||||
let set_id = authority_set.set_id;
|
||||
|
||||
verify_justification::<BridgedHeader<T>>(
|
||||
(finality_target.hash(), *finality_target.number()),
|
||||
set_id,
|
||||
voter_set,
|
||||
&justification
|
||||
)
|
||||
.map_err(|_| <Error<T>>::InvalidJustification)?;
|
||||
|
||||
let best_finalized = T::HeaderChain::best_finalized();
|
||||
ensure!(
|
||||
T::AncestryChecker::are_ancestors(&best_finalized, &finality_target, &ancestry_proof),
|
||||
<Error<T>>::InvalidAncestryProof
|
||||
);
|
||||
|
||||
// If for whatever reason we are unable to fully import headers and the corresponding
|
||||
// finality proof we want to avoid writing to the base pallet storage
|
||||
use frame_support::storage::{with_transaction, TransactionOutcome};
|
||||
with_transaction(|| {
|
||||
for header in ancestry_proof {
|
||||
if T::HeaderChain::import_header(header).is_err() {
|
||||
return TransactionOutcome::Rollback(Err(<Error<T>>::FailedToWriteHeader))
|
||||
}
|
||||
}
|
||||
|
||||
if T::HeaderChain::import_finality_proof(finality_target, justification).is_err() {
|
||||
return TransactionOutcome::Rollback(Err(<Error<T>>::FailedToWriteFinalityProof))
|
||||
}
|
||||
|
||||
TransactionOutcome::Commit(Ok(()))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{run_test, test_header, Origin, TestRuntime};
|
||||
use bp_test_utils::{authority_list, make_justification_for_header};
|
||||
use codec::Encode;
|
||||
use frame_support::{assert_err, assert_ok};
|
||||
|
||||
fn initialize_substrate_bridge() {
|
||||
let genesis = test_header(0);
|
||||
|
||||
let init_data = pallet_substrate_bridge::InitializationData {
|
||||
header: genesis,
|
||||
authority_list: authority_list(),
|
||||
set_id: 1,
|
||||
scheduled_change: None,
|
||||
is_halted: false,
|
||||
};
|
||||
|
||||
assert_ok!(pallet_substrate_bridge::Module::<TestRuntime>::initialize(
|
||||
Origin::root(),
|
||||
init_data
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn succesfully_imports_header_with_valid_finality_and_ancestry_proofs() {
|
||||
run_test(|| {
|
||||
initialize_substrate_bridge();
|
||||
|
||||
let child = test_header(1);
|
||||
let header = test_header(2);
|
||||
|
||||
let set_id = 1;
|
||||
let grandpa_round = 1;
|
||||
let justification =
|
||||
make_justification_for_header(&header, grandpa_round, set_id, &authority_list()).encode();
|
||||
let ancestry_proof = vec![child, header.clone()];
|
||||
|
||||
assert_ok!(Module::<TestRuntime>::submit_finality_proof(
|
||||
Origin::signed(1),
|
||||
header.clone(),
|
||||
justification,
|
||||
ancestry_proof,
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
pallet_substrate_bridge::Module::<TestRuntime>::best_headers(),
|
||||
vec![(*header.number(), header.hash())]
|
||||
);
|
||||
|
||||
assert_eq!(pallet_substrate_bridge::Module::<TestRuntime>::best_finalized(), header);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_import_header_with_invalid_finality_proof() {
|
||||
run_test(|| {
|
||||
initialize_substrate_bridge();
|
||||
|
||||
let child = test_header(1);
|
||||
let header = test_header(2);
|
||||
|
||||
let justification = [1u8; 32].encode();
|
||||
let ancestry_proof = vec![child, header.clone()];
|
||||
|
||||
assert_err!(
|
||||
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,),
|
||||
<Error<TestRuntime>>::InvalidJustification
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_import_header_with_invalid_ancestry_proof() {
|
||||
run_test(|| {
|
||||
initialize_substrate_bridge();
|
||||
|
||||
let header = test_header(2);
|
||||
|
||||
let set_id = 1;
|
||||
let grandpa_round = 1;
|
||||
let justification =
|
||||
make_justification_for_header(&header, grandpa_round, set_id, &authority_list()).encode();
|
||||
|
||||
// For testing, we've made it so that an empty ancestry proof is invalid
|
||||
let ancestry_proof = vec![];
|
||||
|
||||
assert_err!(
|
||||
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,),
|
||||
<Error<TestRuntime>>::InvalidAncestryProof
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallows_ancestry_proofs_which_are_too_large() {
|
||||
run_test(|| {
|
||||
initialize_substrate_bridge();
|
||||
|
||||
let header = test_header(1);
|
||||
let justification = [1u8; 32].encode();
|
||||
|
||||
let mut ancestry_proof = vec![];
|
||||
let max_len = <TestRuntime as Config>::MaxHeadersInSingleProof::get();
|
||||
for i in 1..=max_len + 1 {
|
||||
ancestry_proof.push(test_header(i as u64));
|
||||
}
|
||||
|
||||
assert_err!(
|
||||
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,),
|
||||
<Error<TestRuntime>>::OversizedAncestryProof
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallows_invalid_authority_set() {
|
||||
run_test(|| {
|
||||
use bp_test_utils::{alice, bob};
|
||||
|
||||
let genesis = test_header(0);
|
||||
|
||||
let invalid_authority_list = vec![(alice(), u64::MAX), (bob(), u64::MAX)];
|
||||
let init_data = pallet_substrate_bridge::InitializationData {
|
||||
header: genesis,
|
||||
authority_list: invalid_authority_list,
|
||||
set_id: 1,
|
||||
scheduled_change: None,
|
||||
is_halted: false,
|
||||
};
|
||||
|
||||
assert_ok!(pallet_substrate_bridge::Module::<TestRuntime>::initialize(
|
||||
Origin::root(),
|
||||
init_data
|
||||
));
|
||||
|
||||
let header = test_header(1);
|
||||
let justification = [1u8; 32].encode();
|
||||
let ancestry_proof = vec![];
|
||||
|
||||
assert_err!(
|
||||
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,),
|
||||
<Error<TestRuntime>>::InvalidAuthoritySet
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{BridgedHeader, Config};
|
||||
use bp_runtime::{BlockNumberOf, Chain};
|
||||
use frame_support::{impl_outer_origin, parameter_types, weights::Weight};
|
||||
use sp_runtime::{
|
||||
testing::{Header, H256},
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
Perbill,
|
||||
};
|
||||
|
||||
pub type AccountId = u64;
|
||||
pub type TestHeader = BridgedHeader<TestRuntime>;
|
||||
pub type TestNumber = BlockNumberOf<<TestRuntime as Config>::BridgedChain>;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub struct TestRuntime;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for TestRuntime where system = frame_system {}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
|
||||
impl frame_system::Config for TestRuntime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type Call = ();
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type BaseCallFilter = ();
|
||||
type SystemWeightInfo = ();
|
||||
type DbWeight = ();
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type SS58Prefix = ();
|
||||
}
|
||||
|
||||
impl pallet_substrate_bridge::Config for TestRuntime {
|
||||
type BridgedChain = TestBridgedChain;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxHeadersInSingleProof: u8 = 5;
|
||||
}
|
||||
|
||||
impl crate::Config for TestRuntime {
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type HeaderChain = pallet_substrate_bridge::Module<TestRuntime>;
|
||||
type AncestryChecker = Checker<<Self::BridgedChain as Chain>::Header, Vec<<Self::BridgedChain as Chain>::Header>>;
|
||||
type MaxHeadersInSingleProof = MaxHeadersInSingleProof;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBridgedChain;
|
||||
|
||||
impl Chain for TestBridgedChain {
|
||||
type BlockNumber = <TestRuntime as frame_system::Config>::BlockNumber;
|
||||
type Hash = <TestRuntime as frame_system::Config>::Hash;
|
||||
type Hasher = <TestRuntime as frame_system::Config>::Hashing;
|
||||
type Header = <TestRuntime as frame_system::Config>::Header;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Checker<H, P>(std::marker::PhantomData<(H, P)>);
|
||||
|
||||
impl<H> crate::AncestryChecker<H, Vec<H>> for Checker<H, Vec<H>> {
|
||||
fn are_ancestors(_ancestor: &H, _child: &H, proof: &Vec<H>) -> bool {
|
||||
!proof.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(test)
|
||||
}
|
||||
|
||||
pub fn test_header(num: TestNumber) -> TestHeader {
|
||||
// We wrap the call to avoid explicit type annotations in our tests
|
||||
bp_test_utils::test_header(num)
|
||||
}
|
||||
@@ -38,6 +38,7 @@ default = ["std"]
|
||||
std = [
|
||||
"bp-header-chain/std",
|
||||
"bp-runtime/std",
|
||||
"bp-header-chain/std",
|
||||
"codec/std",
|
||||
"finality-grandpa/std",
|
||||
"frame-support/std",
|
||||
|
||||
@@ -55,9 +55,10 @@
|
||||
//! because the header is an old header.
|
||||
|
||||
use crate::mock::*;
|
||||
use crate::storage::{AuthoritySet, ImportedHeader};
|
||||
use crate::storage::ImportedHeader;
|
||||
use crate::verifier::*;
|
||||
use crate::{BestFinalized, BestHeight, BridgeStorage, NextScheduledChange, PalletStorage};
|
||||
use bp_header_chain::AuthoritySet;
|
||||
use bp_test_utils::{alice, authority_list, bob, make_justification_for_header};
|
||||
use codec::Encode;
|
||||
use frame_support::{IterableStorageMap, StorageValue};
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
use crate::storage::ImportedHeader;
|
||||
use bp_header_chain::AuthoritySet;
|
||||
use bp_runtime::{BlockNumberOf, Chain, HashOf, HasherOf, HeaderOf};
|
||||
use frame_support::{
|
||||
decl_error, decl_module, decl_storage, dispatch::DispatchResult, ensure, traits::Get, weights::DispatchClass,
|
||||
@@ -43,7 +44,7 @@ use sp_std::{marker::PhantomData, prelude::*};
|
||||
use sp_trie::StorageProof;
|
||||
|
||||
// Re-export since the node uses these when configuring genesis
|
||||
pub use storage::{AuthoritySet, InitializationData, ScheduledChange};
|
||||
pub use storage::{InitializationData, ScheduledChange};
|
||||
|
||||
pub use storage_proof::StorageProofChecker;
|
||||
|
||||
@@ -361,6 +362,38 @@ impl<T: Config> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> bp_header_chain::HeaderChain<BridgedHeader<T>> for Module<T> {
|
||||
fn best_finalized() -> BridgedHeader<T> {
|
||||
PalletStorage::<T>::new().best_finalized_header().header
|
||||
}
|
||||
|
||||
fn authority_set() -> AuthoritySet {
|
||||
PalletStorage::<T>::new().current_authority_set()
|
||||
}
|
||||
|
||||
fn import_header(header: BridgedHeader<T>) -> Result<(), ()> {
|
||||
let mut verifier = verifier::Verifier {
|
||||
storage: PalletStorage::<T>::new(),
|
||||
};
|
||||
|
||||
let _ = verifier.import_header(header.hash(), header).map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_finality_proof(header: BridgedHeader<T>, finality_proof: Vec<u8>) -> Result<(), ()> {
|
||||
let mut verifier = verifier::Verifier {
|
||||
storage: PalletStorage::<T>::new(),
|
||||
};
|
||||
|
||||
let _ = verifier
|
||||
.import_finality_proof(header.hash(), finality_proof.into())
|
||||
.map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that the origin is either root, or `ModuleOwner`.
|
||||
fn ensure_owner_or_root<T: Config>(origin: T::Origin) -> Result<(), BadOrigin> {
|
||||
match origin.into() {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
//! Storage primitives for the Substrate light client (a.k.a bridge) pallet.
|
||||
|
||||
use bp_header_chain::AuthoritySet;
|
||||
use codec::{Decode, Encode};
|
||||
use core::default::Default;
|
||||
#[cfg(feature = "std")]
|
||||
@@ -42,23 +43,6 @@ pub struct InitializationData<H: HeaderT> {
|
||||
pub is_halted: bool,
|
||||
}
|
||||
|
||||
/// A GRANDPA Authority List and ID.
|
||||
#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone)]
|
||||
#[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 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps track of when the next GRANDPA authority set change will occur.
|
||||
#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
//! has been signed off by the correct GRANDPA authorities, and also enact any authority set changes
|
||||
//! if required.
|
||||
|
||||
use crate::storage::{AuthoritySet, ImportedHeader, ScheduledChange};
|
||||
use crate::storage::{ImportedHeader, ScheduledChange};
|
||||
use crate::BridgeStorage;
|
||||
|
||||
use bp_header_chain::justification::verify_justification;
|
||||
use bp_header_chain::{justification::verify_justification, AuthoritySet};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
|
||||
use sp_runtime::generic::OpaqueDigestItemId;
|
||||
|
||||
@@ -9,6 +9,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false }
|
||||
finality-grandpa = { version = "0.12.3", default-features = false }
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
@@ -20,15 +21,16 @@ sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master
|
||||
|
||||
[dev-dependencies]
|
||||
bp-test-utils = { path = "../test-utils" }
|
||||
sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"finality-grandpa/std",
|
||||
"serde/std",
|
||||
"frame-support/std",
|
||||
"sp-core/std",
|
||||
"sp-finality-grandpa/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
@@ -19,10 +19,16 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Codec, EncodeLike};
|
||||
use codec::{Codec, Decode, Encode, EncodeLike};
|
||||
use core::clone::Clone;
|
||||
use core::cmp::Eq;
|
||||
use core::default::Default;
|
||||
use core::fmt::Debug;
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_finality_grandpa::{AuthorityList, SetId};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::prelude::Vec;
|
||||
|
||||
pub mod justification;
|
||||
|
||||
@@ -32,8 +38,25 @@ pub mod justification;
|
||||
pub trait Parameter: Codec + EncodeLike + Clone + Eq + Debug {}
|
||||
impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + Debug {}
|
||||
|
||||
/// A base trait for pallets which want to keep track of a full set of headers from a bridged chain.
|
||||
pub trait BaseHeaderChain {
|
||||
/// A GRANDPA Authority List and ID.
|
||||
#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone)]
|
||||
#[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 }
|
||||
}
|
||||
}
|
||||
|
||||
/// base trait for verifying transaction inclusion proofs.
|
||||
pub trait InclusionProofVerifier {
|
||||
/// Transaction type.
|
||||
type Transaction: Parameter;
|
||||
/// Transaction inclusion proof type.
|
||||
@@ -44,3 +67,54 @@ pub trait BaseHeaderChain {
|
||||
/// Returns Some(transaction) if proof is valid and None otherwise.
|
||||
fn verify_transaction_inclusion_proof(proof: &Self::TransactionInclusionProof) -> Option<Self::Transaction>;
|
||||
}
|
||||
|
||||
/// A base trait for pallets which want to keep track of a full set of headers from a bridged chain.
|
||||
pub trait HeaderChain<H> {
|
||||
/// Get the best finalized header known to the header chain.
|
||||
fn best_finalized() -> H;
|
||||
|
||||
/// Get the best authority set known to the header chain.
|
||||
fn authority_set() -> AuthoritySet;
|
||||
|
||||
/// Write the given header to the underlying pallet storage.
|
||||
#[allow(clippy::result_unit_err)]
|
||||
fn import_header(header: H) -> Result<(), ()>;
|
||||
|
||||
/// Submit a valid finality proof for the given header to the underlying pallet storage.
|
||||
///
|
||||
/// This will finalize the given header and enact any authority set changes if required.
|
||||
#[allow(clippy::result_unit_err)]
|
||||
fn import_finality_proof(header: H, finality_proof: Vec<u8>) -> Result<(), ()>;
|
||||
}
|
||||
|
||||
impl<H: Default> HeaderChain<H> for () {
|
||||
fn best_finalized() -> H {
|
||||
H::default()
|
||||
}
|
||||
|
||||
fn authority_set() -> AuthoritySet {
|
||||
AuthoritySet::default()
|
||||
}
|
||||
|
||||
#[allow(clippy::result_unit_err)]
|
||||
fn import_header(_header: H) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::result_unit_err)]
|
||||
fn import_finality_proof(_header: H, _finality_proof: Vec<u8>) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for checking if a given child header is a direct decendant of an ancestor.
|
||||
pub trait AncestryChecker<H, P> {
|
||||
/// Is the child header a decendant of the ancestor header?
|
||||
fn are_ancestors(ancestor: &H, child: &H, proof: &P) -> bool;
|
||||
}
|
||||
|
||||
impl<H, P> AncestryChecker<H, P> for () {
|
||||
fn are_ancestors(_ancestor: &H, _child: &H, _proof: &P) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user