// 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 . //! 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 crate::storage::ImportedHeader; use bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderOf}; use frame_support::{decl_error, decl_module, decl_storage, dispatch::DispatchResult}; use frame_system::ensure_signed; use sp_runtime::traits::Header as HeaderT; use sp_std::{marker::PhantomData, prelude::*}; // Re-export since the node uses these when configuring genesis pub use storage::{AuthoritySet, ScheduledChange}; mod justification; mod storage; mod storage_proof; mod verifier; #[cfg(test)] mod mock; pub trait Trait: frame_system::Trait { /// Chain that we are bridging here. type BridgedChain: Chain; } /// Block number of the bridged chain. pub(crate) type BridgedBlockNumber = BlockNumberOf<::BridgedChain>; /// Block hash of the bridged chain. pub(crate) type BridgedBlockHash = HashOf<::BridgedChain>; /// Header of the bridged chain. pub(crate) type BridgedHeader = HeaderOf<::BridgedChain>; decl_storage! { trait Store for Module as SubstrateBridge { /// Hash of the header at the highest known height. BestHeader: BridgedBlockHash; /// Hash of the best finalized header. BestFinalized: BridgedBlockHash; /// A header which enacts an authority set change and therefore /// requires a Grandpa justification. // Since we won't always have an authority set change scheduled we // won't always have a header which needs a justification. RequiresJustification: Option>; /// Headers which have been imported into the pallet. ImportedHeaders: map hasher(identity) BridgedBlockHash => 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()); >::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: BridgedHeader, ) -> 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: BridgedBlockHash, finality_proof: Vec, ) -> 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(()) } } } impl Module { /// Get the highest header that the pallet knows of. // In a future where we support forks this could be a Vec of headers // since we may have multiple headers at the same height. pub fn best_header() -> BridgedHeader { PalletStorage::::new().best_header().header } /// Get the best finalized header the pallet knows of. /// /// Since this has been finalized correctly a user of the bridge /// pallet should be confident that any transactions that were /// included in this or any previous header will not be reverted. pub fn best_finalized() -> BridgedHeader { PalletStorage::::new().best_finalized_header().header } /// Check if a particular header is known to the bridge pallet. pub fn is_known_header(hash: BridgedBlockHash) -> bool { PalletStorage::::new().header_exists(hash) } /// Check if a particular header is finalized. /// /// Will return false if the header is not known to the pallet. // One thing worth noting here is that this approach won't work well // once we track forks since there could be an older header on a // different fork which isn't an ancestor of our best finalized header. pub fn is_finalized_header(hash: BridgedBlockHash) -> bool { let storage = PalletStorage::::new(); if let Some(header) = storage.header_by_hash(hash) { header.number() <= storage.best_finalized_header().number() } else { false } } /// Return the latest header which enacts an authority set change /// and still needs a finality proof. /// /// Will return None if there are no headers which are missing finality proofs. pub fn requires_justification() -> Option> { let storage = PalletStorage::::new(); let hash = storage.unfinalized_header()?; let imported_header = storage.header_by_hash(hash).expect( "We write a header to storage before marking it as unfinalized, therefore this must always exist if we got an unfinalized header hash.", ); Some(imported_header.header) } } /// 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 header at the highest known height. fn best_header(&self) -> ImportedHeader; /// Update the header at the highest height. fn update_best_header(&mut self, hash: ::Hash); /// 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; /// Return a header which requires a justification. A header will require /// a justification when it enacts an new authority set. fn unfinalized_header(&self) -> Option<::Hash>; /// Mark a header as eventually requiring a justification. /// /// If None is passed the storage item is cleared. fn update_unfinalized_header(&mut self, hash: Option<::Hash>); /// 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 = BridgedHeader; fn write_header(&mut self, header: &ImportedHeader>) { let hash = header.header.hash(); >::insert(hash, header); } fn best_header(&self) -> ImportedHeader> { let hash = >::get(); self.header_by_hash(hash) .expect("A header must have been written at genesis, therefore this must always exist") } fn update_best_header(&mut self, hash: BridgedBlockHash) { >::put(hash) } 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: BridgedBlockHash) { >::put(hash) } fn header_exists(&self, hash: BridgedBlockHash) -> bool { >::contains_key(hash) } fn header_by_hash(&self, hash: BridgedBlockHash) -> Option>> { >::get(hash) } fn unfinalized_header(&self) -> Option> { >::get() } fn update_unfinalized_header(&mut self, hash: Option<::Hash>) { if let Some(hash) = hash { >::put(hash); } else { >::kill(); } } 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) } }