Skip to content

Commit

Permalink
Implement Substrate Pallet Runtime APIs (paritytech#389)
Browse files Browse the repository at this point in the history
* Implement public helpers for querying header info

* Update `best_header` when importing headers

* Add BestHeader to GenesisConfig

* Define extra types for Millau primitives

* Start implementing runtime APIs in Millau runtime

* Add helper for getting headers which require a justification

* Add runtime API for getting headers requiring a justification

* Reword `expect()` proof for valid authority sets

* Fix typo

* Clean up Hasher comment

* Add the Call Dispatch Pallet back to the Millau runtime

* Use types from Rialto in bridge pallet config

* Use the Rialto runtime APIS in the Millau runtime

* Include Millau bridge instance in Rialto runtime

* Add missing doc comment

* Use one storage function for setting and clearing `RequiresJustification`

* Remove TODO comments
  • Loading branch information
HCastano authored and bkchr committed Apr 10, 2024
1 parent cfe1e43 commit 86834e2
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 31 deletions.
4 changes: 4 additions & 0 deletions bridges/bin/millau/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ serde = { version = "1.0.115", optional = true, features = ["derive"] }

bp-message-lane = { path = "../../../primitives/message-lane", default-features = false }
bp-millau = { path = "../../../primitives/millau", default-features = false }
bp-rialto = { path = "../../../primitives/rialto", default-features = false }
pallet-bridge-call-dispatch = { path = "../../../modules/call-dispatch", default-features = false }
pallet-message-lane = { path = "../../../modules/message-lane", default-features = false }
pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
pallet-substrate-bridge = { path = "../../../modules/substrate", default-features = false }

# Substrate Dependencies

Expand Down Expand Up @@ -53,6 +55,7 @@ default = ["std"]
std = [
"bp-message-lane/std",
"bp-millau/std",
"bp-rialto/std",
"codec/std",
"frame-executive/std",
"frame-support/std",
Expand All @@ -66,6 +69,7 @@ std = [
"pallet-randomness-collective-flip/std",
"pallet-shift-session-manager/std",
"pallet-session/std",
"pallet-substrate-bridge/std",
"pallet-sudo/std",
"pallet-timestamp/std",
"pallet-transaction-payment/std",
Expand Down
40 changes: 39 additions & 1 deletion bridges/bin/millau/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub use frame_support::{
};

pub use pallet_balances::Call as BalancesCall;
pub use pallet_substrate_bridge::Call as BridgeSubstrateCall;
pub use pallet_timestamp::Call as TimestampCall;

#[cfg(any(feature = "std", test))]
Expand Down Expand Up @@ -216,7 +217,6 @@ impl frame_system::Trait for Runtime {
impl pallet_aura::Trait for Runtime {
type AuthorityId = AuraId;
}

impl pallet_bridge_call_dispatch::Trait for Runtime {
type Event = Event;
type MessageId = (bp_message_lane::LaneId, bp_message_lane::MessageNonce);
Expand Down Expand Up @@ -308,6 +308,13 @@ impl pallet_session::Trait for Runtime {
type WeightInfo = ();
}

impl pallet_substrate_bridge::Trait for Runtime {
type BridgedHeader = bp_rialto::Header;
type BridgedBlockNumber = bp_rialto::BlockNumber;
type BridgedBlockHash = bp_rialto::Hash;
type BridgedBlockHasher = bp_rialto::Hasher;
}

impl pallet_shift_session_manager::Trait for Runtime {}

construct_runtime!(
Expand All @@ -316,6 +323,7 @@ construct_runtime!(
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
BridgeRialto: pallet_substrate_bridge::{Module, Call, Storage},
BridgeCallDispatch: pallet_bridge_call_dispatch::{Module, Event<T>},
System: frame_system::{Module, Call, Config, Storage, Event<T>},
RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage},
Expand Down Expand Up @@ -479,4 +487,34 @@ impl_runtime_apis! {
None
}
}

impl bp_rialto::RialtoHeaderApi<Block> for Runtime {
fn best_block() -> (bp_rialto::BlockNumber, bp_rialto::Hash) {
let header = BridgeRialto::best_header();
(header.number, header.hash())
}

fn finalized_block() -> (bp_rialto::BlockNumber, bp_rialto::Hash) {
let header = BridgeRialto::best_finalized();
(header.number, header.hash())
}

fn incomplete_headers() -> Vec<(bp_rialto::BlockNumber, bp_rialto::Hash)> {
// Since the pallet doesn't accept multiple scheduled changes right now
// we can only have one header requiring a justification at any time.
if let Some(header) = BridgeRialto::requires_justification() {
vec![(header.number, header.hash())]
} else {
vec![]
}
}

fn is_known_block(hash: bp_rialto::Hash) -> bool {
BridgeRialto::is_known_header(hash)
}

fn is_finalized_block(hash: bp_rialto::Hash) -> bool {
BridgeRialto::is_finalized_header(hash)
}
}
}
4 changes: 3 additions & 1 deletion bridges/bin/rialto/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ bp-rialto = { path = "../../../primitives/rialto", default-features = false }
pallet-bridge-eth-poa = { path = "../../../modules/ethereum", default-features = false }
pallet-bridge-call-dispatch = { path = "../../../modules/call-dispatch", default-features = false }
pallet-bridge-currency-exchange = { path = "../../../modules/currency-exchange", default-features = false }
pallet-substrate-bridge = { path = "../../../modules/substrate", default-features = false }
pallet-message-lane = { path = "../../../modules/message-lane", default-features = false }
pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }

Expand Down Expand Up @@ -68,8 +69,8 @@ std = [
"bp-eth-poa/std",
"bp-header-chain/std",
"bp-message-lane/std",
"bp-millau/std",
"bp-rialto/std",
"bp-millau/std",
"codec/std",
"frame-benchmarking/std",
"frame-executive/std",
Expand All @@ -85,6 +86,7 @@ std = [
"pallet-message-lane/std",
"pallet-randomness-collective-flip/std",
"pallet-shift-session-manager/std",
"pallet-substrate-bridge/std",
"pallet-sudo/std",
"pallet-timestamp/std",
"pallet-transaction-payment/std",
Expand Down
54 changes: 37 additions & 17 deletions bridges/bin/rialto/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,13 @@ impl pallet_session::Trait for Runtime {
type WeightInfo = ();
}

impl pallet_substrate_bridge::Trait for Runtime {
type BridgedHeader = bp_millau::Header;
type BridgedBlockNumber = bp_millau::BlockNumber;
type BridgedBlockHash = bp_millau::Hash;
type BridgedBlockHasher = bp_millau::Hasher;
}

impl pallet_shift_session_manager::Trait for Runtime {}

construct_runtime!(
Expand All @@ -426,6 +433,7 @@ construct_runtime!(
BridgeKovan: pallet_bridge_eth_poa::<Instance2>::{Module, Call, Config, Storage, ValidateUnsigned},
BridgeRialtoCurrencyExchange: pallet_bridge_currency_exchange::<Instance1>::{Module, Call},
BridgeKovanCurrencyExchange: pallet_bridge_currency_exchange::<Instance2>::{Module, Call},
BridgeMillau: pallet_substrate_bridge::{Module, Call, Storage},
BridgeCallDispatch: pallet_bridge_call_dispatch::{Module, Event<T>},
System: frame_system::{Module, Call, Config, Storage, Event<T>},
RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage},
Expand Down Expand Up @@ -562,33 +570,45 @@ impl_runtime_apis! {
}
}

impl bp_currency_exchange::RialtoCurrencyExchangeApi<Block, exchange::EthereumTransactionInclusionProof> for Runtime {
fn filter_transaction_proof(proof: exchange::EthereumTransactionInclusionProof) -> bool {
BridgeRialtoCurrencyExchange::filter_transaction_proof(&proof)
}
}

impl bp_currency_exchange::KovanCurrencyExchangeApi<Block, exchange::EthereumTransactionInclusionProof> for Runtime {
fn filter_transaction_proof(proof: exchange::EthereumTransactionInclusionProof) -> bool {
BridgeKovanCurrencyExchange::filter_transaction_proof(&proof)
}
}

impl bp_millau::MillauHeaderApi<Block> for Runtime {
fn best_block() -> (bp_millau::BlockNumber, bp_millau::Hash) {
unimplemented!("https://github.com/paritytech/parity-bridges-common/issues/368")
let header = BridgeMillau::best_header();
(header.number, header.hash())
}

fn finalized_block() -> (bp_millau::BlockNumber, bp_millau::Hash) {
unimplemented!("https://github.com/paritytech/parity-bridges-common/issues/368")
let header = BridgeMillau::best_finalized();
(header.number, header.hash())
}

fn incomplete_headers() -> Vec<(bp_millau::BlockNumber, bp_millau::Hash)> {
unimplemented!("https://github.com/paritytech/parity-bridges-common/issues/368")
// Since the pallet doesn't accept multiple scheduled changes right now
// we can only have one header requiring a justification at any time.
if let Some(header) = BridgeMillau::requires_justification() {
vec![(header.number, header.hash())]
} else {
vec![]
}
}

fn is_known_block(_hash: bp_millau::Hash) -> bool {
unimplemented!("https://github.com/paritytech/parity-bridges-common/issues/368")
fn is_known_block(hash: bp_millau::Hash) -> bool {
BridgeMillau::is_known_header(hash)
}

fn is_finalized_block(hash: bp_millau::Hash) -> bool {
BridgeMillau::is_finalized_header(hash)
}
}

impl bp_currency_exchange::RialtoCurrencyExchangeApi<Block, exchange::EthereumTransactionInclusionProof> for Runtime {
fn filter_transaction_proof(proof: exchange::EthereumTransactionInclusionProof) -> bool {
BridgeRialtoCurrencyExchange::filter_transaction_proof(&proof)
}
}

impl bp_currency_exchange::KovanCurrencyExchangeApi<Block, exchange::EthereumTransactionInclusionProof> for Runtime {
fn filter_transaction_proof(proof: exchange::EthereumTransactionInclusionProof) -> bool {
BridgeKovanCurrencyExchange::filter_transaction_proof(&proof)
}
}

Expand Down
97 changes: 97 additions & 0 deletions bridges/modules/substrate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,15 @@ pub trait Trait: frame_system::Trait {

decl_storage! {
trait Store for Module<T: Trait> as SubstrateBridge {
/// Hash of the header at the highest known height.
BestHeader: T::BridgedBlockHash;
/// Hash of the best finalized header.
BestFinalized: T::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<T::BridgedBlockHash>;
/// Headers which have been imported into the pallet.
ImportedHeaders: map hasher(identity) T::BridgedBlockHash => Option<ImportedHeader<T::BridgedHeader>>;
/// The current Grandpa Authority set.
Expand All @@ -136,6 +143,7 @@ decl_storage! {
.clone()
.expect("An initial header is needed");

<BestHeader<T>>::put(initial_header.hash());
<BestFinalized<T>>::put(initial_header.hash());
<ImportedHeaders<T>>::insert(
initial_header.hash(),
Expand Down Expand Up @@ -223,6 +231,58 @@ decl_module! {
}
}

impl<T: Trait> Module<T> {
/// 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() -> T::BridgedHeader {
PalletStorage::<T>::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() -> T::BridgedHeader {
PalletStorage::<T>::new().best_finalized_header().header
}

/// Check if a particular header is known to the bridge pallet.
pub fn is_known_header(hash: T::BridgedBlockHash) -> bool {
PalletStorage::<T>::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: T::BridgedBlockHash) -> bool {
let storage = PalletStorage::<T>::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<T::BridgedHeader> {
let storage = PalletStorage::<T>::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 {
Expand All @@ -232,6 +292,12 @@ pub trait BridgeStorage {
/// Write a header to storage.
fn write_header(&mut self, header: &ImportedHeader<Self::Header>);

/// Get the header at the highest known height.
fn best_header(&self) -> ImportedHeader<Self::Header>;

/// Update the header at the highest height.
fn update_best_header(&mut self, hash: <Self::Header as HeaderT>::Hash);

/// Get the best finalized header the pallet knows of.
fn best_finalized_header(&self) -> ImportedHeader<Self::Header>;

Expand All @@ -241,6 +307,15 @@ pub trait BridgeStorage {
/// Check if a particular header is known to the pallet.
fn header_exists(&self, hash: <Self::Header as HeaderT>::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<<Self::Header as HeaderT>::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<<Self::Header as HeaderT>::Hash>);

/// Get a specific header by its hash.
///
/// Returns None if it is not known to the pallet.
Expand Down Expand Up @@ -284,6 +359,16 @@ impl<T: Trait> BridgeStorage for PalletStorage<T> {
<ImportedHeaders<T>>::insert(hash, header);
}

fn best_header(&self) -> ImportedHeader<Self::Header> {
let hash = <BestHeader<T>>::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: T::BridgedBlockHash) {
<BestHeader<T>>::put(hash)
}

fn best_finalized_header(&self) -> ImportedHeader<T::BridgedHeader> {
let hash = <BestFinalized<T>>::get();
self.header_by_hash(hash)
Expand All @@ -302,6 +387,18 @@ impl<T: Trait> BridgeStorage for PalletStorage<T> {
<ImportedHeaders<T>>::get(hash)
}

fn unfinalized_header(&self) -> Option<T::BridgedBlockHash> {
<RequiresJustification<T>>::get()
}

fn update_unfinalized_header(&mut self, hash: Option<<Self::Header as HeaderT>::Hash>) {
if let Some(hash) = hash {
<RequiresJustification<T>>::put(hash);
} else {
<RequiresJustification<T>>::kill();
}
}

fn current_authority_set(&self) -> AuthoritySet {
CurrentAuthoritySet::get()
}
Expand Down
Loading

0 comments on commit 86834e2

Please sign in to comment.