diff --git a/bridges/bin/rialto/runtime/src/kovan.rs b/bridges/bin/rialto/runtime/src/kovan.rs index bc2252bbb9..fa76347db2 100644 --- a/bridges/bin/rialto/runtime/src/kovan.rs +++ b/bridges/bin/rialto/runtime/src/kovan.rs @@ -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; diff --git a/bridges/bin/rialto/runtime/src/rialto_poa.rs b/bridges/bin/rialto/runtime/src/rialto_poa.rs index 324179b55d..54ac8e2571 100644 --- a/bridges/bin/rialto/runtime/src/rialto_poa.rs +++ b/bridges/bin/rialto/runtime/src/rialto_poa.rs @@ -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; diff --git a/bridges/modules/currency-exchange/src/benchmarking.rs b/bridges/modules/currency-exchange/src/benchmarking.rs index 8b38d42eaa..d10dd3c684 100644 --- a/bridges/modules/currency-exchange/src/benchmarking.rs +++ b/bridges/modules/currency-exchange/src/benchmarking.rs @@ -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: CurrencyExchangeConfig { /// Prepare proof for importing exchange transaction. fn make_proof( proof_params: ProofParams, - ) -> <>::PeerBlockchain as BaseHeaderChain>::TransactionInclusionProof; + ) -> <>::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof; } benchmarks_instance! { diff --git a/bridges/modules/currency-exchange/src/lib.rs b/bridges/modules/currency-exchange/src/lib.rs index d3e12bff39..463b052cac 100644 --- a/bridges/modules/currency-exchange/src/lib.rs +++ b/bridges/modules/currency-exchange/src/lib.rs @@ -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: frame_system::Config { /// Handler for transaction submission result. type OnTransactionSubmitted: OnTransactionSubmitted; /// Represents the blockchain that we'll be exchanging currency with. - type PeerBlockchain: BaseHeaderChain; + type PeerBlockchain: InclusionProofVerifier; /// Peer blockchain transaction parser. type PeerMaybeLockFundsTransaction: MaybeLockFundsTransaction< - Transaction = ::Transaction, + Transaction = ::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: <>::PeerBlockchain as BaseHeaderChain>::TransactionInclusionProof, + proof: <>::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof, ) -> DispatchResult { let submitter = frame_system::ensure_signed(origin)?; @@ -134,7 +134,9 @@ decl_storage! { impl, I: Instance> Module { /// Returns true if currency exchange module is able to import given transaction proof in /// its current state. - pub fn filter_transaction_proof(proof: &::TransactionInclusionProof) -> bool { + pub fn filter_transaction_proof( + proof: &::TransactionInclusionProof, + ) -> bool { if let Err(err) = prepare_deposit_details::(proof) { frame_support::debug::trace!( target: "runtime", @@ -180,7 +182,7 @@ struct DepositDetails, I: Instance> { /// Verify and parse transaction proof, preparing everything required for importing /// this transaction proof. fn prepare_deposit_details, I: Instance>( - proof: &<>::PeerBlockchain as BaseHeaderChain>::TransactionInclusionProof, + proof: &<>::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof, ) -> Result, Error> { // ensure that transaction is included in finalized block that we know of let transaction = >::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); diff --git a/bridges/modules/finality-verifier/Cargo.toml b/bridges/modules/finality-verifier/Cargo.toml new file mode 100644 index 0000000000..b98f995b61 --- /dev/null +++ b/bridges/modules/finality-verifier/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "pallet-finality-verifier" +version = "0.1.0" +authors = ["Parity Technologies "] +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", +] diff --git a/bridges/modules/finality-verifier/src/lib.rs b/bridges/modules/finality-verifier/src/lib.rs new file mode 100644 index 0000000000..299bc368d4 --- /dev/null +++ b/bridges/modules/finality-verifier/src/lib.rs @@ -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 . + +//! 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 = HeaderOf<::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<::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< + ::Header, + Vec<::Header>, + >; + /// The maximum length of headers we can have in a single ancestry proof. This prevents + /// unbounded iteration when verifying proofs. + type MaxHeadersInSingleProof: Get; +} + +decl_storage! { + trait Store for Module as FinalityVerifier {} +} + +decl_error! { + pub enum Error for Module { + /// 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 for enum Call where origin: T::Origin { + type Error = Error; + + /// 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, + justification: Vec, + ancestry_proof: Vec>, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + + ensure!( + ancestry_proof.len() <= T::MaxHeadersInSingleProof::get() as usize, + >::OversizedAncestryProof + ); + + let authority_set = T::HeaderChain::authority_set(); + let voter_set = + VoterSet::new(authority_set.authorities).ok_or(>::InvalidAuthoritySet)?; + let set_id = authority_set.set_id; + + verify_justification::>( + (finality_target.hash(), *finality_target.number()), + set_id, + voter_set, + &justification + ) + .map_err(|_| >::InvalidJustification)?; + + let best_finalized = T::HeaderChain::best_finalized(); + ensure!( + T::AncestryChecker::are_ancestors(&best_finalized, &finality_target, &ancestry_proof), + >::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(>::FailedToWriteHeader)) + } + } + + if T::HeaderChain::import_finality_proof(finality_target, justification).is_err() { + return TransactionOutcome::Rollback(Err(>::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::::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::::submit_finality_proof( + Origin::signed(1), + header.clone(), + justification, + ancestry_proof, + )); + + assert_eq!( + pallet_substrate_bridge::Module::::best_headers(), + vec![(*header.number(), header.hash())] + ); + + assert_eq!(pallet_substrate_bridge::Module::::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::::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,), + >::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::::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,), + >::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 = ::MaxHeadersInSingleProof::get(); + for i in 1..=max_len + 1 { + ancestry_proof.push(test_header(i as u64)); + } + + assert_err!( + Module::::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,), + >::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::::initialize( + Origin::root(), + init_data + )); + + let header = test_header(1); + let justification = [1u8; 32].encode(); + let ancestry_proof = vec![]; + + assert_err!( + Module::::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,), + >::InvalidAuthoritySet + ); + }) + } +} diff --git a/bridges/modules/finality-verifier/src/mock.rs b/bridges/modules/finality-verifier/src/mock.rs new file mode 100644 index 0000000000..71b1e28420 --- /dev/null +++ b/bridges/modules/finality-verifier/src/mock.rs @@ -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 . + +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; +pub type TestNumber = BlockNumberOf<::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; + 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; + type AncestryChecker = Checker<::Header, Vec<::Header>>; + type MaxHeadersInSingleProof = MaxHeadersInSingleProof; +} + +#[derive(Debug)] +pub struct TestBridgedChain; + +impl Chain for TestBridgedChain { + type BlockNumber = ::BlockNumber; + type Hash = ::Hash; + type Hasher = ::Hashing; + type Header = ::Header; +} + +#[derive(Debug)] +pub struct Checker(std::marker::PhantomData<(H, P)>); + +impl crate::AncestryChecker> for Checker> { + fn are_ancestors(_ancestor: &H, _child: &H, proof: &Vec) -> bool { + !proof.is_empty() + } +} + +pub fn run_test(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) +} diff --git a/bridges/modules/substrate/Cargo.toml b/bridges/modules/substrate/Cargo.toml index 8eb2536061..02c3b17047 100644 --- a/bridges/modules/substrate/Cargo.toml +++ b/bridges/modules/substrate/Cargo.toml @@ -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", diff --git a/bridges/modules/substrate/src/fork_tests.rs b/bridges/modules/substrate/src/fork_tests.rs index a2529dec13..810678852c 100644 --- a/bridges/modules/substrate/src/fork_tests.rs +++ b/bridges/modules/substrate/src/fork_tests.rs @@ -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}; diff --git a/bridges/modules/substrate/src/lib.rs b/bridges/modules/substrate/src/lib.rs index e1c797c2ae..9644e508ed 100644 --- a/bridges/modules/substrate/src/lib.rs +++ b/bridges/modules/substrate/src/lib.rs @@ -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 Module { } } +impl bp_header_chain::HeaderChain> for Module { + fn best_finalized() -> BridgedHeader { + PalletStorage::::new().best_finalized_header().header + } + + fn authority_set() -> AuthoritySet { + PalletStorage::::new().current_authority_set() + } + + fn import_header(header: BridgedHeader) -> Result<(), ()> { + let mut verifier = verifier::Verifier { + storage: PalletStorage::::new(), + }; + + let _ = verifier.import_header(header.hash(), header).map_err(|_| ())?; + + Ok(()) + } + + fn import_finality_proof(header: BridgedHeader, finality_proof: Vec) -> Result<(), ()> { + let mut verifier = verifier::Verifier { + storage: PalletStorage::::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(origin: T::Origin) -> Result<(), BadOrigin> { match origin.into() { diff --git a/bridges/modules/substrate/src/storage.rs b/bridges/modules/substrate/src/storage.rs index 93f30bdec7..5b521306b2 100644 --- a/bridges/modules/substrate/src/storage.rs +++ b/bridges/modules/substrate/src/storage.rs @@ -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 { 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))] diff --git a/bridges/modules/substrate/src/verifier.rs b/bridges/modules/substrate/src/verifier.rs index 5cb47cb656..b22d69f78d 100644 --- a/bridges/modules/substrate/src/verifier.rs +++ b/bridges/modules/substrate/src/verifier.rs @@ -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; diff --git a/bridges/primitives/header-chain/Cargo.toml b/bridges/primitives/header-chain/Cargo.toml index d9b4a70e57..ecd5f05940 100644 --- a/bridges/primitives/header-chain/Cargo.toml +++ b/bridges/primitives/header-chain/Cargo.toml @@ -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", ] diff --git a/bridges/primitives/header-chain/src/lib.rs b/bridges/primitives/header-chain/src/lib.rs index 00978d8081..699e79cae5 100644 --- a/bridges/primitives/header-chain/src/lib.rs +++ b/bridges/primitives/header-chain/src/lib.rs @@ -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 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; } + +/// A base trait for pallets which want to keep track of a full set of headers from a bridged chain. +pub trait HeaderChain { + /// 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) -> Result<(), ()>; +} + +impl HeaderChain 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) -> Result<(), ()> { + Ok(()) + } +} + +/// A trait for checking if a given child header is a direct decendant of an ancestor. +pub trait AncestryChecker { + /// Is the child header a decendant of the ancestor header? + fn are_ancestors(ancestor: &H, child: &H, proof: &P) -> bool; +} + +impl AncestryChecker for () { + fn are_ancestors(_ancestor: &H, _child: &H, _proof: &P) -> bool { + true + } +}