From da67430b40c7032f4f7fda44dd991090eed73cac Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 22 Jul 2021 15:47:59 +0300 Subject: [PATCH 01/14] parachains finality --- Cargo.lock | 20 ++ modules/grandpa/src/lib.rs | 2 +- modules/messages/src/lib.rs | 25 +-- modules/parachains/Cargo.toml | 46 +++++ modules/parachains/src/lib.rs | 259 +++++++++++++++++++++++++ modules/parachains/src/mock.rs | 120 ++++++++++++ primitives/runtime/src/lib.rs | 1 + primitives/runtime/src/storage_keys.rs | 44 +++++ 8 files changed, 496 insertions(+), 21 deletions(-) create mode 100644 modules/parachains/Cargo.toml create mode 100644 modules/parachains/src/lib.rs create mode 100644 modules/parachains/src/mock.rs create mode 100644 primitives/runtime/src/storage_keys.rs diff --git a/Cargo.lock b/Cargo.lock index dc40b8003d..992ab295c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4986,6 +4986,26 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-bridge-parachains" +version = "0.1.0" +dependencies = [ + "bp-header-chain", + "bp-runtime", + "bp-test-utils", + "frame-support", + "frame-system", + "log", + "pallet-bridge-grandpa", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-trie", +] + [[package]] name = "pallet-grandpa" version = "3.1.0" diff --git a/modules/grandpa/src/lib.rs b/modules/grandpa/src/lib.rs index 4cca1c7827..615126dde8 100644 --- a/modules/grandpa/src/lib.rs +++ b/modules/grandpa/src/lib.rs @@ -269,7 +269,7 @@ pub mod pallet { /// Headers which have been imported into the pallet. #[pallet::storage] - pub(super) type ImportedHeaders, I: 'static = ()> = + pub type ImportedHeaders, I: 'static = ()> = StorageMap<_, Identity, BridgedBlockHash, BridgedHeader>; /// The current GRANDPA Authority set. diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 12d8ab342b..52ee23ea44 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -841,38 +841,23 @@ impl, I: Instance> Pallet { /// trying to avoid here) - by using strings like "Instance2", "OutboundMessages", etc. pub mod storage_keys { use super::*; - use frame_support::{traits::Instance, StorageHasher}; + use bp_runtime::storage_keys::storage_map_final_key_with_instance; + use frame_support::traits::Instance; use sp_core::storage::StorageKey; /// Storage key of the outbound message in the runtime storage. pub fn message_key(lane: &LaneId, nonce: MessageNonce) -> StorageKey { - storage_map_final_key::("OutboundMessages", &MessageKey { lane_id: *lane, nonce }.encode()) + storage_map_final_key_with_instance::("OutboundMessages", &MessageKey { lane_id: *lane, nonce }.encode()) } /// Storage key of the outbound message lane state in the runtime storage. pub fn outbound_lane_data_key(lane: &LaneId) -> StorageKey { - storage_map_final_key::("OutboundLanes", lane) + storage_map_final_key_with_instance::("OutboundLanes", lane) } /// Storage key of the inbound message lane state in the runtime storage. pub fn inbound_lane_data_key(lane: &LaneId) -> StorageKey { - storage_map_final_key::("InboundLanes", lane) - } - - /// This is a copypaste of the `frame_support::storage::generator::StorageMap::storage_map_final_key`. - fn storage_map_final_key(map_name: &str, key: &[u8]) -> StorageKey { - let module_prefix_hashed = frame_support::Twox128::hash(I::PREFIX.as_bytes()); - let storage_prefix_hashed = frame_support::Twox128::hash(map_name.as_bytes()); - let key_hashed = frame_support::Blake2_128Concat::hash(key); - - let mut final_key = - Vec::with_capacity(module_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len()); - - final_key.extend_from_slice(&module_prefix_hashed[..]); - final_key.extend_from_slice(&storage_prefix_hashed[..]); - final_key.extend_from_slice(key_hashed.as_ref()); - - StorageKey(final_key) + storage_map_final_key_with_instance::("InboundLanes", lane) } } diff --git a/modules/parachains/Cargo.toml b/modules/parachains/Cargo.toml new file mode 100644 index 0000000000..c375878a31 --- /dev/null +++ b/modules/parachains/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "pallet-bridge-parachains" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } +log = { version = "0.4.14", default-features = false } +serde = { version = "1.0", optional = true } + +# Bridge Dependencies + +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-runtime/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-bridge-grandpa/std", + "serde", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", +] diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs new file mode 100644 index 0000000000..3c2b78ffac --- /dev/null +++ b/modules/parachains/src/lib.rs @@ -0,0 +1,259 @@ +// 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 . + +//! 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_runtime::{BlockNumberOf, HashOf, HasherOf}; +use codec::{Decode, Encode}; +use frame_support::RuntimeDebug; +use sp_runtime::traits::Header as HeaderT; + +// 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 = HashOf<::BridgedChain>; +/// Block number of the bridged relay chain. +pub type RelayBlockNumber = BlockNumberOf<::BridgedChain>; +/// Hasher of the bridged relay chain. +pub type RelayBlockHasher = HasherOf<::BridgedChain>; + +/// Parachain id. +pub type ParaId = u32; + +/// Parachain head, which is and encoded parachain header. +pub type ParaHead = Vec; + +/// Parachain heads storage proof. +pub type ParachainHeadsProof = Vec>; + +/// Parachain head as it is stored in the runtime storage. +#[derive(Decode, Encode, PartialEq, RuntimeDebug)] +pub struct StoredParaHead { + /// Number of relay block where this head has been updated. + pub at_relay_block_number: RelayBlockNumber, + /// Parachain head. + pub head: ParaHead, +} + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use super::*; + + #[pallet::error] + pub enum Error { + /// 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: pallet_bridge_grandpa::Config { + } + + /// Parachain heads. + #[pallet::storage] + pub type ParaHeads = StorageMap<_, Identity, ParaId, StoredParaHead>>; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::call] + impl Pallet { + /// 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 + pub fn submit_parachain_heads( + _origin: OriginFor, + relay_block_hash: RelayBlockHash, + parachains: Vec, + 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::::get(relay_block_hash) + .ok_or(Error::::UnknownRelayChainBlock)?; + let relay_block_number = *relay_block.number(); + + // now parse storage proof and read parachain heads + pallet_bridge_grandpa::Pallet::::parse_finalized_storage_proof( + relay_block_hash, + sp_trie::StorageProof::new(parachain_heads_proof), + move |storage| { + for parachain in parachains { + let parachain_head = match 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 _ = ParaHeads::::try_mutate(parachain, |parachain_head_data| { + match parachain_head_data { + Some(stored_data) if stored_data.at_relay_block_number <= relay_block_number => (), + None => (), + Some(stored_data) => { + log::trace!( + target: "runtime::bridge-parachains", + "The head of parachain {} can't be updated, because it has been already updated\ + at better relay chain block: {} > {}", + parachain, + stored_data.at_relay_block_number, + relay_block_number, + ); + return Err(()); + }, + } + + log::trace!( + target: "runtime::bridge-parachains", + "The head of parachain {} has been updated at relay block {}", + parachain, + relay_block_number, + ); + + *parachain_head_data = Some(StoredParaHead { + at_relay_block_number: relay_block_number, + head: parachain_head, + }); + Ok(()) + }); + } + }, + ).map_err(|_| Error::::InvalidStorageProof)?; + + // TODO: save previous heads??? pruning??? + + Ok(().into()) + } + } +} + +pub mod storage_keys { + use super::*; + use bp_runtime::storage_keys::storage_map_final_key; + 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("Heads", ¶chain.encode()) + } +} + +/// Read parachain head from storage proof. +fn read_parachain_head( + storage: &bp_runtime::StorageProofChecker>, + parachain: ParaId, +) -> Option { + let parachain_head_key = storage_keys::parachain_head_key(parachain); + let parachain_head = storage.read_value(parachain_head_key.0.as_ref()).ok()?; + parachain_head +} + +#[cfg(test)] +mod tests { + use crate::mock::{Origin, TestHash, TestHasher, TestNumber, TestRuntime, run_test, test_header}; + use super::*; + + use bp_test_utils::authority_list; + use frame_support::assert_ok; + use sp_trie::{record_all_keys, trie_types::TrieDBMut, Layout, MemoryDB, Recorder, TrieMut}; + + fn initialize(state_root: TestHash) { + pallet_bridge_grandpa::Pallet::::initialize( + Origin::root(), + bp_header_chain::InitializationData { + header: test_header(0, state_root), + authority_list: authority_list(), + set_id: 1, + is_halted: false, + }, + ).unwrap(); + } + + fn prepare_parachain_heads_proof(heads: Vec<(ParaId, ParaId)>) -> (TestHash, ParachainHeadsProof) { + let mut root = Default::default(); + let mut mdb = MemoryDB::default(); + { + let mut trie = TrieDBMut::::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::::new(); + record_all_keys::, _>(&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 stored_head(parachain: ParaId) -> StoredParaHead { + StoredParaHead { + at_relay_block_number: 0, + head: parachain.encode(), + } + } + + #[test] + fn imports_parachain_heads() { + let (state_root, proof) = prepare_parachain_heads_proof(vec![ + (1, 1), + ]); + run_test(|| { + initialize(state_root); + assert_ok!( + Pallet::::submit_parachain_heads( + Origin::signed(1), + test_header(0, state_root).hash(), + vec![1, 2], + proof, + ), + ); + + assert_eq!(ParaHeads::::get(1), Some(stored_head(1))); + assert_eq!(ParaHeads::::get(2), None); + }); + } +} diff --git a/modules/parachains/src/mock.rs b/modules/parachains/src/mock.rs new file mode 100644 index 0000000000..cb6bb2a85a --- /dev/null +++ b/modules/parachains/src/mock.rs @@ -0,0 +1,120 @@ +// 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 . + +use bp_runtime::{Chain, HeaderOf}; +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 TestHeader = HeaderOf<::BridgedChain>; +pub type TestNumber = crate::RelayBlockNumber; +pub type TestHash = crate::RelayBlockHash; +pub type TestHasher = BlakeTwo256; + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +construct_runtime! { + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Grandpa: pallet_bridge_grandpa::{Pallet}, + Parachains: pallet_bridge_parachains::{Pallet}, + } +} + +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::Config for TestRuntime { + type Origin = Origin; + type Index = u64; + type Call = 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 Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = (); + type SystemWeightInfo = (); + type DbWeight = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = (); + type OnSetCode = (); +} + +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 for TestRuntime { + type BridgedChain = TestBridgedChain; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type WeightInfo = (); +} + +impl pallet_bridge_parachains::Config for TestRuntime { +} + +#[derive(Debug)] +pub struct TestBridgedChain; + +impl Chain for TestBridgedChain { + type BlockNumber = ::BlockNumber; + type Hash = ::Hash; + type Hasher = ::Hashing; + type Header = ::Header; +} + +pub fn run_test(test: impl FnOnce() -> T) -> T { + sp_io::TestExternalities::new(Default::default()).execute_with(test) +} + +pub fn test_header(num: TestNumber, state_root: TestHash) -> TestHeader { + TestHeader::new( + num, + Default::default(), + state_root, + Default::default(), + Default::default(), + ) +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 7b17a2947f..dfcb95c7f5 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -30,6 +30,7 @@ pub use storage_proof::{Error as StorageProofError, StorageProofChecker}; pub use storage_proof::craft_valid_storage_proof; pub mod messages; +pub mod storage_keys; mod chain; mod storage_proof; diff --git a/primitives/runtime/src/storage_keys.rs b/primitives/runtime/src/storage_keys.rs new file mode 100644 index 0000000000..85872e60b1 --- /dev/null +++ b/primitives/runtime/src/storage_keys.rs @@ -0,0 +1,44 @@ +// 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 . + +use frame_support::{traits::Instance, StorageHasher}; +use sp_core::storage::StorageKey; +use sp_std::prelude::*; + +/// Key of the entry in the storage map, that is a part of pallet without instances. +pub fn storage_map_final_key(map_name: &str, key: &[u8]) -> StorageKey { + storage_map_final_key_with_prefix("", map_name, key) +} + +/// Key of the entry in the storage map, that is a part of pallet without instances. +pub fn storage_map_final_key_with_instance(map_name: &str, key: &[u8]) -> StorageKey { + storage_map_final_key_with_prefix(I::PREFIX, map_name, key) +} + +fn storage_map_final_key_with_prefix(module_prefix: &str, map_name: &str, key: &[u8]) -> StorageKey { + let module_prefix_hashed = frame_support::Twox128::hash(module_prefix.as_bytes()); + let storage_prefix_hashed = frame_support::Twox128::hash(map_name.as_bytes()); + let key_hashed = frame_support::Blake2_128Concat::hash(key); + + let mut final_key = + Vec::with_capacity(module_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len()); + + final_key.extend_from_slice(&module_prefix_hashed[..]); + final_key.extend_from_slice(&storage_prefix_hashed[..]); + final_key.extend_from_slice(key_hashed.as_ref()); + + StorageKey(final_key) +} From a98aecb4ece7ad0ae2f5329410f7e08707008537 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 23 Jul 2021 13:49:34 +0300 Subject: [PATCH 02/14] parachains pallet test --- Cargo.lock | 1 + modules/parachains/Cargo.toml | 2 + modules/parachains/src/lib.rs | 459 +++++++++++++++++---- modules/parachains/src/mock.rs | 30 +- primitives/polkadot-core/src/lib.rs | 2 + primitives/polkadot-core/src/parachains.rs | 43 ++ primitives/runtime/src/storage_keys.rs | 3 +- 7 files changed, 434 insertions(+), 106 deletions(-) create mode 100644 primitives/polkadot-core/src/parachains.rs diff --git a/Cargo.lock b/Cargo.lock index 992ab295c3..57fc943250 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4991,6 +4991,7 @@ name = "pallet-bridge-parachains" version = "0.1.0" dependencies = [ "bp-header-chain", + "bp-polkadot-core", "bp-runtime", "bp-test-utils", "frame-support", diff --git a/modules/parachains/Cargo.toml b/modules/parachains/Cargo.toml index c375878a31..2be1eb4aa0 100644 --- a/modules/parachains/Cargo.toml +++ b/modules/parachains/Cargo.toml @@ -12,6 +12,7 @@ serde = { version = "1.0", 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 } @@ -32,6 +33,7 @@ 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", diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index 3c2b78ffac..e493907f5c 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -23,7 +23,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use bp_runtime::{BlockNumberOf, HashOf, HasherOf}; +use bp_polkadot_core::parachains::{parachain_head_hash, ParaHash, ParaHead, ParaId, ParachainHeadsProof}; use codec::{Decode, Encode}; use frame_support::RuntimeDebug; use sp_runtime::traits::Header as HeaderT; @@ -35,35 +35,28 @@ pub use pallet::*; mod mock; /// Block hash of the bridged relay chain. -pub type RelayBlockHash = HashOf<::BridgedChain>; +pub type RelayBlockHash = bp_polkadot_core::Hash; /// Block number of the bridged relay chain. -pub type RelayBlockNumber = BlockNumberOf<::BridgedChain>; +pub type RelayBlockNumber = bp_polkadot_core::BlockNumber; /// Hasher of the bridged relay chain. -pub type RelayBlockHasher = HasherOf<::BridgedChain>; +pub type RelayBlockHasher = bp_polkadot_core::Hasher; -/// Parachain id. -pub type ParaId = u32; - -/// Parachain head, which is and encoded parachain header. -pub type ParaHead = Vec; - -/// Parachain heads storage proof. -pub type ParachainHeadsProof = Vec>; - -/// Parachain head as it is stored in the runtime storage. +/// Best known parachain head as it is stored in the runtime storage. #[derive(Decode, Encode, PartialEq, RuntimeDebug)] -pub struct StoredParaHead { +pub struct BestParaHead { /// Number of relay block where this head has been updated. pub at_relay_block_number: RelayBlockNumber, - /// Parachain head. - pub head: ParaHead, + /// 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::*; - use super::*; #[pallet::error] pub enum Error { @@ -76,17 +69,36 @@ pub mod pallet { #[pallet::config] #[pallet::disable_frame_system_supertrait_check] pub trait Config: pallet_bridge_grandpa::Config { + /// 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; } - /// Parachain heads. + /// Best parachain heads. #[pallet::storage] - pub type ParaHeads = StorageMap<_, Identity, ParaId, StoredParaHead>>; + pub type BestParaHeads = StorageMap<_, Identity, ParaId, BestParaHead>; + + /// Parachain heads which have been imported into the pallet. + #[pallet::storage] + pub type ImportedParaHeads = StorageDoubleMap<_, Identity, ParaId, Identity, ParaHash, ParaHead>; + + /// A ring buffer of imported parachain head hashes. Ordered by the insertion time. + #[pallet::storage] + pub(super) type ImportedParaHashes = StorageDoubleMap<_, Identity, ParaId, Identity, u32, ParaHash>; #[pallet::pallet] pub struct Pallet(PhantomData); #[pallet::call] - impl Pallet { + impl Pallet + where + ::BridgedChain: + bp_runtime::Chain, + { /// Submit proof of one or several parachain heads. /// /// The proof is supposed to be proof of some `Heads` entries from the @@ -96,7 +108,7 @@ pub mod pallet { #[pallet::weight(0)] // TODO pub fn submit_parachain_heads( _origin: OriginFor, - relay_block_hash: RelayBlockHash, + relay_block_hash: RelayBlockHash, parachains: Vec, parachain_heads_proof: ParachainHeadsProof, ) -> DispatchResult { @@ -111,7 +123,7 @@ pub mod pallet { sp_trie::StorageProof::new(parachain_heads_proof), move |storage| { for parachain in parachains { - let parachain_head = match read_parachain_head::(&storage, parachain) { + let parachain_head = match Pallet::::read_parachain_head(&storage, parachain) { Some(parachain_head) => parachain_head, None => { log::trace!( @@ -119,47 +131,105 @@ pub mod pallet { "The head of parachain {} has been declared, but is missing from the proof", parachain, ); - continue - }, - }; - - let _ = ParaHeads::::try_mutate(parachain, |parachain_head_data| { - match parachain_head_data { - Some(stored_data) if stored_data.at_relay_block_number <= relay_block_number => (), - None => (), - Some(stored_data) => { - log::trace!( - target: "runtime::bridge-parachains", - "The head of parachain {} can't be updated, because it has been already updated\ - at better relay chain block: {} > {}", - parachain, - stored_data.at_relay_block_number, - relay_block_number, - ); - return Err(()); - }, + continue; } + }; - log::trace!( - target: "runtime::bridge-parachains", - "The head of parachain {} has been updated at relay block {}", + let _: Result<_, ()> = BestParaHeads::::try_mutate(parachain, |stored_best_head| { + *stored_best_head = Some(Pallet::::update_parachain_head( parachain, + stored_best_head.take(), relay_block_number, - ); - - *parachain_head_data = Some(StoredParaHead { - at_relay_block_number: relay_block_number, - head: parachain_head, - }); + parachain_head, + )?); Ok(()) }); } }, - ).map_err(|_| Error::::InvalidStorageProof)?; + ) + .map_err(|_| Error::::InvalidStorageProof)?; + + Ok(()) + } + } + + impl Pallet { + /// Read parachain head from storage proof. + fn read_parachain_head( + storage: &bp_runtime::StorageProofChecker, + parachain: ParaId, + ) -> Option { + let parachain_head_key = storage_keys::parachain_head_key(parachain); + let parachain_head = storage.read_value(parachain_head_key.0.as_ref()).ok()?; + parachain_head + } + + /// Try to update parachain head. + fn update_parachain_head( + parachain: ParaId, + stored_best_head: Option, + updated_at_relay_block_number: RelayBlockNumber, + updated_head: ParaHead, + ) -> Result { + // 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 = parachain_head_hash(&updated_head); + 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(()); + } - // TODO: save previous heads??? pruning??? + 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::::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::::insert(parachain, next_imported_hash_position, updated_head_hash); + ImportedParaHeads::::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::::remove(parachain, head_hash_to_prune); + } - Ok(().into()) + Ok(updated_best_para_head) } } } @@ -175,53 +245,56 @@ pub mod storage_keys { } } -/// Read parachain head from storage proof. -fn read_parachain_head( - storage: &bp_runtime::StorageProofChecker>, - parachain: ParaId, -) -> Option { - let parachain_head_key = storage_keys::parachain_head_key(parachain); - let parachain_head = storage.read_value(parachain_head_key.0.as_ref()).ok()?; - parachain_head -} - #[cfg(test)] mod tests { - use crate::mock::{Origin, TestHash, TestHasher, TestNumber, TestRuntime, run_test, test_header}; use super::*; + use crate::mock::{run_test, test_relay_header, Origin, TestRuntime}; - use bp_test_utils::authority_list; - use frame_support::assert_ok; + 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::TrieDBMut, Layout, MemoryDB, Recorder, TrieMut}; - fn initialize(state_root: TestHash) { + fn initialize(state_root: RelayBlockHash) { pallet_bridge_grandpa::Pallet::::initialize( Origin::root(), bp_header_chain::InitializationData { - header: test_header(0, state_root), + header: test_relay_header(0, state_root), authority_list: authority_list(), set_id: 1, is_halted: false, }, - ).unwrap(); + ) + .unwrap(); + } + + fn proceed(num: RelayBlockNumber, state_root: RelayBlockHash) { + pallet_bridge_grandpa::Pallet::::on_initialize(0); + + let header = test_relay_header(num, state_root); + let justification = make_default_justification(&header); + assert_ok!(pallet_bridge_grandpa::Pallet::::submit_finality_proof( + Origin::signed(1), + header, + justification, + )); } - fn prepare_parachain_heads_proof(heads: Vec<(ParaId, ParaId)>) -> (TestHash, ParachainHeadsProof) { + fn prepare_parachain_heads_proof(heads: Vec<(ParaId, ParaHead)>) -> (RelayBlockHash, ParachainHeadsProof) { let mut root = Default::default(); let mut mdb = MemoryDB::default(); { - let mut trie = TrieDBMut::::new(&mut mdb, &mut root); + let mut trie = TrieDBMut::::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()) + trie.insert(&storage_key.0, &head) .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::::new(); - record_all_keys::, _>(&mdb, &root, &mut proof_recorder) + let mut proof_recorder = Recorder::::new(); + record_all_keys::, _>(&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(); @@ -229,31 +302,235 @@ mod tests { (root, storage_proof) } - fn stored_head(parachain: ParaId) -> StoredParaHead { - StoredParaHead { + fn initial_best_head(parachain: ParaId) -> BestParaHead { + BestParaHead { at_relay_block_number: 0, - head: parachain.encode(), + head_hash: parachain_head_hash(&head_data(parachain, 0)), + next_imported_hash_position: 1, } } + fn head_data(parachain: ParaId, head_number: u32) -> ParaHead { + (parachain, head_number).encode() + } + + fn head_hash(parachain: ParaId, head_number: u32) -> ParaHash { + parachain_head_hash(&head_data(parachain, head_number)) + } + + fn import_parachain_1_head( + relay_chain_block: RelayBlockNumber, + relay_state_root: RelayBlockHash, + proof: ParachainHeadsProof, + ) -> sp_runtime::DispatchResult { + Pallet::::submit_parachain_heads( + Origin::signed(1), + test_relay_header(relay_chain_block, relay_state_root).hash(), + vec![1], + proof, + ) + } + #[test] - fn imports_parachain_heads() { - let (state_root, proof) = prepare_parachain_heads_proof(vec![ - (1, 1), - ]); + fn imports_initial_parachain_heads() { + let (state_root, proof) = prepare_parachain_heads_proof(vec![(1, head_data(1, 0)), (3, head_data(3, 10))]); run_test(|| { initialize(state_root); - assert_ok!( - Pallet::::submit_parachain_heads( - Origin::signed(1), - test_header(0, state_root).hash(), - vec![1, 2], - proof, - ), + + // we're trying to update heads of parachains 1, 2 and 3 + assert_ok!(Pallet::::submit_parachain_heads( + Origin::signed(1), + test_relay_header(0, state_root).hash(), + vec![1, 2, 3], + proof, + ),); + + // but only 1 and 2 are updated, because proof is missing head of parachain#2 + assert_eq!(BestParaHeads::::get(1), Some(initial_best_head(1))); + assert_eq!(BestParaHeads::::get(2), None); + assert_eq!( + BestParaHeads::::get(3), + Some(BestParaHead { + at_relay_block_number: 0, + head_hash: parachain_head_hash(&head_data(3, 10)), + next_imported_hash_position: 1, + }) ); - assert_eq!(ParaHeads::::get(1), Some(stored_head(1))); - assert_eq!(ParaHeads::::get(2), None); + assert_eq!( + ImportedParaHeads::::get(1, initial_best_head(1).head_hash), + Some(head_data(1, 0)) + ); + assert_eq!( + ImportedParaHeads::::get(2, initial_best_head(2).head_hash), + None + ); + assert_eq!( + ImportedParaHeads::::get(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![(1, head_data(1, 5))]); + let (state_root_10, proof_10) = prepare_parachain_heads_proof(vec![(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::::get(1), + Some(BestParaHead { + at_relay_block_number: 0, + head_hash: parachain_head_hash(&head_data(1, 5)), + next_imported_hash_position: 1, + }) + ); + assert_eq!( + ImportedParaHeads::::get(1, parachain_head_hash(&head_data(1, 5))), + Some(head_data(1, 5)) + ); + assert_eq!( + ImportedParaHeads::::get(1, parachain_head_hash(&head_data(1, 10))), + 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::::get(1), + Some(BestParaHead { + at_relay_block_number: 1, + head_hash: parachain_head_hash(&head_data(1, 10)), + next_imported_hash_position: 2, + }) + ); + assert_eq!( + ImportedParaHeads::::get(1, parachain_head_hash(&head_data(1, 5))), + Some(head_data(1, 5)) + ); + assert_eq!( + ImportedParaHeads::::get(1, parachain_head_hash(&head_data(1, 10))), + 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![(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::::get(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::::get(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![(1, head_data(1, 5))]); + let (state_root_10, proof_10) = prepare_parachain_heads_proof(vec![(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::::get(1), + Some(BestParaHead { + at_relay_block_number: 1, + head_hash: parachain_head_hash(&head_data(1, 10)), + 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::::get(1), + Some(BestParaHead { + at_relay_block_number: 1, + head_hash: parachain_head_hash(&head_data(1, 10)), + 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![(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::::get(1, parachain_head_hash(&head_data(1, i))).is_some()); + } + + // import next relay chain header and next parachain head + let (state_root, proof) = prepare_parachain_heads_proof(vec![(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::::get(1, parachain_head_hash(&head_data(1, 0))).is_none()); + for i in 1..=heads_to_keep { + assert!(ImportedParaHeads::::get(1, parachain_head_hash(&head_data(1, i))).is_some()); + } + }); + } + + #[test] + fn fails_on_unknown_relay_chain_block() { + let (state_root, proof) = prepare_parachain_heads_proof(vec![(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::::UnknownRelayChainBlock + ); + }); + } + + #[test] + fn fails_on_invalid_storage_proof() { + let (_state_root, proof) = prepare_parachain_heads_proof(vec![(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::::InvalidStorageProof + ); }); } } diff --git a/modules/parachains/src/mock.rs b/modules/parachains/src/mock.rs index cb6bb2a85a..3531dad56b 100644 --- a/modules/parachains/src/mock.rs +++ b/modules/parachains/src/mock.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use bp_runtime::{Chain, HeaderOf}; +use bp_runtime::Chain; use frame_support::{construct_runtime, parameter_types, weights::Weight}; use sp_runtime::{ testing::{Header, H256}, @@ -25,10 +25,9 @@ use sp_runtime::{ use crate as pallet_bridge_parachains; pub type AccountId = u64; -pub type TestHeader = HeaderOf<::BridgedChain>; -pub type TestNumber = crate::RelayBlockNumber; -pub type TestHash = crate::RelayBlockHash; -pub type TestHasher = BlakeTwo256; +pub type TestNumber = u64; + +pub type RelayBlockHeader = sp_runtime::generic::Header; type Block = frame_system::mocking::MockBlock; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -46,7 +45,7 @@ construct_runtime! { } parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: TestNumber = 250; pub const MaximumBlockWeight: Weight = 1024; pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); @@ -56,7 +55,7 @@ impl frame_system::Config for TestRuntime { type Origin = Origin; type Index = u64; type Call = Call; - type BlockNumber = u64; + type BlockNumber = TestNumber; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; @@ -92,25 +91,30 @@ impl pallet_bridge_grandpa::Config for TestRuntime { type WeightInfo = (); } +parameter_types! { + pub const HeadsToKeep: u32 = 4; +} + impl pallet_bridge_parachains::Config for TestRuntime { + type HeadsToKeep = HeadsToKeep; } #[derive(Debug)] pub struct TestBridgedChain; impl Chain for TestBridgedChain { - type BlockNumber = ::BlockNumber; - type Hash = ::Hash; - type Hasher = ::Hashing; - type Header = ::Header; + type BlockNumber = crate::RelayBlockNumber; + type Hash = crate::RelayBlockHash; + type Hasher = crate::RelayBlockHasher; + type Header = RelayBlockHeader; } pub fn run_test(test: impl FnOnce() -> T) -> T { sp_io::TestExternalities::new(Default::default()).execute_with(test) } -pub fn test_header(num: TestNumber, state_root: TestHash) -> TestHeader { - TestHeader::new( +pub fn test_relay_header(num: crate::RelayBlockNumber, state_root: crate::RelayBlockHash) -> RelayBlockHeader { + RelayBlockHeader::new( num, Default::default(), state_root, diff --git a/primitives/polkadot-core/src/lib.rs b/primitives/polkadot-core/src/lib.rs index a1619b27bc..fb398f11fb 100644 --- a/primitives/polkadot-core/src/lib.rs +++ b/primitives/polkadot-core/src/lib.rs @@ -41,6 +41,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. diff --git a/primitives/polkadot-core/src/parachains.rs b/primitives/polkadot-core/src/parachains.rs new file mode 100644 index 0000000000..df76fa2df3 --- /dev/null +++ b/primitives/polkadot-core/src/parachains.rs @@ -0,0 +1,43 @@ +// 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 . + +//! Primitives of polkadot-like chains, that are related to parachains functionality. +//! +//! Even though this (bridges) repository references polkadot repository, we can't +//! referenece 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 sp_core::Hasher; +use sp_std::vec::Vec; + +/// Parachain id. +pub type ParaId = u32; + +/// Parachain head, which is (at least in Cumulus) a SCALE-encoded parachain header. +pub type ParaHead = Vec; + +/// Parachain head hash. +pub type ParaHash = crate::Hash; + +/// Raw storage proof of parachain heads, stored in polkadot-like chain runtime. +pub type ParachainHeadsProof = Vec>; + +/// Return hash of the parachain head. +pub fn parachain_head_hash(head: &[u8]) -> ParaHash { + sp_runtime::traits::BlakeTwo256::hash(head) +} diff --git a/primitives/runtime/src/storage_keys.rs b/primitives/runtime/src/storage_keys.rs index 85872e60b1..c02814919f 100644 --- a/primitives/runtime/src/storage_keys.rs +++ b/primitives/runtime/src/storage_keys.rs @@ -33,8 +33,7 @@ fn storage_map_final_key_with_prefix(module_prefix: &str, map_name: &str, key: & let storage_prefix_hashed = frame_support::Twox128::hash(map_name.as_bytes()); let key_hashed = frame_support::Blake2_128Concat::hash(key); - let mut final_key = - Vec::with_capacity(module_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len()); + let mut final_key = Vec::with_capacity(module_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len()); final_key.extend_from_slice(&module_prefix_hashed[..]); final_key.extend_from_slice(&storage_prefix_hashed[..]); From e7f3635e1c7fa886c761d279f0c527497b087a0d Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 23 Jul 2021 14:02:12 +0300 Subject: [PATCH 03/14] demo of how to configure GRANDPA pallet instance --- modules/parachains/src/lib.rs | 22 +++++++++++++++------- modules/parachains/src/mock.rs | 23 +++++++++++++++++++++-- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index e493907f5c..eef4284db1 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -68,7 +68,13 @@ pub mod pallet { #[pallet::config] #[pallet::disable_frame_system_supertrait_check] - pub trait Config: pallet_bridge_grandpa::Config { + pub trait Config: pallet_bridge_grandpa::Config { + /// 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 @@ -96,7 +102,7 @@ pub mod pallet { #[pallet::call] impl Pallet where - ::BridgedChain: + >::BridgedChain: bp_runtime::Chain, { /// Submit proof of one or several parachain heads. @@ -113,12 +119,12 @@ pub mod pallet { 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::::get(relay_block_hash) + let relay_block = pallet_bridge_grandpa::ImportedHeaders::::get(relay_block_hash) .ok_or(Error::::UnknownRelayChainBlock)?; let relay_block_number = *relay_block.number(); // now parse storage proof and read parachain heads - pallet_bridge_grandpa::Pallet::::parse_finalized_storage_proof( + pallet_bridge_grandpa::Pallet::::parse_finalized_storage_proof( relay_block_hash, sp_trie::StorageProof::new(parachain_heads_proof), move |storage| { @@ -254,8 +260,10 @@ mod tests { use frame_support::{assert_noop, assert_ok, traits::OnInitialize}; use sp_trie::{record_all_keys, trie_types::TrieDBMut, Layout, MemoryDB, Recorder, TrieMut}; + type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1; + fn initialize(state_root: RelayBlockHash) { - pallet_bridge_grandpa::Pallet::::initialize( + pallet_bridge_grandpa::Pallet::::initialize( Origin::root(), bp_header_chain::InitializationData { header: test_relay_header(0, state_root), @@ -268,11 +276,11 @@ mod tests { } fn proceed(num: RelayBlockNumber, state_root: RelayBlockHash) { - pallet_bridge_grandpa::Pallet::::on_initialize(0); + pallet_bridge_grandpa::Pallet::::on_initialize(0); let header = test_relay_header(num, state_root); let justification = make_default_justification(&header); - assert_ok!(pallet_bridge_grandpa::Pallet::::submit_finality_proof( + assert_ok!(pallet_bridge_grandpa::Pallet::::submit_finality_proof( Origin::signed(1), header, justification, diff --git a/modules/parachains/src/mock.rs b/modules/parachains/src/mock.rs index 3531dad56b..ecb0de501c 100644 --- a/modules/parachains/src/mock.rs +++ b/modules/parachains/src/mock.rs @@ -39,7 +39,8 @@ construct_runtime! { UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - Grandpa: pallet_bridge_grandpa::{Pallet}, + Grandpa1: pallet_bridge_grandpa::::{Pallet}, + Grandpa2: pallet_bridge_grandpa::::{Pallet}, Parachains: pallet_bridge_parachains::{Pallet}, } } @@ -84,7 +85,14 @@ parameter_types! { pub const NumValidators: u32 = 5; } -impl pallet_bridge_grandpa::Config for TestRuntime { +impl pallet_bridge_grandpa::Config for TestRuntime { + type BridgedChain = TestBridgedChain; + type MaxRequests = MaxRequests; + type HeadersToKeep = HeadersToKeep; + type WeightInfo = (); +} + +impl pallet_bridge_grandpa::Config for TestRuntime { type BridgedChain = TestBridgedChain; type MaxRequests = MaxRequests; type HeadersToKeep = HeadersToKeep; @@ -96,6 +104,7 @@ parameter_types! { } impl pallet_bridge_parachains::Config for TestRuntime { + type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1; type HeadsToKeep = HeadsToKeep; } @@ -109,6 +118,16 @@ impl Chain for TestBridgedChain { type Header = RelayBlockHeader; } +#[derive(Debug)] +pub struct OtherBridgedChain; + +impl Chain for OtherBridgedChain { + type BlockNumber = u128; + type Hash = crate::RelayBlockHash; + type Hasher = crate::RelayBlockHasher; + type Header = sp_runtime::generic::Header; +} + pub fn run_test(test: impl FnOnce() -> T) -> T { sp_io::TestExternalities::new(Default::default()).execute_with(test) } From 1ab3c99deb1ed73d96b5ca1ecbe0ade750cb8245 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 23 Jul 2021 14:08:07 +0300 Subject: [PATCH 04/14] allow instances in parachains pallet --- modules/parachains/src/lib.rs | 51 +++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index eef4284db1..87a31c2001 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -59,7 +59,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::error] - pub enum Error { + pub enum Error { /// Relay chain block is unknown to us. UnknownRelayChainBlock, /// Invalid storage proof has been passed. @@ -68,7 +68,7 @@ pub mod pallet { #[pallet::config] #[pallet::disable_frame_system_supertrait_check] - pub trait Config: pallet_bridge_grandpa::Config { + pub trait Config: pallet_bridge_grandpa::Config { /// 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 @@ -86,21 +86,23 @@ pub mod pallet { /// Best parachain heads. #[pallet::storage] - pub type BestParaHeads = StorageMap<_, Identity, ParaId, BestParaHead>; + pub type BestParaHeads, I: 'static = ()> = StorageMap<_, Identity, ParaId, BestParaHead>; /// Parachain heads which have been imported into the pallet. #[pallet::storage] - pub type ImportedParaHeads = StorageDoubleMap<_, Identity, ParaId, Identity, ParaHash, ParaHead>; + pub type ImportedParaHeads, I: 'static = ()> = + StorageDoubleMap<_, Identity, ParaId, Identity, ParaHash, ParaHead>; /// A ring buffer of imported parachain head hashes. Ordered by the insertion time. #[pallet::storage] - pub(super) type ImportedParaHashes = StorageDoubleMap<_, Identity, ParaId, Identity, u32, ParaHash>; + pub(super) type ImportedParaHashes, I: 'static = ()> = + StorageDoubleMap<_, Identity, ParaId, Identity, u32, ParaHash>; #[pallet::pallet] - pub struct Pallet(PhantomData); + pub struct Pallet(PhantomData<(T, I)>); #[pallet::call] - impl Pallet + impl, I: 'static> Pallet where >::BridgedChain: bp_runtime::Chain, @@ -119,8 +121,9 @@ pub mod pallet { 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::::get(relay_block_hash) - .ok_or(Error::::UnknownRelayChainBlock)?; + let relay_block = + pallet_bridge_grandpa::ImportedHeaders::::get(relay_block_hash) + .ok_or(Error::::UnknownRelayChainBlock)?; let relay_block_number = *relay_block.number(); // now parse storage proof and read parachain heads @@ -129,7 +132,7 @@ pub mod pallet { sp_trie::StorageProof::new(parachain_heads_proof), move |storage| { for parachain in parachains { - let parachain_head = match Pallet::::read_parachain_head(&storage, parachain) { + let parachain_head = match Pallet::::read_parachain_head(&storage, parachain) { Some(parachain_head) => parachain_head, None => { log::trace!( @@ -141,8 +144,8 @@ pub mod pallet { } }; - let _: Result<_, ()> = BestParaHeads::::try_mutate(parachain, |stored_best_head| { - *stored_best_head = Some(Pallet::::update_parachain_head( + let _: Result<_, ()> = BestParaHeads::::try_mutate(parachain, |stored_best_head| { + *stored_best_head = Some(Pallet::::update_parachain_head( parachain, stored_best_head.take(), relay_block_number, @@ -153,13 +156,13 @@ pub mod pallet { } }, ) - .map_err(|_| Error::::InvalidStorageProof)?; + .map_err(|_| Error::::InvalidStorageProof)?; Ok(()) } } - impl Pallet { + impl, I: 'static> Pallet { /// Read parachain head from storage proof. fn read_parachain_head( storage: &bp_runtime::StorageProofChecker, @@ -215,14 +218,14 @@ pub mod pallet { }; // insert updated best parachain head - let head_hash_to_prune = ImportedParaHashes::::try_get(parachain, next_imported_hash_position); + let head_hash_to_prune = ImportedParaHashes::::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::::insert(parachain, next_imported_hash_position, updated_head_hash); - ImportedParaHeads::::insert(parachain, updated_head_hash, updated_head); + ImportedParaHashes::::insert(parachain, next_imported_hash_position, updated_head_hash); + ImportedParaHeads::::insert(parachain, updated_head_hash, updated_head); // remove old head if let Ok(head_hash_to_prune) = head_hash_to_prune { @@ -232,7 +235,7 @@ pub mod pallet { parachain, head_hash_to_prune, ); - ImportedParaHeads::::remove(parachain, head_hash_to_prune); + ImportedParaHeads::::remove(parachain, head_hash_to_prune); } Ok(updated_best_para_head) @@ -280,11 +283,13 @@ mod tests { let header = test_relay_header(num, state_root); let justification = make_default_justification(&header); - assert_ok!(pallet_bridge_grandpa::Pallet::::submit_finality_proof( - Origin::signed(1), - header, - justification, - )); + assert_ok!( + pallet_bridge_grandpa::Pallet::::submit_finality_proof( + Origin::signed(1), + header, + justification, + ) + ); } fn prepare_parachain_heads_proof(heads: Vec<(ParaId, ParaHead)>) -> (RelayBlockHash, ParachainHeadsProof) { From 1d173ff578962e723d273fb0337a927952937990 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 30 Aug 2021 12:47:13 +0300 Subject: [PATCH 05/14] spellcheck --- primitives/polkadot-core/src/parachains.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/polkadot-core/src/parachains.rs b/primitives/polkadot-core/src/parachains.rs index df76fa2df3..9354600926 100644 --- a/primitives/polkadot-core/src/parachains.rs +++ b/primitives/polkadot-core/src/parachains.rs @@ -17,7 +17,7 @@ //! Primitives of polkadot-like chains, that are related to parachains functionality. //! //! Even though this (bridges) repository references polkadot repository, we can't -//! referenece polkadot crates from pallets. That's because bridges repository is +//! 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. From 1840f9e8cc29cada0a4aa5d7472c9031a902a35e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 27 Oct 2021 09:12:41 +0300 Subject: [PATCH 06/14] TODO + fix --- modules/parachains/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index 0648081d41..432283044d 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -30,6 +30,7 @@ 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::*; @@ -170,6 +171,11 @@ pub mod pallet { ) .map_err(|_| Error::::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` + + // TODO: if some parachain is no more interesting to us, we should start pruning its heads + Ok(()) } } From 62ebaf563e6ff8a3dd2a85142c42e4c466c5024c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 27 Oct 2021 09:24:31 +0300 Subject: [PATCH 07/14] fmt --- modules/parachains/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index 432283044d..d1cdae0848 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -171,10 +171,11 @@ pub mod pallet { ) .map_err(|_| Error::::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` + // TODO: there may be parachains we are not interested in - so we only need to accept + // intersection of `parachains-interesting-to-us` and `parachains` - // TODO: if some parachain is no more interesting to us, we should start pruning its heads + // TODO: if some parachain is no more interesting to us, we should start pruning its + // heads Ok(()) } From 28cc6be8dfb610f33c136634019e5ece14a5dbf4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 7 Dec 2021 16:36:06 +0300 Subject: [PATCH 08/14] removed invalid storage_keys file --- modules/messages/src/lib.rs | 7 +-- modules/parachains/src/lib.rs | 5 +- primitives/runtime/src/lib.rs | 36 ++------------ primitives/runtime/src/storage_keys.rs | 49 ------------------- .../src/cli/register_parachain.rs | 3 +- relays/bin-substrate/src/cli/swap_tokens.rs | 4 +- 6 files changed, 16 insertions(+), 88 deletions(-) delete mode 100644 primitives/runtime/src/storage_keys.rs diff --git a/modules/messages/src/lib.rs b/modules/messages/src/lib.rs index 119869d81e..344e9a3729 100644 --- a/modules/messages/src/lib.rs +++ b/modules/messages/src/lib.rs @@ -805,11 +805,12 @@ pub mod pallet { /// messages and lanes states proofs. pub mod storage_keys { use super::*; + use frame_support::Blake2_128Concat; use sp_core::storage::StorageKey; /// Storage key of the outbound message in the runtime storage. pub fn message_key(pallet_prefix: &str, lane: &LaneId, nonce: MessageNonce) -> StorageKey { - bp_runtime::storage_map_final_key_blake2_128concat( + bp_runtime::storage_map_final_key::( pallet_prefix, "OutboundMessages", &MessageKey { lane_id: *lane, nonce }.encode(), @@ -818,12 +819,12 @@ pub mod storage_keys { /// Storage key of the outbound message lane state in the runtime storage. pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { - bp_runtime::storage_map_final_key_blake2_128concat(pallet_prefix, "OutboundLanes", lane) + bp_runtime::storage_map_final_key::(pallet_prefix, "OutboundLanes", lane) } /// Storage key of the inbound message lane state in the runtime storage. pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { - bp_runtime::storage_map_final_key_blake2_128concat(pallet_prefix, "InboundLanes", lane) + bp_runtime::storage_map_final_key::(pallet_prefix, "InboundLanes", lane) } } diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index d1cdae0848..05d38dfcb3 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -272,12 +272,13 @@ pub mod pallet { pub mod storage_keys { use super::*; - use bp_runtime::storage_keys::storage_map_final_key; + 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("Heads", ¶chain.encode()) + storage_map_final_key::("Paras", "Heads", ¶chain.encode()) } } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index c26e49c4a2..cd4e2d39f2 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -35,7 +35,6 @@ pub use storage_proof::{Error as StorageProofError, StorageProofChecker}; pub use storage_proof::craft_valid_storage_proof; pub mod messages; -pub mod storage_keys; mod chain; mod storage_proof; @@ -202,47 +201,22 @@ impl, BlockHash: Copy> TransactionEra( pallet_prefix: &str, map_name: &str, key: &[u8], ) -> StorageKey { - storage_map_final_key_identity( - pallet_prefix, - map_name, - &frame_support::Blake2_128Concat::hash(key), - ) -} - -/// -pub fn storage_map_final_key_twox64_concat( - pallet_prefix: &str, - map_name: &str, - key: &[u8], -) -> StorageKey { - storage_map_final_key_identity(pallet_prefix, map_name, &frame_support::Twox64Concat::hash(key)) -} - -/// This is a copy of the -/// `frame_support::storage::generator::StorageMap::storage_map_final_key` for `Identity` maps. -/// -/// We're using it because to call `storage_map_final_key` directly, we need access to the runtime -/// and pallet instance, which (sometimes) is impossible. -pub fn storage_map_final_key_identity( - pallet_prefix: &str, - map_name: &str, - key_hashed: &[u8], -) -> StorageKey { + let key_hashed = H::hash(key); let pallet_prefix_hashed = frame_support::Twox128::hash(pallet_prefix.as_bytes()); let storage_prefix_hashed = frame_support::Twox128::hash(map_name.as_bytes()); let mut final_key = Vec::with_capacity( - pallet_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len(), + pallet_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.as_ref().len(), ); final_key.extend_from_slice(&pallet_prefix_hashed[..]); diff --git a/primitives/runtime/src/storage_keys.rs b/primitives/runtime/src/storage_keys.rs deleted file mode 100644 index f8400df876..0000000000 --- a/primitives/runtime/src/storage_keys.rs +++ /dev/null @@ -1,49 +0,0 @@ -// 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 . - -use frame_support::{traits::Instance, StorageHasher}; -use sp_core::storage::StorageKey; -use sp_std::prelude::*; - -/// Key of the entry in the storage map, that is a part of pallet without instances. -pub fn storage_map_final_key(map_name: &str, key: &[u8]) -> StorageKey { - storage_map_final_key_with_prefix("", map_name, key) -} - -/// Key of the entry in the storage map, that is a part of pallet without instances. -pub fn storage_map_final_key_with_instance(map_name: &str, key: &[u8]) -> StorageKey { - storage_map_final_key_with_prefix(I::PREFIX, map_name, key) -} - -fn storage_map_final_key_with_prefix( - module_prefix: &str, - map_name: &str, - key: &[u8], -) -> StorageKey { - let module_prefix_hashed = frame_support::Twox128::hash(module_prefix.as_bytes()); - let storage_prefix_hashed = frame_support::Twox128::hash(map_name.as_bytes()); - let key_hashed = frame_support::Blake2_128Concat::hash(key); - - let mut final_key = Vec::with_capacity( - module_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len(), - ); - - final_key.extend_from_slice(&module_prefix_hashed[..]); - final_key.extend_from_slice(&storage_prefix_hashed[..]); - final_key.extend_from_slice(key_hashed.as_ref()); - - StorageKey(final_key) -} diff --git a/relays/bin-substrate/src/cli/register_parachain.rs b/relays/bin-substrate/src/cli/register_parachain.rs index fecc431148..8780993bd8 100644 --- a/relays/bin-substrate/src/cli/register_parachain.rs +++ b/relays/bin-substrate/src/cli/register_parachain.rs @@ -20,6 +20,7 @@ use crate::cli::{ }; use codec::Encode; +use frame_support::Twox64Concat; use num_traits::Zero; use polkadot_parachain::primitives::{ HeadData as ParaHeadData, Id as ParaId, ValidationCode as ParaValidationCode, @@ -188,7 +189,7 @@ impl RegisterParachain { log::info!(target: "bridge", "Registered parachain: {:?}. Waiting for onboarding", para_id); // wait until parathread is onboarded - let para_state_key = bp_runtime::storage_map_final_key_twox64_concat( + let para_state_key = bp_runtime::storage_map_final_key::( PARAS_PALLET_NAME, PARAS_LIFECYCLES_STORAGE_NAME, ¶_id.encode(), diff --git a/relays/bin-substrate/src/cli/swap_tokens.rs b/relays/bin-substrate/src/cli/swap_tokens.rs index aa3996aa41..65c73248d9 100644 --- a/relays/bin-substrate/src/cli/swap_tokens.rs +++ b/relays/bin-substrate/src/cli/swap_tokens.rs @@ -26,7 +26,7 @@ use rand::random; use structopt::StructOpt; use strum::{EnumString, EnumVariantNames, VariantNames}; -use frame_support::dispatch::GetDispatchInfo; +use frame_support::{dispatch::GetDispatchInfo, Identity}; use relay_substrate_client::{ AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, CallOf, Chain, ChainWithBalances, Client, Error as SubstrateError, HashOf, SignatureOf, Subscription, TransactionSignScheme, @@ -256,7 +256,7 @@ impl SwapTokens { // read state of swap after it has been created let token_swap_hash: H256 = token_swap.using_encoded(blake2_256).into(); - let token_swap_storage_key = bp_runtime::storage_map_final_key_identity( + let token_swap_storage_key = bp_runtime::storage_map_final_key::( TOKEN_SWAP_PALLET_NAME, pallet_bridge_token_swap::PENDING_SWAPS_MAP_NAME, token_swap_hash.as_ref(), From 8c2d3bbd966a666c83dabb6896808ae104dcd38a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 7 Dec 2021 16:51:45 +0300 Subject: [PATCH 09/14] change all hashers to Blake2_128Concat --- modules/parachains/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index 05d38dfcb3..1e7e6d5a6b 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -93,17 +93,17 @@ pub mod pallet { /// Best parachain heads. #[pallet::storage] pub type BestParaHeads, I: 'static = ()> = - StorageMap<_, Identity, ParaId, BestParaHead>; + StorageMap<_, Blake2_128Concat, ParaId, BestParaHead>; /// Parachain heads which have been imported into the pallet. #[pallet::storage] pub type ImportedParaHeads, I: 'static = ()> = - StorageDoubleMap<_, Identity, ParaId, Identity, ParaHash, ParaHead>; + 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, I: 'static = ()> = - StorageDoubleMap<_, Identity, ParaId, Identity, u32, ParaHash>; + StorageDoubleMap<_, Blake2_128Concat, ParaId, Blake2_128Concat, u32, ParaHash>; #[pallet::pallet] pub struct Pallet(PhantomData<(T, I)>); From ea05bde1e4282a3573b509e3d18a2596b77b7e0f Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 7 Dec 2021 16:53:54 +0300 Subject: [PATCH 10/14] use Twox64Concat for insertion position --- modules/parachains/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index 1e7e6d5a6b..fc3267e8aa 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -103,7 +103,7 @@ pub mod pallet { /// A ring buffer of imported parachain head hashes. Ordered by the insertion time. #[pallet::storage] pub(super) type ImportedParaHashes, I: 'static = ()> = - StorageDoubleMap<_, Blake2_128Concat, ParaId, Blake2_128Concat, u32, ParaHash>; + StorageDoubleMap<_, Blake2_128Concat, ParaId, twox64Concat, u32, ParaHash>; #[pallet::pallet] pub struct Pallet(PhantomData<(T, I)>); From 2fa25221ab22937769051ea1245381f57d53482d Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 7 Dec 2021 17:02:13 +0300 Subject: [PATCH 11/14] fix build --- modules/parachains/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index fc3267e8aa..8a9a060378 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -103,7 +103,7 @@ pub mod pallet { /// A ring buffer of imported parachain head hashes. Ordered by the insertion time. #[pallet::storage] pub(super) type ImportedParaHashes, I: 'static = ()> = - StorageDoubleMap<_, Blake2_128Concat, ParaId, twox64Concat, u32, ParaHash>; + StorageDoubleMap<_, Blake2_128Concat, ParaId, Twox64Concat, u32, ParaHash>; #[pallet::pallet] pub struct Pallet(PhantomData<(T, I)>); From 297428a6d69b5e2362fb67b71be92665eb293793 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 8 Dec 2021 13:06:23 +0300 Subject: [PATCH 12/14] fix compilation --- modules/parachains/src/mock.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/parachains/src/mock.rs b/modules/parachains/src/mock.rs index b9f8e5871b..f5046075c1 100644 --- a/modules/parachains/src/mock.rs +++ b/modules/parachains/src/mock.rs @@ -122,6 +122,14 @@ impl Chain for TestBridgedChain { 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)] @@ -137,6 +145,14 @@ impl Chain for OtherBridgedChain { 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(test: impl FnOnce() -> T) -> T { From 251c283334d179f1477c8f3fd7053ef37a8acb08 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 13 Dec 2021 11:49:24 +0300 Subject: [PATCH 13/14] change ParaId and ParaHead types --- Cargo.lock | 2 + modules/parachains/src/lib.rs | 131 ++++++++++----------- primitives/polkadot-core/Cargo.toml | 4 + primitives/polkadot-core/src/parachains.rs | 55 +++++++-- 4 files changed, 118 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index adc842d58a..892343d3cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -810,7 +810,9 @@ dependencies = [ "frame-system", "hex", "parity-scale-codec", + "parity-util-mem", "scale-info", + "serde", "sp-api", "sp-core", "sp-runtime", diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index 8a9a060378..3564e0697d 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -23,9 +23,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use bp_polkadot_core::parachains::{ - parachain_head_hash, ParaHash, ParaHead, ParaId, ParachainHeadsProof, -}; +use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaId, ParachainHeadsProof}; use codec::{Decode, Encode}; use frame_support::RuntimeDebug; use scale_info::TypeInfo; @@ -150,7 +148,7 @@ pub mod pallet { None => { log::trace!( target: "runtime::bridge-parachains", - "The head of parachain {} has been declared, but is missing from the proof", + "The head of parachain {:?} has been declared, but is missing from the proof", parachain, ); continue; @@ -188,8 +186,9 @@ pub mod pallet { parachain: ParaId, ) -> Option { let parachain_head_key = storage_keys::parachain_head_key(parachain); - let parachain_head = storage.read_value(parachain_head_key.0.as_ref()).ok()?; - parachain_head + 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. @@ -201,7 +200,7 @@ pub mod pallet { ) -> Result { // 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 = parachain_head_hash(&updated_head); + 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 => @@ -210,7 +209,7 @@ pub mod pallet { 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\ + "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, @@ -226,7 +225,7 @@ pub mod pallet { 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\ + "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, @@ -258,7 +257,7 @@ pub mod pallet { if let Ok(head_hash_to_prune) = head_hash_to_prune { log::trace!( target: "runtime::bridge-parachains", - "Pruning old head of parachain {}: {}", + "Pruning old head of parachain {:?}: {}", parachain, head_hash_to_prune, ); @@ -331,7 +330,7 @@ mod tests { let mut trie = TrieDBMut::::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) + trie.insert(&storage_key.0, &head.encode()) .map_err(|_| "TrieMut::insert has failed") .expect("TrieMut::insert should not fail in tests"); } @@ -347,20 +346,20 @@ mod tests { (root, storage_proof) } - fn initial_best_head(parachain: ParaId) -> BestParaHead { + fn initial_best_head(parachain: u32) -> BestParaHead { BestParaHead { at_relay_block_number: 0, - head_hash: parachain_head_hash(&head_data(parachain, 0)), + head_hash: head_data(parachain, 0).hash(), next_imported_hash_position: 1, } } - fn head_data(parachain: ParaId, head_number: u32) -> ParaHead { - (parachain, head_number).encode() + fn head_data(parachain: u32, head_number: u32) -> ParaHead { + ParaHead((parachain, head_number).encode()) } - fn head_hash(parachain: ParaId, head_number: u32) -> ParaHash { - parachain_head_hash(&head_data(parachain, head_number)) + fn head_hash(parachain: u32, head_number: u32) -> ParaHash { + head_data(parachain, head_number).hash() } fn import_parachain_1_head( @@ -371,15 +370,17 @@ mod tests { Pallet::::submit_parachain_heads( Origin::signed(1), test_relay_header(relay_chain_block, relay_state_root).hash(), - vec![1], + vec![ParaId(1)], proof, ) } #[test] fn imports_initial_parachain_heads() { - let (state_root, proof) = - prepare_parachain_heads_proof(vec![(1, head_data(1, 0)), (3, head_data(3, 10))]); + 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); @@ -387,32 +388,32 @@ mod tests { assert_ok!(Pallet::::submit_parachain_heads( Origin::signed(1), test_relay_header(0, state_root).hash(), - vec![1, 2, 3], + 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::::get(1), Some(initial_best_head(1))); - assert_eq!(BestParaHeads::::get(2), None); + assert_eq!(BestParaHeads::::get(ParaId(1)), Some(initial_best_head(1))); + assert_eq!(BestParaHeads::::get(ParaId(2)), None); assert_eq!( - BestParaHeads::::get(3), + BestParaHeads::::get(ParaId(3)), Some(BestParaHead { at_relay_block_number: 0, - head_hash: parachain_head_hash(&head_data(3, 10)), + head_hash: head_data(3, 10).hash(), next_imported_hash_position: 1, }) ); assert_eq!( - ImportedParaHeads::::get(1, initial_best_head(1).head_hash), + ImportedParaHeads::::get(ParaId(1), initial_best_head(1).head_hash), Some(head_data(1, 0)) ); assert_eq!( - ImportedParaHeads::::get(2, initial_best_head(2).head_hash), + ImportedParaHeads::::get(ParaId(2), initial_best_head(2).head_hash), None ); assert_eq!( - ImportedParaHeads::::get(3, head_hash(3, 10)), + ImportedParaHeads::::get(ParaId(3), head_hash(3, 10)), Some(head_data(3, 10)) ); }); @@ -420,26 +421,28 @@ mod tests { #[test] fn imports_parachain_heads_is_able_to_progress() { - let (state_root_5, proof_5) = prepare_parachain_heads_proof(vec![(1, head_data(1, 5))]); - let (state_root_10, proof_10) = prepare_parachain_heads_proof(vec![(1, head_data(1, 10))]); + 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::::get(1), + BestParaHeads::::get(ParaId(1)), Some(BestParaHead { at_relay_block_number: 0, - head_hash: parachain_head_hash(&head_data(1, 5)), + head_hash: head_data(1, 5).hash(), next_imported_hash_position: 1, }) ); assert_eq!( - ImportedParaHeads::::get(1, parachain_head_hash(&head_data(1, 5))), + ImportedParaHeads::::get(ParaId(1), head_data(1, 5).hash()), Some(head_data(1, 5)) ); assert_eq!( - ImportedParaHeads::::get(1, parachain_head_hash(&head_data(1, 10))), + ImportedParaHeads::::get(ParaId(1), head_data(1, 10).hash()), None ); @@ -447,19 +450,19 @@ mod tests { proceed(1, state_root_10); assert_ok!(import_parachain_1_head(1, state_root_10, proof_10)); assert_eq!( - BestParaHeads::::get(1), + BestParaHeads::::get(ParaId(1)), Some(BestParaHead { at_relay_block_number: 1, - head_hash: parachain_head_hash(&head_data(1, 10)), + head_hash: head_data(1, 10).hash(), next_imported_hash_position: 2, }) ); assert_eq!( - ImportedParaHeads::::get(1, parachain_head_hash(&head_data(1, 5))), + ImportedParaHeads::::get(ParaId(1), head_data(1, 5).hash()), Some(head_data(1, 5)) ); assert_eq!( - ImportedParaHeads::::get(1, parachain_head_hash(&head_data(1, 10))), + ImportedParaHeads::::get(ParaId(1), head_data(1, 10).hash()), Some(head_data(1, 10)) ); }); @@ -467,25 +470,27 @@ mod tests { #[test] fn does_nothing_when_already_imported_this_head_at_previous_relay_header() { - let (state_root, proof) = prepare_parachain_heads_proof(vec![(1, head_data(1, 0))]); + 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::::get(1), Some(initial_best_head(1))); + assert_eq!(BestParaHeads::::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::::get(1), Some(initial_best_head(1))); + assert_eq!(BestParaHeads::::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![(1, head_data(1, 5))]); - let (state_root_10, proof_10) = prepare_parachain_heads_proof(vec![(1, head_data(1, 10))]); + 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); @@ -494,10 +499,10 @@ mod tests { proceed(1, state_root_10); assert_ok!(import_parachain_1_head(1, state_root_10, proof_10)); assert_eq!( - BestParaHeads::::get(1), + BestParaHeads::::get(ParaId(1)), Some(BestParaHead { at_relay_block_number: 1, - head_hash: parachain_head_hash(&head_data(1, 10)), + head_hash: head_data(1, 10).hash(), next_imported_hash_position: 1, }) ); @@ -506,10 +511,10 @@ mod tests { // => 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::::get(1), + BestParaHeads::::get(ParaId(1)), Some(BestParaHead { at_relay_block_number: 1, - head_hash: parachain_head_hash(&head_data(1, 10)), + head_hash: head_data(1, 10).hash(), next_imported_hash_position: 1, }) ); @@ -523,7 +528,8 @@ mod tests { // import exactly `HeadsToKeep` headers for i in 0..heads_to_keep { - let (state_root, proof) = prepare_parachain_heads_proof(vec![(1, head_data(1, i))]); + let (state_root, proof) = + prepare_parachain_heads_proof(vec![(ParaId(1), head_data(1, i))]); if i == 0 { initialize(state_root); } else { @@ -534,38 +540,30 @@ mod tests { // nothing is pruned yet for i in 0..heads_to_keep { - assert!(ImportedParaHeads::::get( - 1, - parachain_head_hash(&head_data(1, i)) - ) - .is_some()); + assert!(ImportedParaHeads::::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![(1, head_data(1, heads_to_keep))]); + 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::::get( - 1, - parachain_head_hash(&head_data(1, 0)) - ) - .is_none()); + assert!( + ImportedParaHeads::::get(ParaId(1), head_data(1, 0).hash()).is_none() + ); for i in 1..=heads_to_keep { - assert!(ImportedParaHeads::::get( - 1, - parachain_head_hash(&head_data(1, i)) - ) - .is_some()); + assert!(ImportedParaHeads::::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![(1, head_data(1, 5))]); + 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); @@ -580,7 +578,8 @@ mod tests { #[test] fn fails_on_invalid_storage_proof() { - let (_state_root, proof) = prepare_parachain_heads_proof(vec![(1, head_data(1, 5))]); + 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()); diff --git a/primitives/polkadot-core/Cargo.toml b/primitives/polkadot-core/Cargo.toml index f05edd0d91..c156919695 100644 --- a/primitives/polkadot-core/Cargo.toml +++ b/primitives/polkadot-core/Cargo.toml @@ -8,7 +8,9 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] parity-scale-codec = { version = "2.2.0", default-features = false, features = ["derive"] } +parity-util-mem = { version = "0.10.0", optional = true } scale-info = { version = "1.0", 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", diff --git a/primitives/polkadot-core/src/parachains.rs b/primitives/polkadot-core/src/parachains.rs index 9354600926..c0448a846b 100644 --- a/primitives/polkadot-core/src/parachains.rs +++ b/primitives/polkadot-core/src/parachains.rs @@ -22,22 +22,61 @@ //! 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. -pub type ParaId = u32; +/// +/// 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); -/// Parachain head, which is (at least in Cumulus) a SCALE-encoded parachain header. -pub type ParaHead = Vec; +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>; - -/// Return hash of the parachain head. -pub fn parachain_head_hash(head: &[u8]) -> ParaHash { - sp_runtime::traits::BlakeTwo256::hash(head) -} From 1431b9f696ae210c08bc4970f8ecfe8b851b0531 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 12 May 2022 13:01:39 +0300 Subject: [PATCH 14/14] TODOs -> TODOs with issues refs --- modules/parachains/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/parachains/src/lib.rs b/modules/parachains/src/lib.rs index e3c944ea81..3f5d214f61 100644 --- a/modules/parachains/src/lib.rs +++ b/modules/parachains/src/lib.rs @@ -124,7 +124,7 @@ pub mod pallet { /// `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 + #[pallet::weight(0)] // TODO: https://github.com/paritytech/parity-bridges-common/issues/1391 pub fn submit_parachain_heads( _origin: OriginFor, relay_block_hash: RelayBlockHash, @@ -145,6 +145,7 @@ pub mod pallet { 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::::read_parachain_head(&storage, parachain) { Some(parachain_head) => parachain_head, None => { @@ -173,9 +174,11 @@ pub mod pallet { // 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(()) }