diff --git a/bridges/modules/substrate/Cargo.toml b/bridges/modules/substrate/Cargo.toml index 365ce68993..6fb935e548 100644 --- a/bridges/modules/substrate/Cargo.toml +++ b/bridges/modules/substrate/Cargo.toml @@ -8,17 +8,32 @@ 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.4", default-features = false } +bp-header-chain = { path = "../../primitives/header-chain", default-features = false } +bp-substrate = { path = "../../primitives/substrate", default-features = false } finality-grandpa = { version = "0.12.3", default-features = false } hash-db = { version = "0.15.2", default-features = false } +serde = { version = "1.0", optional = true } + +[dependencies.codec] +package = "parity-scale-codec" +version = "1.3.1" +default-features = false +features = ["derive"] # Substrate Based Dependencies + [dependencies.frame-support] version = "2.0.0-rc6" tag = 'v2.0.0-rc6' default-features = false git = "https://github.com/paritytech/substrate/" +[dependencies.frame-system] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate/" + [dependencies.sp-finality-grandpa] version = "2.0.0-rc6" tag = 'v2.0.0-rc6' @@ -43,6 +58,12 @@ tag = 'v2.0.0-rc6' default-features = false git = "https://github.com/paritytech/substrate/" +# Dev Dependencies +[dev-dependencies.sp-io] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +git = "https://github.com/paritytech/substrate/" + [dev-dependencies.sp-core] version = "2.0.0-rc6" tag = 'v2.0.0-rc6' @@ -67,8 +88,11 @@ std = [ "codec/std", "finality-grandpa/std", "frame-support/std", + "frame-system/std", + "serde", "sp-finality-grandpa/std", "sp-runtime/std", "sp-std/std", "sp-trie/std", + "sp-io/std", ] diff --git a/bridges/modules/substrate/src/justification.rs b/bridges/modules/substrate/src/justification.rs index 0457278197..4887e18089 100644 --- a/bridges/modules/substrate/src/justification.rs +++ b/bridges/modules/substrate/src/justification.rs @@ -26,6 +26,7 @@ use frame_support::RuntimeDebug; use sp_finality_grandpa::{AuthorityId, AuthoritySignature, SetId}; use sp_runtime::traits::Header as HeaderT; use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; +use sp_std::prelude::Vec; /// Justification verification error. #[derive(RuntimeDebug, PartialEq)] diff --git a/bridges/modules/substrate/src/lib.rs b/bridges/modules/substrate/src/lib.rs index ec00311e6e..4fb9dbd3d5 100644 --- a/bridges/modules/substrate/src/lib.rs +++ b/bridges/modules/substrate/src/lib.rs @@ -14,5 +14,263 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +//! Substrate Bridge Pallet +//! +//! This pallet is an on-chain light client for chains which have a notion of finality. +//! +//! It has a simple interface for achieving this. First it can import headers to the runtime +//! storage. During this it will check the validity of the headers and ensure they don't conflict +//! with any existing headers (e.g they're on a different finalized chain). Secondly it can finalize +//! an already imported header (and its ancestors) given a valid Grandpa justification. +//! +//! With these two functions the pallet is able to form a "source of truth" for what headers have +//! been finalized on a given Substrate chain. This can be a useful source of info for other +//! higher-level applications. + +#![cfg_attr(not(feature = "std"), no_std)] +// Runtime-generated enums +#![allow(clippy::large_enum_variant)] + +use bp_substrate::{AuthoritySet, ImportedHeader, ScheduledChange}; +use frame_support::{decl_error, decl_module, decl_storage, dispatch}; +use frame_system::ensure_signed; +use sp_runtime::traits::Header as HeaderT; +use sp_std::{marker::PhantomData, prelude::*}; + mod justification; mod storage_proof; +mod verifier; + +#[cfg(test)] +mod mock; + +type Hash = ::Hash; +type Number = ::Number; + +pub trait Trait: frame_system::Trait {} + +decl_storage! { + trait Store for Module as SubstrateBridge { + /// Hash of the best finalized header. + BestFinalized: T::Hash; + /// Headers which have been imported into the pallet. + ImportedHeaders: map hasher(identity) T::Hash => Option>; + /// The current Grandpa Authority set. + CurrentAuthoritySet: AuthoritySet; + /// The next scheduled authority set change. + /// + // Grandpa doesn't require there to always be a pending change. In fact, most of the time + // there will be no pending change available. + NextScheduledChange: Option>>; + } + add_extra_genesis { + config(initial_header): Option; + config(initial_authority_list): sp_finality_grandpa::AuthorityList; + config(initial_set_id): sp_finality_grandpa::SetId; + config(first_scheduled_change): Option>>; + build(|config| { + assert!( + !config.initial_authority_list.is_empty(), + "An initial authority list is needed." + ); + + let initial_header = config + .initial_header + .clone() + .expect("An initial header is needed"); + + >::put(initial_header.hash()); + >::insert( + initial_header.hash(), + ImportedHeader { + header: initial_header, + requires_justification: false, + is_finalized: true, + }, + ); + + let authority_set = + AuthoritySet::new(config.initial_authority_list.clone(), config.initial_set_id); + CurrentAuthoritySet::put(authority_set); + + if let Some(ref change) = config.first_scheduled_change { + >::put(change); + }; + }) + } +} + +decl_error! { + pub enum Error for Module { + /// This header has failed basic verification. + InvalidHeader, + /// This header has not been finalized. + UnfinalizedHeader, + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + type Error = Error; + + /// Import a signed Substrate header into the runtime. + /// + /// This will perform some basic checks to make sure it is fine to + /// import into the runtime. However, it does not perform any checks + /// related to finality. + // TODO: Update weights [#78] + #[weight = 0] + pub fn import_signed_header( + origin, + header: T::Header, + ) -> dispatch::DispatchResult { + let _ = ensure_signed(origin)?; + frame_support::debug::trace!(target: "sub-bridge", "Got header {:?}", header); + + let mut verifier = verifier::Verifier { + storage: PalletStorage::::new(), + }; + + let _ = verifier + .import_header(header) + .map_err(|_| >::InvalidHeader)?; + + Ok(()) + } + + /// Import a finalty proof for a particular header. + /// + /// This will take care of finalizing any already imported headers + /// which get finalized when importing this particular proof, as well + /// as updating the current and next validator sets. + // TODO: Update weights [#78] + #[weight = 0] + pub fn finalize_header( + origin, + hash: Hash, + finality_proof: Vec, + ) -> dispatch::DispatchResult { + let _ = ensure_signed(origin)?; + frame_support::debug::trace!(target: "sub-bridge", "Got header hash {:?}", hash); + + let mut verifier = verifier::Verifier { + storage: PalletStorage::::new(), + }; + + let _ = verifier + .import_finality_proof(hash, finality_proof.into()) + .map_err(|_| >::UnfinalizedHeader)?; + + Ok(()) + } + } +} + +/// Expected interface for interacting with bridge pallet storage. +// TODO: This should be split into its own less-Substrate-dependent crate +pub trait BridgeStorage { + /// The header type being used by the pallet. + type Header: HeaderT; + + /// Write a header to storage. + fn write_header(&mut self, header: &ImportedHeader); + + /// Get the best finalized header the pallet knows of. + fn best_finalized_header(&self) -> ImportedHeader; + + /// Update the best finalized header the pallet knows of. + fn update_best_finalized(&self, hash: ::Hash); + + /// Check if a particular header is known to the pallet. + fn header_exists(&self, hash: ::Hash) -> bool; + + /// Get a specific header by its hash. + /// + /// Returns None if it is not known to the pallet. + fn header_by_hash(&self, hash: ::Hash) -> Option>; + + /// Get the current Grandpa authority set. + fn current_authority_set(&self) -> AuthoritySet; + + /// Update the current Grandpa authority set. + /// + /// Should only be updated when a scheduled change has been triggered. + fn update_current_authority_set(&self, new_set: AuthoritySet); + + /// Replace the current authority set with the next scheduled set. + /// + /// Returns an error if there is no scheduled authority set to enact. + fn enact_authority_set(&mut self) -> Result<(), ()>; + + /// Get the next scheduled Grandpa authority set change. + fn scheduled_set_change(&self) -> Option::Number>>; + + /// Schedule a Grandpa authority set change in the future. + fn schedule_next_set_change(&self, next_change: ScheduledChange<::Number>); +} + +/// Used to interact with the pallet storage in a more abstract way. +#[derive(Default, Clone)] +pub struct PalletStorage(PhantomData); + +impl PalletStorage { + fn new() -> Self { + Self(PhantomData::::default()) + } +} + +impl BridgeStorage for PalletStorage { + type Header = T::Header; + + fn write_header(&mut self, header: &ImportedHeader) { + let hash = header.header.hash(); + >::insert(hash, header); + } + + fn best_finalized_header(&self) -> ImportedHeader { + let hash = >::get(); + self.header_by_hash(hash) + .expect("A finalized header was added at genesis, therefore this must always exist") + } + + fn update_best_finalized(&self, hash: Hash) { + >::put(hash) + } + + fn header_exists(&self, hash: Hash) -> bool { + >::contains_key(hash) + } + + fn header_by_hash(&self, hash: Hash) -> Option> { + >::get(hash) + } + + fn current_authority_set(&self) -> AuthoritySet { + CurrentAuthoritySet::get() + } + + fn update_current_authority_set(&self, new_set: AuthoritySet) { + CurrentAuthoritySet::put(new_set) + } + + fn enact_authority_set(&mut self) -> Result<(), ()> { + if >::exists() { + let new_set = >::take() + .expect("Ensured that entry existed in storage") + .authority_set; + self.update_current_authority_set(new_set); + + Ok(()) + } else { + Err(()) + } + } + + fn scheduled_set_change(&self) -> Option>> { + >::get() + } + + fn schedule_next_set_change(&self, next_change: ScheduledChange>) { + >::put(next_change) + } +} diff --git a/bridges/modules/substrate/src/mock.rs b/bridges/modules/substrate/src/mock.rs new file mode 100644 index 0000000000..303f1ad61d --- /dev/null +++ b/bridges/modules/substrate/src/mock.rs @@ -0,0 +1,75 @@ +// Copyright 2020 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 . + +#![cfg(test)] + +use crate::Trait; +use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; +use sp_runtime::{ + testing::{Header, H256}, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +pub type AccountId = u64; + +#[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::Trait 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 MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = (); + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); + type ModuleToIndex = (); + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = (); + type SystemWeightInfo = (); +} + +impl Trait for TestRuntime {} + +pub fn run_test(test: impl FnOnce() -> T) -> T { + sp_io::TestExternalities::new(Default::default()).execute_with(test) +} diff --git a/bridges/modules/substrate/src/storage_proof.rs b/bridges/modules/substrate/src/storage_proof.rs index 40a1399695..97ec09e132 100644 --- a/bridges/modules/substrate/src/storage_proof.rs +++ b/bridges/modules/substrate/src/storage_proof.rs @@ -21,6 +21,7 @@ use hash_db::{HashDB, Hasher, EMPTY_PREFIX}; use sp_runtime::RuntimeDebug; +use sp_std::vec::Vec; use sp_trie::{read_trie_value, Layout, MemoryDB, StorageProof}; /// This struct is used to read storage values from a subset of a Merklized database. The "proof" diff --git a/bridges/modules/substrate/src/verifier.rs b/bridges/modules/substrate/src/verifier.rs new file mode 100644 index 0000000000..aa0924508a --- /dev/null +++ b/bridges/modules/substrate/src/verifier.rs @@ -0,0 +1,680 @@ +// Copyright 2020 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 . + +//! The verifier's role is to check the validity of headers being imported, and also determine if +//! they can be finalized. +//! +//! When importing headers it performs checks to ensure that no invariants are broken (like +//! importing the same header twice). When it imports finality proofs it will ensure that the proof +//! has been signed off by the correct Grandpa authorities, and also enact any authority set changes +//! if required. + +use crate::BridgeStorage; +use bp_substrate::{check_finality_proof, AuthoritySet, ImportedHeader, ScheduledChange}; +use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID}; +use sp_runtime::generic::OpaqueDigestItemId; +use sp_runtime::traits::{CheckedAdd, Header as HeaderT, One}; +use sp_std::{prelude::Vec, vec}; + +/// The finality proof used by the pallet. +/// +/// For a Substrate based chain using Grandpa this will +/// be an encoded Grandpa Justification. +pub struct FinalityProof(Vec); + +impl From<&[u8]> for FinalityProof { + fn from(proof: &[u8]) -> Self { + Self(proof.to_vec()) + } +} + +impl From> for FinalityProof { + fn from(proof: Vec) -> Self { + Self(proof) + } +} + +/// Errors which can happen while importing a header. +#[derive(Debug, PartialEq)] +pub enum ImportError { + /// This header is older than our latest finalized block, thus not useful. + OldHeader, + /// This header has already been imported by the pallet. + HeaderAlreadyExists, + /// We're missing a parent for this header. + MissingParent, + /// The number of the header does not follow its parent's number. + InvalidChildNumber, + /// The height of the next authority set change overflowed. + ScheduledHeightOverflow, +} + +/// Errors which can happen while verifying a headers finality. +#[derive(Debug, PartialEq)] +pub enum FinalizationError { + /// This header has never been imported by the pallet. + UnknownHeader, + /// We were unable to prove finality for this header. + UnfinalizedHeader, + /// Trying to prematurely import a justification + PrematureJustification, + /// We failed to verify this header's ancestry. + AncestryCheckFailed, + /// This header is older than our latest finalized block, thus not useful. + OldHeader, +} + +/// Used to verify imported headers and their finality status. +#[derive(Debug)] +pub struct Verifier { + pub storage: S, +} + +impl Verifier +where + S: BridgeStorage
, + H: HeaderT, +{ + /// Import a header to the pallet. + /// + /// Will perform some basic checks to make sure that this header doesn't break any assumptions + /// such as being on a different finalized fork. + pub fn import_header(&mut self, header: H) -> Result<(), ImportError> { + let best_finalized = self.storage.best_finalized_header(); + + if header.number() <= best_finalized.number() { + return Err(ImportError::OldHeader); + } + + if self.storage.header_exists(header.hash()) { + return Err(ImportError::HeaderAlreadyExists); + } + + let parent_header = self + .storage + .header_by_hash(*header.parent_hash()) + .ok_or(ImportError::MissingParent)?; + + let parent_number = *parent_header.number(); + if parent_number + One::one() != *header.number() { + return Err(ImportError::InvalidChildNumber); + } + + // This header requires a justification since it enacts an authority set change. We don't + // need to act on it right away (we'll update the set once this header gets finalized), but + // we need to make a note of it. + // + // TODO: This assumes that we can only have one authority set change pending at a time. + // This is not strictly true as Grandpa may schedule multiple changes on a given chain + // if the "next next" change is scheduled after the "delay" period of the "next" change + let requires_justification = if let Some(change) = self.storage.scheduled_set_change() { + change.height == *header.number() + } else { + // Since we don't currently have a pending authority set change let's check if the header + // contains a log indicating when the next change should be. + if let Some(change) = find_scheduled_change(&header) { + let next_set = AuthoritySet { + authorities: change.next_authorities, + set_id: self.storage.current_authority_set().set_id + 1, + }; + + let height = (*header.number()) + .checked_add(&change.delay) + .ok_or(ImportError::ScheduledHeightOverflow)?; + let scheduled_change = ScheduledChange { + authority_set: next_set, + height, + }; + + self.storage.schedule_next_set_change(scheduled_change); + + // If the delay is 0 this header will enact the change it signaled + height == *header.number() + } else { + false + } + }; + + let is_finalized = false; + self.storage.write_header(&ImportedHeader { + header, + requires_justification, + is_finalized, + }); + + Ok(()) + } + + /// Verify that a previously imported header can be finalized with the given Grandpa finality + /// proof. If the header enacts an authority set change the change will be applied once the + /// header has been finalized. + pub fn import_finality_proof(&mut self, hash: H::Hash, proof: FinalityProof) -> Result<(), FinalizationError> { + // Make sure that we've previously imported this header + let header = self + .storage + .header_by_hash(hash) + .ok_or(FinalizationError::UnknownHeader)?; + + // We don't want to finalize an ancestor of an already finalized + // header, this would be inconsistent + let last_finalized = self.storage.best_finalized_header(); + if header.number() <= last_finalized.number() { + return Err(FinalizationError::OldHeader); + } + + let current_authority_set = self.storage.current_authority_set(); + let is_finalized = check_finality_proof(&header, ¤t_authority_set, &proof.0); + if !is_finalized { + return Err(FinalizationError::UnfinalizedHeader); + } + + frame_support::debug::trace!(target: "sub-bridge", "Checking ancestry for headers between {:?} and {:?}", last_finalized, header); + let mut finalized_headers = + if let Some(ancestors) = headers_between(&self.storage, last_finalized, header.clone()) { + // Since we only try and finalize headers with a height strictly greater + // than `best_finalized` if `headers_between` returns Some we must have + // at least one element. If we don't something's gone wrong, so best + // to die before we write to storage. + assert_eq!( + ancestors.is_empty(), + false, + "Empty ancestry list returned from `headers_between()`", + ); + + // Check if any of our ancestors `requires_justification` a.k.a schedule authority + // set changes. If they're still waiting to be finalized we must reject this + // justification. We don't include our current header in this check. + // + // We do this because it is important to to import justifications _in order_, + // otherwise we risk finalizing headers on competing chains. + let requires_justification = ancestors.iter().skip(1).find(|h| h.requires_justification); + if requires_justification.is_some() { + return Err(FinalizationError::PrematureJustification); + } + + ancestors + } else { + return Err(FinalizationError::AncestryCheckFailed); + }; + + // If the current header was marked as `requires_justification` it means that it enacts a + // new authority set change. When we finalize the header we need to update the current + // authority set. + if header.requires_justification { + // If we are unable to enact an authority set it means our storage entry for scheduled + // changes is missing. Best to crash since this is likely a bug. + let _ = self.storage.enact_authority_set().expect( + "Headers must only be marked as `requires_justification` if there's a scheduled change in storage.", + ); + } + + for header in finalized_headers.iter_mut() { + header.is_finalized = true; + header.requires_justification = false; + self.storage.write_header(header); + } + + self.storage.update_best_finalized(hash); + + Ok(()) + } +} + +/// Returns the lineage of headers between [child, ancestor) +fn headers_between( + storage: &S, + ancestor: ImportedHeader, + child: ImportedHeader, +) -> Option>> +where + S: BridgeStorage
, + H: HeaderT, +{ + let mut ancestors = vec![]; + let mut current_header = child; + + while ancestor.hash() != current_header.hash() { + // We've gotten to the same height and we're not related + if ancestor.number() >= current_header.number() { + return None; + } + + let parent = storage.header_by_hash(*current_header.parent_hash()); + ancestors.push(current_header); + current_header = match parent { + Some(h) => h, + None => return None, + } + } + + Some(ancestors) +} + +fn find_scheduled_change(header: &H) -> Option> { + let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID); + + let filter_log = |log: ConsensusLog| match log { + ConsensusLog::ScheduledChange(change) => Some(change), + _ => None, + }; + + // find the first consensus digest with the right ID which converts to + // the right kind of consensus log. + header.digest().convert_first(|l| l.try_to(id).and_then(filter_log)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + use crate::{BestFinalized, ImportedHeaders, PalletStorage}; + use frame_support::{assert_err, assert_ok}; + use frame_support::{StorageMap, StorageValue}; + use sp_finality_grandpa::{AuthorityId, AuthorityList}; + use sp_runtime::testing::UintAuthorityId; + + type TestHeader = ::Header; + type TestNumber = ::Number; + + fn unfinalized_header(num: u64) -> ImportedHeader { + ImportedHeader { + header: TestHeader::new_from_number(num), + requires_justification: false, + is_finalized: false, + } + } + + fn get_authorities(authorities: Vec<(u64, u64)>) -> AuthorityList { + authorities + .iter() + .map(|(id, weight)| (UintAuthorityId(*id).to_public_key::(), *weight)) + .collect() + } + + fn schedule_next_change( + authorities: Vec<(u64, u64)>, + set_id: u64, + height: TestNumber, + ) -> ScheduledChange { + let authorities = get_authorities(authorities); + let authority_set = AuthoritySet::new(authorities, set_id); + ScheduledChange { authority_set, height } + } + + // Useful for quickly writing a chain of headers to storage + fn write_headers>( + storage: &mut S, + headers: Vec<(u64, bool, bool)>, + ) -> Vec> { + let mut imported_headers = vec![]; + let genesis = ImportedHeader { + header: TestHeader::new_from_number(0), + requires_justification: false, + is_finalized: true, + }; + + >::put(genesis.hash()); + storage.write_header(&genesis); + imported_headers.push(genesis); + + for (num, requires_justification, is_finalized) in headers { + let mut h = TestHeader::new_from_number(num); + h.parent_hash = imported_headers.last().unwrap().hash(); + + let header = ImportedHeader { + header: h, + requires_justification, + is_finalized, + }; + + storage.write_header(&header); + imported_headers.push(header); + } + + imported_headers + } + + #[test] + fn fails_to_import_old_header() { + run_test(|| { + let mut storage = PalletStorage::::new(); + let parent = unfinalized_header(5); + storage.write_header(&parent); + storage.update_best_finalized(parent.hash()); + + let header = TestHeader::new_from_number(1); + let mut verifier = Verifier { storage }; + assert_err!(verifier.import_header(header), ImportError::OldHeader); + }) + } + + #[test] + fn fails_to_import_header_without_parent() { + run_test(|| { + let mut storage = PalletStorage::::new(); + let parent = unfinalized_header(1); + storage.write_header(&parent); + storage.update_best_finalized(parent.hash()); + + // By default the parent is `0x00` + let header = TestHeader::new_from_number(2); + + let mut verifier = Verifier { storage }; + assert_err!(verifier.import_header(header), ImportError::MissingParent); + }) + } + + #[test] + fn fails_to_import_header_twice() { + run_test(|| { + let storage = PalletStorage::::new(); + let header = TestHeader::new_from_number(1); + >::put(header.hash()); + + let imported_header = ImportedHeader { + header: header.clone(), + requires_justification: false, + is_finalized: false, + }; + >::insert(header.hash(), &imported_header); + + let mut verifier = Verifier { storage }; + assert_err!(verifier.import_header(header), ImportError::OldHeader); + }) + } + + #[test] + fn succesfully_imports_valid_but_unfinalized_header() { + run_test(|| { + let storage = PalletStorage::::new(); + let parent = TestHeader::new_from_number(1); + let parent_hash = parent.hash(); + >::put(parent.hash()); + + let imported_header = ImportedHeader { + header: parent, + requires_justification: false, + is_finalized: true, + }; + >::insert(parent_hash, &imported_header); + + let mut header = TestHeader::new_from_number(2); + header.parent_hash = parent_hash; + let mut verifier = Verifier { + storage: storage.clone(), + }; + assert_ok!(verifier.import_header(header.clone())); + + let stored_header = storage.header_by_hash(header.hash()); + assert!(stored_header.is_some()); + assert_eq!(stored_header.unwrap().is_finalized, false); + }) + } + + #[test] + fn related_headers_are_ancestors() { + run_test(|| { + let mut storage = PalletStorage::::new(); + + let headers = vec![(1, false, false), (2, false, false), (3, false, false)]; + let mut imported_headers = write_headers(&mut storage, headers); + + for header in imported_headers.iter() { + assert!(storage.header_exists(header.hash())); + } + + let ancestor = imported_headers.remove(0); + let child = imported_headers.pop().unwrap(); + let ancestors = headers_between(&storage, ancestor, child); + + assert!(ancestors.is_some()); + assert_eq!(ancestors.unwrap().len(), 3); + }) + } + + #[test] + fn unrelated_headers_are_not_ancestors() { + run_test(|| { + let mut storage = PalletStorage::::new(); + + let headers = vec![(1, false, false), (2, false, false), (3, false, false)]; + let mut imported_headers = write_headers(&mut storage, headers); + for header in imported_headers.iter() { + assert!(storage.header_exists(header.hash())); + } + + // Need to give it a different parent_hash or else it'll be + // related to our test genesis header + let mut bad_ancestor = TestHeader::new_from_number(0); + bad_ancestor.parent_hash = [1u8; 32].into(); + let bad_ancestor = ImportedHeader { + header: bad_ancestor, + requires_justification: false, + is_finalized: false, + }; + + let child = imported_headers.pop().unwrap(); + let ancestors = headers_between(&storage, bad_ancestor, child); + assert!(ancestors.is_none()); + }) + } + + #[test] + fn ancestor_newer_than_child_is_not_related() { + run_test(|| { + let mut storage = PalletStorage::::new(); + + let headers = vec![(1, false, false), (2, false, false), (3, false, false)]; + let mut imported_headers = write_headers(&mut storage, headers); + for header in imported_headers.iter() { + assert!(storage.header_exists(header.hash())); + } + + // What if we have an "ancestor" that's newer than child? + let new_ancestor = TestHeader::new_from_number(5); + let new_ancestor = ImportedHeader { + header: new_ancestor, + requires_justification: false, + is_finalized: false, + }; + + let child = imported_headers.pop().unwrap(); + let ancestors = headers_between(&storage, new_ancestor, child); + assert!(ancestors.is_none()); + }) + } + + #[test] + fn finalizes_header_which_doesnt_enact_or_schedule_a_new_authority_set() { + run_test(|| { + let mut storage = PalletStorage::::new(); + let headers = vec![(1, false, false)]; + let imported_headers = write_headers(&mut storage, headers); + + // Nothing special about this header, yet Grandpa may have created a justification + // for it since it does that periodically + let mut header = TestHeader::new_from_number(2); + header.parent_hash = imported_headers[1].hash(); + + let mut verifier = Verifier { + storage: storage.clone(), + }; + + assert_ok!(verifier.import_header(header.clone())); + assert_ok!(verifier.import_finality_proof(header.hash(), vec![4, 2].into())); + assert_eq!(storage.best_finalized_header().header, header); + }) + } + + #[test] + fn correctly_verifies_and_finalizes_chain_of_headers() { + run_test(|| { + let mut storage = PalletStorage::::new(); + let headers = vec![(1, false, false), (2, false, false)]; + let imported_headers = write_headers(&mut storage, headers); + + let mut header = TestHeader::new_from_number(3); + header.parent_hash = imported_headers[2].hash(); + + let mut verifier = Verifier { + storage: storage.clone(), + }; + assert!(verifier.import_header(header.clone()).is_ok()); + assert!(verifier.import_finality_proof(header.hash(), vec![4, 2].into()).is_ok()); + + // Make sure we marked the our headers as finalized + assert!(storage.header_by_hash(imported_headers[1].hash()).unwrap().is_finalized); + assert!(storage.header_by_hash(imported_headers[2].hash()).unwrap().is_finalized); + assert!(storage.header_by_hash(header.hash()).unwrap().is_finalized); + + // Make sure the header at the highest height is the best finalized + assert_eq!(storage.best_finalized_header().header, header); + }); + } + + #[test] + fn updates_authority_set_upon_finalizing_header_which_enacts_change() { + run_test(|| { + let mut storage = PalletStorage::::new(); + let headers = vec![(1, false, false)]; + let imported_headers = write_headers(&mut storage, headers); + + let set_id = 0; + let authorities = get_authorities(vec![(1, 1)]); + let initial_authority_set = AuthoritySet::new(authorities, set_id); + storage.update_current_authority_set(initial_authority_set); + + // This header enacts an authority set change upon finalization + let mut header = TestHeader::new_from_number(2); + header.parent_hash = imported_headers[1].hash(); + + // Schedule a change at the height of our header + let set_id = 1; + let height = *header.number(); + let authorities = vec![(2, 1)]; + let change = schedule_next_change(authorities, set_id, height); + storage.schedule_next_set_change(change.clone()); + + let mut verifier = Verifier { + storage: storage.clone(), + }; + + assert_ok!(verifier.import_header(header.clone())); + assert_ok!(verifier.import_finality_proof(header.hash(), vec![4, 2].into())); + assert_eq!(storage.best_finalized_header().header, header); + + // Make sure that we have updated the set now that we've finalized our header + assert_eq!(storage.current_authority_set(), change.authority_set); + }) + } + + #[test] + fn importing_finality_proof_for_already_finalized_header_doesnt_work() { + run_test(|| { + let mut storage = PalletStorage::::new(); + let genesis = TestHeader::new_from_number(0); + + let genesis = ImportedHeader { + header: genesis, + requires_justification: false, + is_finalized: true, + }; + + // Make sure that genesis is the best finalized header + >::put(genesis.hash()); + storage.write_header(&genesis); + + let mut verifier = Verifier { storage }; + + // Now we want to try and import it again to see what happens + assert_eq!( + verifier + .import_finality_proof(genesis.hash(), vec![4, 2].into()) + .unwrap_err(), + FinalizationError::OldHeader + ); + }); + } + + // We're supposed to enact a set change at header N. This means that when we import it we must + // remember that it requires a justification. We can continue importing headers past N but must + // not finalize any childen. At a later point in time we should be able to import the + // justification for N. + // + // Since N enacts a new authority set, when we finalize it we should see this change reflected + // correctly. + // + // [G] <- [N-1] <- [N] <- [N+1] <- [N+2] + // | |- Import justification for N here + // |- Enacts change, needs justification + #[test] + fn allows_importing_justification_at_block_past_scheduled_change() { + run_test(|| { + let mut storage = PalletStorage::::new(); + let headers = vec![(1, false, false)]; + let imported_headers = write_headers(&mut storage, headers); + + // This is header N + let mut header = TestHeader::new_from_number(2); + header.parent_hash = imported_headers[1].hash(); + + // Schedule a change at height N + let set_id = 1; + let height = *header.number(); + let authorities = vec![(1, 1)]; + let change = schedule_next_change(authorities, set_id, height); + storage.schedule_next_set_change(change.clone()); + + // Import header N + let mut verifier = Verifier { + storage: storage.clone(), + }; + assert!(verifier.import_header(header.clone()).is_ok()); + + // Header N should be marked as needing a justification + assert_eq!( + storage.header_by_hash(header.hash()).unwrap().requires_justification, + true + ); + + // Now we want to import some headers which are past N + let mut child = TestHeader::new_from_number(*header.number() + 1); + child.parent_hash = header.hash(); + assert!(verifier.import_header(child.clone()).is_ok()); + + let mut grandchild = TestHeader::new_from_number(*child.number() + 1); + grandchild.parent_hash = child.hash(); + assert!(verifier.import_header(grandchild).is_ok()); + + // Even though we're a few headers ahead we should still be able to import + // a justification for header N + assert!(verifier.import_finality_proof(header.hash(), vec![4, 2].into()).is_ok()); + + // Some checks to make sure that our header has been correctly finalized + let finalized_header = storage.header_by_hash(header.hash()).unwrap(); + assert!(finalized_header.is_finalized); + assert_eq!(finalized_header.requires_justification, false); + assert_eq!(storage.best_finalized_header().header, header); + + // Make sure we marked the parent of the header at N as finalized + assert!(storage.header_by_hash(imported_headers[1].hash()).unwrap().is_finalized); + + // Since our header was supposed to enact a new authority set change when it got + // finalized let's make sure that the authority set actually changed + assert_eq!(storage.current_authority_set(), change.authority_set); + }) + } +} diff --git a/bridges/primitives/substrate/Cargo.toml b/bridges/primitives/substrate/Cargo.toml new file mode 100644 index 0000000000..c452b767a8 --- /dev/null +++ b/bridges/primitives/substrate/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "bp-substrate" +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] +serde = { version = "1.0", optional = true } + +[dependencies.sp-finality-grandpa] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate.git" + +[dependencies.sp-runtime] +version = "2.0.0-rc6" +tag = 'v2.0.0-rc6' +default-features = false +git = "https://github.com/paritytech/substrate/" + +[dependencies.parity-scale-codec] +version = "1.3.1" +default-features = false +features = ["derive"] + +[features] +default = ["std"] +std = [ + "serde/std", + "sp-runtime/std", + "sp-finality-grandpa/std", +] diff --git a/bridges/primitives/substrate/src/lib.rs b/bridges/primitives/substrate/src/lib.rs new file mode 100644 index 0000000000..0b63f0deef --- /dev/null +++ b/bridges/primitives/substrate/src/lib.rs @@ -0,0 +1,82 @@ +// Copyright 2019-2020 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 . + +//! Primitives for the Substrate light client (a.k.a bridge) pallet. + +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +use core::default::Default; +use parity_scale_codec::{Decode, Encode}; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_finality_grandpa::{AuthorityList, SetId}; +use sp_runtime::RuntimeDebug; + +/// 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))] +pub struct ScheduledChange { + /// The authority set that will be used once this change is enacted. + pub authority_set: AuthoritySet, + /// The block height at which the authority set should be enacted. + /// + /// Note: It will only be enacted once a header at this height is finalized. + pub height: N, +} + +/// A more useful representation of a header for storage purposes. +#[derive(Default, Encode, Decode, Clone, RuntimeDebug, PartialEq)] +pub struct ImportedHeader { + /// A plain Substrate header. + pub header: H, + /// Does this header enact a new authority set change. If it does + /// then it will require a justification. + pub requires_justification: bool, + /// Has this header been finalized, either explicitly via a justification, + /// or implicitly via one of its children getting finalized. + pub is_finalized: bool, +} + +impl core::ops::Deref for ImportedHeader { + type Target = H; + + fn deref(&self) -> &H { + &self.header + } +} + +/// Prove that the given header was finalized by the given authority set. +pub fn check_finality_proof(_header: &H, _set: &AuthoritySet, _justification: &[u8]) -> bool { + true +}