// 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)
}
}