mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 22:11:02 +00:00
Parachains finality pallet (#1068)
* parachains finality * parachains pallet test * demo of how to configure GRANDPA pallet instance * allow instances in parachains pallet * spellcheck * TODO + fix * fmt * removed invalid storage_keys file * change all hashers to Blake2_128Concat * use Twox64Concat for insertion position * fix build * fix compilation * change ParaId and ParaHead types * TODOs -> TODOs with issues refs
This commit is contained in:
committed by
Bastian Köcher
parent
f2a8ca1d3e
commit
f54bd6c779
@@ -284,7 +284,7 @@ pub mod pallet {
|
||||
|
||||
/// Headers which have been imported into the pallet.
|
||||
#[pallet::storage]
|
||||
pub(super) type ImportedHeaders<T: Config<I>, I: 'static = ()> =
|
||||
pub type ImportedHeaders<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Identity, BridgedBlockHash<T, I>, BridgedHeader<T, I>>;
|
||||
|
||||
/// The current GRANDPA Authority set.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
[package]
|
||||
name = "pallet-bridge-parachains"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
|
||||
log = { version = "0.4.14", default-features = false }
|
||||
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
|
||||
serde = { version = "1.0.101", optional = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
|
||||
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
|
||||
bp-runtime = { path = "../../primitives/runtime", default-features = false }
|
||||
pallet-bridge-grandpa = { path = "../grandpa", default-features = false }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
bp-test-utils = { path = "../../primitives/test-utils" }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-polkadot-core/std",
|
||||
"bp-runtime/std",
|
||||
"codec/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"log/std",
|
||||
"pallet-bridge-grandpa/std",
|
||||
"scale-info/std",
|
||||
"serde",
|
||||
"sp-core/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"sp-trie/std",
|
||||
]
|
||||
@@ -0,0 +1,601 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Parachains finality module.
|
||||
//!
|
||||
//! This module needs to be deployed with GRANDPA module, which is syncing relay
|
||||
//! chain blocks. The main entry point of this module is `submit_parachain_heads`, which
|
||||
//! accepts storage proof of some parachain `Heads` entries from bridged relay chain.
|
||||
//! It requires corresponding relay headers to be already synced.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaId, ParachainHeadsProof};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::RuntimeDebug;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
// Re-export in crate namespace for `construct_runtime!`.
|
||||
pub use pallet::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
/// Block hash of the bridged relay chain.
|
||||
pub type RelayBlockHash = bp_polkadot_core::Hash;
|
||||
/// Block number of the bridged relay chain.
|
||||
pub type RelayBlockNumber = bp_polkadot_core::BlockNumber;
|
||||
/// Hasher of the bridged relay chain.
|
||||
pub type RelayBlockHasher = bp_polkadot_core::Hasher;
|
||||
|
||||
/// Best known parachain head as it is stored in the runtime storage.
|
||||
#[derive(Decode, Encode, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct BestParaHead {
|
||||
/// Number of relay block where this head has been updated.
|
||||
pub at_relay_block_number: RelayBlockNumber,
|
||||
/// Hash of parachain head.
|
||||
pub head_hash: ParaHash,
|
||||
/// Current ring buffer position for this parachain.
|
||||
pub next_imported_hash_position: u32,
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// Relay chain block is unknown to us.
|
||||
UnknownRelayChainBlock,
|
||||
/// Invalid storage proof has been passed.
|
||||
InvalidStorageProof,
|
||||
}
|
||||
|
||||
#[pallet::config]
|
||||
#[pallet::disable_frame_system_supertrait_check]
|
||||
pub trait Config<I: 'static = ()>:
|
||||
pallet_bridge_grandpa::Config<Self::BridgesGrandpaPalletInstance>
|
||||
{
|
||||
/// Instance of bridges GRANDPA pallet that this pallet is linked to.
|
||||
///
|
||||
/// The GRANDPA pallet instance must be configured to import headers of relay chain that
|
||||
/// we're interested in.
|
||||
type BridgesGrandpaPalletInstance: 'static;
|
||||
|
||||
/// Maximal number of single parachain heads to keep in the storage.
|
||||
///
|
||||
/// The setting is there to prevent growing the on-chain state indefinitely. Note
|
||||
/// the setting does not relate to parachain block numbers - we will simply keep as much
|
||||
/// items in the storage, so it doesn't guarantee any fixed timeframe for heads.
|
||||
#[pallet::constant]
|
||||
type HeadsToKeep: Get<u32>;
|
||||
}
|
||||
|
||||
/// Best parachain heads.
|
||||
#[pallet::storage]
|
||||
pub type BestParaHeads<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Blake2_128Concat, ParaId, BestParaHead>;
|
||||
|
||||
/// Parachain heads which have been imported into the pallet.
|
||||
#[pallet::storage]
|
||||
pub type ImportedParaHeads<T: Config<I>, I: 'static = ()> =
|
||||
StorageDoubleMap<_, Blake2_128Concat, ParaId, Blake2_128Concat, ParaHash, ParaHead>;
|
||||
|
||||
/// A ring buffer of imported parachain head hashes. Ordered by the insertion time.
|
||||
#[pallet::storage]
|
||||
pub(super) type ImportedParaHashes<T: Config<I>, I: 'static = ()> =
|
||||
StorageDoubleMap<_, Blake2_128Concat, ParaId, Twox64Concat, u32, ParaHash>;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I>
|
||||
where
|
||||
<T as pallet_bridge_grandpa::Config<T::BridgesGrandpaPalletInstance>>::BridgedChain:
|
||||
bp_runtime::Chain<
|
||||
BlockNumber = RelayBlockNumber,
|
||||
Hash = RelayBlockHash,
|
||||
Hasher = RelayBlockHasher,
|
||||
>,
|
||||
{
|
||||
/// Submit proof of one or several parachain heads.
|
||||
///
|
||||
/// The proof is supposed to be proof of some `Heads` entries from the
|
||||
/// `polkadot-runtime-parachains::paras` pallet instance, deployed at the bridged chain.
|
||||
/// The proof is supposed to be crafted at the `relay_header_hash` that must already be
|
||||
/// imported by corresponding GRANDPA pallet at this chain.
|
||||
#[pallet::weight(0)] // TODO: https://github.com/paritytech/parity-bridges-common/issues/1391
|
||||
pub fn submit_parachain_heads(
|
||||
_origin: OriginFor<T>,
|
||||
relay_block_hash: RelayBlockHash,
|
||||
parachains: Vec<ParaId>,
|
||||
parachain_heads_proof: ParachainHeadsProof,
|
||||
) -> DispatchResult {
|
||||
// we'll need relay chain header to verify that parachains heads are always increasing.
|
||||
let relay_block = pallet_bridge_grandpa::ImportedHeaders::<
|
||||
T,
|
||||
T::BridgesGrandpaPalletInstance,
|
||||
>::get(relay_block_hash)
|
||||
.ok_or(Error::<T, I>::UnknownRelayChainBlock)?;
|
||||
let relay_block_number = *relay_block.number();
|
||||
|
||||
// now parse storage proof and read parachain heads
|
||||
pallet_bridge_grandpa::Pallet::<T, T::BridgesGrandpaPalletInstance>::parse_finalized_storage_proof(
|
||||
relay_block_hash,
|
||||
sp_trie::StorageProof::new(parachain_heads_proof),
|
||||
move |storage| {
|
||||
for parachain in parachains {
|
||||
// TODO: https://github.com/paritytech/parity-bridges-common/issues/1393
|
||||
let parachain_head = match Pallet::<T, I>::read_parachain_head(&storage, parachain) {
|
||||
Some(parachain_head) => parachain_head,
|
||||
None => {
|
||||
log::trace!(
|
||||
target: "runtime::bridge-parachains",
|
||||
"The head of parachain {:?} has been declared, but is missing from the proof",
|
||||
parachain,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let _: Result<_, ()> = BestParaHeads::<T, I>::try_mutate(parachain, |stored_best_head| {
|
||||
*stored_best_head = Some(Pallet::<T, I>::update_parachain_head(
|
||||
parachain,
|
||||
stored_best_head.take(),
|
||||
relay_block_number,
|
||||
parachain_head,
|
||||
)?);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
.map_err(|_| Error::<T, I>::InvalidStorageProof)?;
|
||||
|
||||
// TODO: there may be parachains we are not interested in - so we only need to accept
|
||||
// intersection of `parachains-interesting-to-us` and `parachains`
|
||||
// https://github.com/paritytech/parity-bridges-common/issues/1392
|
||||
|
||||
// TODO: if some parachain is no more interesting to us, we should start pruning its
|
||||
// heads
|
||||
// https://github.com/paritytech/parity-bridges-common/issues/1392
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// Read parachain head from storage proof.
|
||||
fn read_parachain_head(
|
||||
storage: &bp_runtime::StorageProofChecker<RelayBlockHasher>,
|
||||
parachain: ParaId,
|
||||
) -> Option<ParaHead> {
|
||||
let parachain_head_key = storage_keys::parachain_head_key(parachain);
|
||||
let parachain_head = storage.read_value(parachain_head_key.0.as_ref()).ok()??;
|
||||
let parachain_head = ParaHead::decode(&mut ¶chain_head[..]).ok()?;
|
||||
Some(parachain_head)
|
||||
}
|
||||
|
||||
/// Try to update parachain head.
|
||||
fn update_parachain_head(
|
||||
parachain: ParaId,
|
||||
stored_best_head: Option<BestParaHead>,
|
||||
updated_at_relay_block_number: RelayBlockNumber,
|
||||
updated_head: ParaHead,
|
||||
) -> Result<BestParaHead, ()> {
|
||||
// check if head has been already updated at better relay chain block. Without this
|
||||
// check, we may import heads in random order
|
||||
let updated_head_hash = updated_head.hash();
|
||||
let next_imported_hash_position = match stored_best_head {
|
||||
Some(stored_best_head)
|
||||
if stored_best_head.at_relay_block_number <= updated_at_relay_block_number =>
|
||||
{
|
||||
// check if this head has already been imported before
|
||||
if updated_head_hash == stored_best_head.head_hash {
|
||||
log::trace!(
|
||||
target: "runtime::bridge-parachains",
|
||||
"The head of parachain {:?} can't be updated to {}, because it has been already updated\
|
||||
to the same value at previous relay chain block: {} < {}",
|
||||
parachain,
|
||||
updated_head_hash,
|
||||
stored_best_head.at_relay_block_number,
|
||||
updated_at_relay_block_number,
|
||||
);
|
||||
return Err(())
|
||||
}
|
||||
|
||||
stored_best_head.next_imported_hash_position
|
||||
},
|
||||
None => 0,
|
||||
Some(stored_best_head) => {
|
||||
log::trace!(
|
||||
target: "runtime::bridge-parachains",
|
||||
"The head of parachain {:?} can't be updated to {}, because it has been already updated\
|
||||
to {} at better relay chain block: {} > {}",
|
||||
parachain,
|
||||
updated_head_hash,
|
||||
stored_best_head.head_hash,
|
||||
stored_best_head.at_relay_block_number,
|
||||
updated_at_relay_block_number,
|
||||
);
|
||||
return Err(())
|
||||
},
|
||||
};
|
||||
|
||||
// insert updated best parachain head
|
||||
let head_hash_to_prune =
|
||||
ImportedParaHashes::<T, I>::try_get(parachain, next_imported_hash_position);
|
||||
let updated_best_para_head = BestParaHead {
|
||||
at_relay_block_number: updated_at_relay_block_number,
|
||||
head_hash: updated_head_hash,
|
||||
next_imported_hash_position: (next_imported_hash_position + 1) %
|
||||
T::HeadsToKeep::get(),
|
||||
};
|
||||
ImportedParaHashes::<T, I>::insert(
|
||||
parachain,
|
||||
next_imported_hash_position,
|
||||
updated_head_hash,
|
||||
);
|
||||
ImportedParaHeads::<T, I>::insert(parachain, updated_head_hash, updated_head);
|
||||
|
||||
// remove old head
|
||||
if let Ok(head_hash_to_prune) = head_hash_to_prune {
|
||||
log::trace!(
|
||||
target: "runtime::bridge-parachains",
|
||||
"Pruning old head of parachain {:?}: {}",
|
||||
parachain,
|
||||
head_hash_to_prune,
|
||||
);
|
||||
ImportedParaHeads::<T, I>::remove(parachain, head_hash_to_prune);
|
||||
}
|
||||
|
||||
Ok(updated_best_para_head)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod storage_keys {
|
||||
use super::*;
|
||||
use bp_runtime::storage_map_final_key;
|
||||
use frame_support::Twox64Concat;
|
||||
use sp_core::storage::StorageKey;
|
||||
|
||||
/// Storage key of the parachain head in the runtime storage of relay chain.
|
||||
pub fn parachain_head_key(parachain: ParaId) -> StorageKey {
|
||||
storage_map_final_key::<Twox64Concat>("Paras", "Heads", ¶chain.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{run_test, test_relay_header, Origin, TestRuntime};
|
||||
|
||||
use bp_test_utils::{authority_list, make_default_justification};
|
||||
use frame_support::{assert_noop, assert_ok, traits::OnInitialize};
|
||||
use sp_trie::{
|
||||
record_all_keys, trie_types::TrieDBMutV1, LayoutV1, MemoryDB, Recorder, TrieMut,
|
||||
};
|
||||
|
||||
type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1;
|
||||
|
||||
fn initialize(state_root: RelayBlockHash) {
|
||||
pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::initialize(
|
||||
Origin::root(),
|
||||
bp_header_chain::InitializationData {
|
||||
header: Box::new(test_relay_header(0, state_root)),
|
||||
authority_list: authority_list(),
|
||||
set_id: 1,
|
||||
is_halted: false,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn proceed(num: RelayBlockNumber, state_root: RelayBlockHash) {
|
||||
pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::on_initialize(
|
||||
0,
|
||||
);
|
||||
|
||||
let header = test_relay_header(num, state_root);
|
||||
let justification = make_default_justification(&header);
|
||||
assert_ok!(
|
||||
pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof(
|
||||
Origin::signed(1),
|
||||
Box::new(header),
|
||||
justification,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_parachain_heads_proof(
|
||||
heads: Vec<(ParaId, ParaHead)>,
|
||||
) -> (RelayBlockHash, ParachainHeadsProof) {
|
||||
let mut root = Default::default();
|
||||
let mut mdb = MemoryDB::default();
|
||||
{
|
||||
let mut trie = TrieDBMutV1::<RelayBlockHasher>::new(&mut mdb, &mut root);
|
||||
for (parachain, head) in heads {
|
||||
let storage_key = storage_keys::parachain_head_key(parachain);
|
||||
trie.insert(&storage_key.0, &head.encode())
|
||||
.map_err(|_| "TrieMut::insert has failed")
|
||||
.expect("TrieMut::insert should not fail in tests");
|
||||
}
|
||||
}
|
||||
|
||||
// generate storage proof to be delivered to This chain
|
||||
let mut proof_recorder = Recorder::<RelayBlockHash>::new();
|
||||
record_all_keys::<LayoutV1<RelayBlockHasher>, _>(&mdb, &root, &mut proof_recorder)
|
||||
.map_err(|_| "record_all_keys has failed")
|
||||
.expect("record_all_keys should not fail in benchmarks");
|
||||
let storage_proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect();
|
||||
|
||||
(root, storage_proof)
|
||||
}
|
||||
|
||||
fn initial_best_head(parachain: u32) -> BestParaHead {
|
||||
BestParaHead {
|
||||
at_relay_block_number: 0,
|
||||
head_hash: head_data(parachain, 0).hash(),
|
||||
next_imported_hash_position: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn head_data(parachain: u32, head_number: u32) -> ParaHead {
|
||||
ParaHead((parachain, head_number).encode())
|
||||
}
|
||||
|
||||
fn head_hash(parachain: u32, head_number: u32) -> ParaHash {
|
||||
head_data(parachain, head_number).hash()
|
||||
}
|
||||
|
||||
fn import_parachain_1_head(
|
||||
relay_chain_block: RelayBlockNumber,
|
||||
relay_state_root: RelayBlockHash,
|
||||
proof: ParachainHeadsProof,
|
||||
) -> sp_runtime::DispatchResult {
|
||||
Pallet::<TestRuntime>::submit_parachain_heads(
|
||||
Origin::signed(1),
|
||||
test_relay_header(relay_chain_block, relay_state_root).hash(),
|
||||
vec![ParaId(1)],
|
||||
proof,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_initial_parachain_heads() {
|
||||
let (state_root, proof) = prepare_parachain_heads_proof(vec![
|
||||
(ParaId(1), head_data(1, 0)),
|
||||
(ParaId(3), head_data(3, 10)),
|
||||
]);
|
||||
run_test(|| {
|
||||
initialize(state_root);
|
||||
|
||||
// we're trying to update heads of parachains 1, 2 and 3
|
||||
assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
|
||||
Origin::signed(1),
|
||||
test_relay_header(0, state_root).hash(),
|
||||
vec![ParaId(1), ParaId(2), ParaId(3)],
|
||||
proof,
|
||||
),);
|
||||
|
||||
// but only 1 and 2 are updated, because proof is missing head of parachain#2
|
||||
assert_eq!(BestParaHeads::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
|
||||
assert_eq!(BestParaHeads::<TestRuntime>::get(ParaId(2)), None);
|
||||
assert_eq!(
|
||||
BestParaHeads::<TestRuntime>::get(ParaId(3)),
|
||||
Some(BestParaHead {
|
||||
at_relay_block_number: 0,
|
||||
head_hash: head_data(3, 10).hash(),
|
||||
next_imported_hash_position: 1,
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ImportedParaHeads::<TestRuntime>::get(ParaId(1), initial_best_head(1).head_hash),
|
||||
Some(head_data(1, 0))
|
||||
);
|
||||
assert_eq!(
|
||||
ImportedParaHeads::<TestRuntime>::get(ParaId(2), initial_best_head(2).head_hash),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ImportedParaHeads::<TestRuntime>::get(ParaId(3), head_hash(3, 10)),
|
||||
Some(head_data(3, 10))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_parachain_heads_is_able_to_progress() {
|
||||
let (state_root_5, proof_5) =
|
||||
prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, 5))]);
|
||||
let (state_root_10, proof_10) =
|
||||
prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, 10))]);
|
||||
run_test(|| {
|
||||
// start with relay block #0 and import head#5 of parachain#1
|
||||
initialize(state_root_5);
|
||||
assert_ok!(import_parachain_1_head(0, state_root_5, proof_5));
|
||||
assert_eq!(
|
||||
BestParaHeads::<TestRuntime>::get(ParaId(1)),
|
||||
Some(BestParaHead {
|
||||
at_relay_block_number: 0,
|
||||
head_hash: head_data(1, 5).hash(),
|
||||
next_imported_hash_position: 1,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash()),
|
||||
Some(head_data(1, 5))
|
||||
);
|
||||
assert_eq!(
|
||||
ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash()),
|
||||
None
|
||||
);
|
||||
|
||||
// import head#10 of parachain#1 at relay block #1
|
||||
proceed(1, state_root_10);
|
||||
assert_ok!(import_parachain_1_head(1, state_root_10, proof_10));
|
||||
assert_eq!(
|
||||
BestParaHeads::<TestRuntime>::get(ParaId(1)),
|
||||
Some(BestParaHead {
|
||||
at_relay_block_number: 1,
|
||||
head_hash: head_data(1, 10).hash(),
|
||||
next_imported_hash_position: 2,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash()),
|
||||
Some(head_data(1, 5))
|
||||
);
|
||||
assert_eq!(
|
||||
ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash()),
|
||||
Some(head_data(1, 10))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_nothing_when_already_imported_this_head_at_previous_relay_header() {
|
||||
let (state_root, proof) = prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, 0))]);
|
||||
run_test(|| {
|
||||
// import head#0 of parachain#1 at relay block#0
|
||||
initialize(state_root);
|
||||
assert_ok!(import_parachain_1_head(0, state_root, proof.clone()));
|
||||
assert_eq!(BestParaHeads::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
|
||||
|
||||
// try to import head#0 of parachain#1 at relay block#1
|
||||
// => call succeeds, but nothing is changed
|
||||
proceed(1, state_root);
|
||||
assert_ok!(import_parachain_1_head(1, state_root, proof));
|
||||
assert_eq!(BestParaHeads::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_nothing_when_already_imported_head_at_better_relay_header() {
|
||||
let (state_root_5, proof_5) =
|
||||
prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, 5))]);
|
||||
let (state_root_10, proof_10) =
|
||||
prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, 10))]);
|
||||
run_test(|| {
|
||||
// start with relay block #0
|
||||
initialize(state_root_5);
|
||||
|
||||
// head#10 of parachain#1 at relay block#1
|
||||
proceed(1, state_root_10);
|
||||
assert_ok!(import_parachain_1_head(1, state_root_10, proof_10));
|
||||
assert_eq!(
|
||||
BestParaHeads::<TestRuntime>::get(ParaId(1)),
|
||||
Some(BestParaHead {
|
||||
at_relay_block_number: 1,
|
||||
head_hash: head_data(1, 10).hash(),
|
||||
next_imported_hash_position: 1,
|
||||
})
|
||||
);
|
||||
|
||||
// now try to import head#1 at relay block#0
|
||||
// => nothing is changed, because better head has already been imported
|
||||
assert_ok!(import_parachain_1_head(0, state_root_5, proof_5));
|
||||
assert_eq!(
|
||||
BestParaHeads::<TestRuntime>::get(ParaId(1)),
|
||||
Some(BestParaHead {
|
||||
at_relay_block_number: 1,
|
||||
head_hash: head_data(1, 10).hash(),
|
||||
next_imported_hash_position: 1,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prunes_old_heads() {
|
||||
run_test(|| {
|
||||
let heads_to_keep = crate::mock::HeadsToKeep::get();
|
||||
|
||||
// import exactly `HeadsToKeep` headers
|
||||
for i in 0..heads_to_keep {
|
||||
let (state_root, proof) =
|
||||
prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, i))]);
|
||||
if i == 0 {
|
||||
initialize(state_root);
|
||||
} else {
|
||||
proceed(i, state_root);
|
||||
}
|
||||
assert_ok!(import_parachain_1_head(i, state_root, proof));
|
||||
}
|
||||
|
||||
// nothing is pruned yet
|
||||
for i in 0..heads_to_keep {
|
||||
assert!(ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, i).hash())
|
||||
.is_some());
|
||||
}
|
||||
|
||||
// import next relay chain header and next parachain head
|
||||
let (state_root, proof) =
|
||||
prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, heads_to_keep))]);
|
||||
proceed(heads_to_keep, state_root);
|
||||
assert_ok!(import_parachain_1_head(heads_to_keep, state_root, proof));
|
||||
|
||||
// and the head#0 is pruned
|
||||
assert!(
|
||||
ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 0).hash()).is_none()
|
||||
);
|
||||
for i in 1..=heads_to_keep {
|
||||
assert!(ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, i).hash())
|
||||
.is_some());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_on_unknown_relay_chain_block() {
|
||||
let (state_root, proof) = prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, 5))]);
|
||||
run_test(|| {
|
||||
// start with relay block #0
|
||||
initialize(state_root);
|
||||
|
||||
// try to import head#5 of parachain#1 at unknown relay chain block #1
|
||||
assert_noop!(
|
||||
import_parachain_1_head(1, state_root, proof),
|
||||
Error::<TestRuntime>::UnknownRelayChainBlock
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_on_invalid_storage_proof() {
|
||||
let (_state_root, proof) =
|
||||
prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, 5))]);
|
||||
run_test(|| {
|
||||
// start with relay block #0
|
||||
initialize(Default::default());
|
||||
|
||||
// try to import head#5 of parachain#1 at relay chain block #0
|
||||
assert_noop!(
|
||||
import_parachain_1_head(0, Default::default(), proof),
|
||||
Error::<TestRuntime>::InvalidStorageProof
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
use bp_runtime::Chain;
|
||||
use frame_support::{construct_runtime, parameter_types, weights::Weight};
|
||||
use sp_runtime::{
|
||||
testing::{Header, H256},
|
||||
traits::{BlakeTwo256, Header as HeaderT, IdentityLookup},
|
||||
Perbill,
|
||||
};
|
||||
|
||||
use crate as pallet_bridge_parachains;
|
||||
|
||||
pub type AccountId = u64;
|
||||
pub type TestNumber = u64;
|
||||
|
||||
pub type RelayBlockHeader =
|
||||
sp_runtime::generic::Header<crate::RelayBlockNumber, crate::RelayBlockHasher>;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
||||
|
||||
construct_runtime! {
|
||||
pub enum TestRuntime where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
|
||||
Grandpa1: pallet_bridge_grandpa::<Instance1>::{Pallet},
|
||||
Grandpa2: pallet_bridge_grandpa::<Instance2>::{Pallet},
|
||||
Parachains: pallet_bridge_parachains::{Pallet},
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: TestNumber = 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 = Call;
|
||||
type BlockNumber = TestNumber;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type SystemWeightInfo = ();
|
||||
type DbWeight = ();
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxRequests: u32 = 2;
|
||||
pub const HeadersToKeep: u32 = 5;
|
||||
pub const SessionLength: u64 = 5;
|
||||
pub const NumValidators: u32 = 5;
|
||||
}
|
||||
|
||||
impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance1> for TestRuntime {
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type MaxRequests = MaxRequests;
|
||||
type HeadersToKeep = HeadersToKeep;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance2> for TestRuntime {
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type MaxRequests = MaxRequests;
|
||||
type HeadersToKeep = HeadersToKeep;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const HeadsToKeep: u32 = 4;
|
||||
}
|
||||
|
||||
impl pallet_bridge_parachains::Config for TestRuntime {
|
||||
type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1;
|
||||
type HeadsToKeep = HeadsToKeep;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBridgedChain;
|
||||
|
||||
impl Chain for TestBridgedChain {
|
||||
type BlockNumber = crate::RelayBlockNumber;
|
||||
type Hash = crate::RelayBlockHash;
|
||||
type Hasher = crate::RelayBlockHasher;
|
||||
type Header = RelayBlockHeader;
|
||||
|
||||
type AccountId = AccountId;
|
||||
type Balance = u32;
|
||||
type Index = u32;
|
||||
type Signature = sp_runtime::testing::TestSignature;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OtherBridgedChain;
|
||||
|
||||
impl Chain for OtherBridgedChain {
|
||||
type BlockNumber = u64;
|
||||
type Hash = crate::RelayBlockHash;
|
||||
type Hasher = crate::RelayBlockHasher;
|
||||
type Header = sp_runtime::generic::Header<u64, crate::RelayBlockHasher>;
|
||||
|
||||
type AccountId = AccountId;
|
||||
type Balance = u32;
|
||||
type Index = u32;
|
||||
type Signature = sp_runtime::testing::TestSignature;
|
||||
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(test)
|
||||
}
|
||||
|
||||
pub fn test_relay_header(
|
||||
num: crate::RelayBlockNumber,
|
||||
state_root: crate::RelayBlockHash,
|
||||
) -> RelayBlockHeader {
|
||||
RelayBlockHeader::new(
|
||||
num,
|
||||
Default::default(),
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
@@ -8,7 +8,9 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
parity-scale-codec = { version = "3.0.0", default-features = false, features = ["derive"] }
|
||||
parity-util-mem = { version = "0.11.0", optional = true }
|
||||
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
|
||||
# Bridge Dependencies
|
||||
|
||||
@@ -36,7 +38,9 @@ std = [
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"parity-scale-codec/std",
|
||||
"parity-util-mem",
|
||||
"scale-info/std",
|
||||
"serde",
|
||||
"sp-api/std",
|
||||
"sp-core/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
@@ -43,6 +43,8 @@ use sp_std::prelude::Vec;
|
||||
pub use frame_support::{weights::constants::ExtrinsicBaseWeight, Parameter};
|
||||
pub use sp_runtime::{traits::Convert, Perbill};
|
||||
|
||||
pub mod parachains;
|
||||
|
||||
/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at
|
||||
/// Polkadot-like chain. This mostly depends on number of entries in the storage trie.
|
||||
/// Some reserve is reserved to account future chain growth.
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitives of polkadot-like chains, that are related to parachains functionality.
|
||||
//!
|
||||
//! Even though this (bridges) repository references polkadot repository, we can't
|
||||
//! reference polkadot crates from pallets. That's because bridges repository is
|
||||
//! included in the polkadot repository and included pallets are used by polkadot
|
||||
//! chains. Having pallets that are referencing polkadot, would mean that there may
|
||||
//! be two versions of polkadot crates included in the runtime. Which is bad.
|
||||
|
||||
use frame_support::RuntimeDebug;
|
||||
use parity_scale_codec::{CompactAs, Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::Hasher;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use parity_util_mem::MallocSizeOf;
|
||||
|
||||
/// Parachain id.
|
||||
///
|
||||
/// This is an equivalent of the `polkadot_parachain::Id`, which is a compact-encoded `u32`.
|
||||
#[derive(
|
||||
Clone,
|
||||
CompactAs,
|
||||
Copy,
|
||||
Decode,
|
||||
Default,
|
||||
Encode,
|
||||
Eq,
|
||||
Hash,
|
||||
MaxEncodedLen,
|
||||
Ord,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub struct ParaId(pub u32);
|
||||
|
||||
/// Parachain head.
|
||||
///
|
||||
/// This is an equivalent of the `polkadot_parachain::HeadData`.
|
||||
///
|
||||
/// The parachain head means (at least in Cumulus) a SCALE-encoded parachain header. Keep in mind
|
||||
/// that in Polkadot it is twice-encoded (so `header.encode().encode()`). We'll also do it to keep
|
||||
/// it binary-compatible (implies hash-compatibility) with other parachain pallets.
|
||||
#[derive(
|
||||
PartialEq, Eq, Clone, PartialOrd, Ord, Encode, Decode, RuntimeDebug, TypeInfo, Default,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash, MallocSizeOf))]
|
||||
pub struct ParaHead(pub Vec<u8>);
|
||||
|
||||
impl ParaHead {
|
||||
/// Returns the hash of this head data.
|
||||
pub fn hash(&self) -> crate::Hash {
|
||||
sp_runtime::traits::BlakeTwo256::hash(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parachain head hash.
|
||||
pub type ParaHash = crate::Hash;
|
||||
|
||||
/// Raw storage proof of parachain heads, stored in polkadot-like chain runtime.
|
||||
pub type ParachainHeadsProof = Vec<Vec<u8>>;
|
||||
Reference in New Issue
Block a user