diff --git a/bridges/modules/parachains/src/extension.rs b/bridges/modules/parachains/src/extension.rs index 18e718da0e..fb93e671e2 100644 --- a/bridges/modules/parachains/src/extension.rs +++ b/bridges/modules/parachains/src/extension.rs @@ -56,7 +56,7 @@ where _ => return Ok(ValidTransaction::default()), }; - let maybe_stored_best_head = crate::BestParaHeads::::get(parachain); + let maybe_stored_best_head = crate::ParasInfo::::get(parachain); Self::validate_updated_parachain_head( parachain, &maybe_stored_best_head, @@ -72,8 +72,9 @@ mod tests { use crate::{ extension::FilterCall, mock::{run_test, Call, TestRuntime}, - BestParaHead, BestParaHeads, RelayBlockNumber, + ParaInfo, ParasInfo, RelayBlockNumber, }; + use bp_parachains::BestParaHeadHash; use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; fn validate_submit_parachain_heads( @@ -91,11 +92,13 @@ mod tests { } fn sync_to_relay_header_10() { - BestParaHeads::::insert( + ParasInfo::::insert( ParaId(1), - BestParaHead { - at_relay_block_number: 10, - head_hash: [1u8; 32].into(), + ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 10, + head_hash: [1u8; 32].into(), + }, next_imported_hash_position: 0, }, ); diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 33338433e3..604ac5df05 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -26,12 +26,10 @@ pub use weights::WeightInfo; pub use weights_ext::WeightInfoExt; -use bp_parachains::parachain_head_storage_key_at_source; +use bp_parachains::{parachain_head_storage_key_at_source, ParaInfo}; use bp_polkadot_core::parachains::{ParaHash, ParaHasher, ParaHead, ParaHeadsProof, ParaId}; use bp_runtime::StorageProofError; -use codec::{Decode, Encode}; -use frame_support::{traits::Contains, weights::PostDispatchInfo, RuntimeDebug}; -use scale_info::TypeInfo; +use frame_support::{traits::Contains, weights::PostDispatchInfo}; use sp_runtime::traits::Header as HeaderT; use sp_std::vec::Vec; @@ -58,21 +56,10 @@ 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, -} - /// Artifacts of the parachains head update. struct UpdateParachainHeadArtifacts { /// New best head of the parachain. - pub best_head: BestParaHead, + pub best_head: ParaInfo, /// If `true`, some old parachain head has been pruned during update. pub prune_happened: bool, } @@ -80,8 +67,10 @@ struct UpdateParachainHeadArtifacts { #[frame_support::pallet] pub mod pallet { use super::*; - use bp_parachains::ImportedParaHeadsKeyProvider; - use bp_runtime::{BasicOperatingMode, OwnedBridgeModule, StorageDoubleMapKeyProvider}; + use bp_parachains::{BestParaHeadHash, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider}; + use bp_runtime::{ + BasicOperatingMode, OwnedBridgeModule, StorageDoubleMapKeyProvider, StorageMapKeyProvider, + }; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; @@ -159,10 +148,18 @@ pub mod pallet { pub type PalletOperatingMode, I: 'static = ()> = StorageValue<_, BasicOperatingMode, ValueQuery>; - /// Best parachain heads. + /// Parachains info. + /// + /// Contains the following info: + /// - best parachain head hash + /// - the head of the `ImportedParaHashes` ring buffer #[pallet::storage] - pub type BestParaHeads, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, ParaId, BestParaHead>; + pub type ParasInfo, I: 'static = ()> = StorageMap< + _, + ::Hasher, + ::Key, + ::Value, + >; /// Parachain heads which have been imported into the pallet. #[pallet::storage] @@ -261,7 +258,7 @@ pub mod pallet { target: LOG_TARGET, "The head of parachain {:?} is None. {}", parachain, - if BestParaHeads::::contains_key(¶chain) { + if ParasInfo::::contains_key(¶chain) { "Looks like it is not yet registered at the source relay chain" } else { "Looks like it has been deregistered from the source relay chain" @@ -294,7 +291,7 @@ pub mod pallet { continue; } - let update_result: Result<_, ()> = BestParaHeads::::try_mutate(parachain, |stored_best_head| { + let update_result: Result<_, ()> = ParasInfo::::try_mutate(parachain, |stored_best_head| { let artifacts = Pallet::::update_parachain_head( parachain, stored_best_head.take(), @@ -348,7 +345,7 @@ pub mod pallet { impl, I: 'static> Pallet { /// Get best finalized header of the given parachain. pub fn best_parachain_head(parachain: ParaId) -> Option { - let best_para_head_hash = BestParaHeads::::get(parachain)?.head_hash; + let best_para_head_hash = ParasInfo::::get(parachain)?.best_head_hash.head_hash; ImportedParaHeads::::get(parachain, best_para_head_hash) } @@ -392,7 +389,7 @@ pub mod pallet { /// Without this check, we may import heads in random order. pub fn validate_updated_parachain_head( parachain: ParaId, - maybe_stored_best_head: &Option, + maybe_stored_best_head: &Option, updated_at_relay_block_number: RelayBlockNumber, updated_head_hash: ParaHash, err_log_prefix: &str, @@ -402,26 +399,28 @@ pub mod pallet { None => return Ok(ValidTransaction::default()), }; - if stored_best_head.at_relay_block_number >= updated_at_relay_block_number { + if stored_best_head.best_head_hash.at_relay_block_number >= + updated_at_relay_block_number + { log::trace!( target: LOG_TARGET, "{}. The parachain head for {:?} was already updated at better relay chain block {} >= {}.", err_log_prefix, parachain, - stored_best_head.at_relay_block_number, + stored_best_head.best_head_hash.at_relay_block_number, updated_at_relay_block_number ); return InvalidTransaction::Stale.into() } - if stored_best_head.head_hash == updated_head_hash { + if stored_best_head.best_head_hash.head_hash == updated_head_hash { log::trace!( target: LOG_TARGET, "{}. The parachain head hash for {:?} was already updated to {} at block {} < {}.", err_log_prefix, parachain, updated_head_hash, - stored_best_head.at_relay_block_number, + stored_best_head.best_head_hash.at_relay_block_number, updated_at_relay_block_number ); return InvalidTransaction::Stale.into() @@ -433,7 +432,7 @@ pub mod pallet { /// Try to update parachain head. pub(super) fn update_parachain_head( parachain: ParaId, - stored_best_head: Option, + stored_best_head: Option, updated_at_relay_block_number: RelayBlockNumber, updated_head: ParaHead, updated_head_hash: ParaHash, @@ -454,9 +453,11 @@ pub mod pallet { // 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, + let updated_best_para_head = ParaInfo { + best_head_hash: BestParaHeadHash { + 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(), }; @@ -527,9 +528,13 @@ mod tests { use crate::mock::{ run_test, test_relay_header, Origin, TestRuntime, PARAS_PALLET_NAME, UNTRACKED_PARACHAIN_ID, }; + use codec::Encode; - use bp_parachains::ImportedParaHeadsKeyProvider; - use bp_runtime::{BasicOperatingMode, OwnedBridgeModuleError, StorageDoubleMapKeyProvider}; + use bp_parachains::{BestParaHeadHash, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider}; + use bp_runtime::{ + BasicOperatingMode, OwnedBridgeModuleError, StorageDoubleMapKeyProvider, + StorageMapKeyProvider, + }; use bp_test_utils::{ authority_list, generate_owned_bridge_module_tests, make_default_justification, }; @@ -606,10 +611,12 @@ mod tests { (root, ParaHeadsProof(storage_proof), parachains) } - fn initial_best_head(parachain: u32) -> BestParaHead { - BestParaHead { - at_relay_block_number: 0, - head_hash: head_data(parachain, 0).hash(), + fn initial_best_head(parachain: u32) -> ParaInfo { + ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(parachain, 0).hash(), + }, next_imported_hash_position: 1, } } @@ -697,23 +704,31 @@ mod tests { assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight)); // but only 1 and 2 are updated, because proof is missing head of parachain#2 - assert_eq!(BestParaHeads::::get(ParaId(1)), Some(initial_best_head(1))); - assert_eq!(BestParaHeads::::get(ParaId(2)), None); + assert_eq!(ParasInfo::::get(ParaId(1)), Some(initial_best_head(1))); + assert_eq!(ParasInfo::::get(ParaId(2)), None); assert_eq!( - BestParaHeads::::get(ParaId(3)), - Some(BestParaHead { - at_relay_block_number: 0, - head_hash: head_data(3, 10).hash(), + ParasInfo::::get(ParaId(3)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(3, 10).hash() + }, next_imported_hash_position: 1, }) ); assert_eq!( - ImportedParaHeads::::get(ParaId(1), initial_best_head(1).head_hash), + ImportedParaHeads::::get( + ParaId(1), + initial_best_head(1).best_head_hash.head_hash + ), Some(head_data(1, 0)) ); assert_eq!( - ImportedParaHeads::::get(ParaId(2), initial_best_head(2).head_hash), + ImportedParaHeads::::get( + ParaId(2), + initial_best_head(2).best_head_hash.head_hash + ), None ); assert_eq!( @@ -734,10 +749,12 @@ mod tests { initialize(state_root_5); assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5)); assert_eq!( - BestParaHeads::::get(ParaId(1)), - Some(BestParaHead { - at_relay_block_number: 0, - head_hash: head_data(1, 5).hash(), + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(1, 5).hash() + }, next_imported_hash_position: 1, }) ); @@ -754,10 +771,12 @@ mod tests { proceed(1, state_root_10); assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10)); assert_eq!( - BestParaHeads::::get(ParaId(1)), - Some(BestParaHead { - at_relay_block_number: 1, - head_hash: head_data(1, 10).hash(), + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 1, + head_hash: head_data(1, 10).hash() + }, next_imported_hash_position: 2, }) ); @@ -797,19 +816,23 @@ mod tests { assert_ok!(result); assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight)); assert_eq!( - BestParaHeads::::get(ParaId(1)), - Some(BestParaHead { - at_relay_block_number: 0, - head_hash: head_data(1, 5).hash(), + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(1, 5).hash() + }, next_imported_hash_position: 1, }) ); - assert_eq!(BestParaHeads::::get(ParaId(UNTRACKED_PARACHAIN_ID)), None,); + assert_eq!(ParasInfo::::get(ParaId(UNTRACKED_PARACHAIN_ID)), None,); assert_eq!( - BestParaHeads::::get(ParaId(2)), - Some(BestParaHead { - at_relay_block_number: 0, - head_hash: head_data(1, 5).hash(), + ParasInfo::::get(ParaId(2)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 0, + head_hash: head_data(1, 5).hash() + }, next_imported_hash_position: 1, }) ); @@ -824,13 +847,13 @@ mod tests { // import head#0 of parachain#1 at relay block#0 initialize(state_root); assert_ok!(import_parachain_1_head(0, state_root, parachains.clone(), proof.clone())); - assert_eq!(BestParaHeads::::get(ParaId(1)), Some(initial_best_head(1))); + assert_eq!(ParasInfo::::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, parachains, proof)); - assert_eq!(BestParaHeads::::get(ParaId(1)), Some(initial_best_head(1))); + assert_eq!(ParasInfo::::get(ParaId(1)), Some(initial_best_head(1))); }); } @@ -848,10 +871,12 @@ mod tests { proceed(1, state_root_10); assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10)); assert_eq!( - BestParaHeads::::get(ParaId(1)), - Some(BestParaHead { - at_relay_block_number: 1, - head_hash: head_data(1, 10).hash(), + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 1, + head_hash: head_data(1, 10).hash() + }, next_imported_hash_position: 1, }) ); @@ -860,10 +885,12 @@ mod tests { // => nothing is changed, because better head has already been imported assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5)); assert_eq!( - BestParaHeads::::get(ParaId(1)), - Some(BestParaHead { - at_relay_block_number: 1, - head_hash: head_data(1, 10).hash(), + ParasInfo::::get(ParaId(1)), + Some(ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 1, + head_hash: head_data(1, 10).hash() + }, next_imported_hash_position: 1, }) ); @@ -1003,9 +1030,8 @@ mod tests { #[test] fn storage_keys_computed_properly() { assert_eq!( - BestParaHeads::::storage_map_final_key(ParaId(42)).to_vec(), - bp_parachains::best_parachain_head_hash_storage_key_at_target("Parachains", ParaId(42)) - .0, + ParasInfo::::storage_map_final_key(ParaId(42)).to_vec(), + ParasInfoKeyProvider::final_key("Parachains", &ParaId(42)).0 ); assert_eq!( diff --git a/bridges/primitives/parachains/src/lib.rs b/bridges/primitives/parachains/src/lib.rs index e6c9360d59..52b4954897 100644 --- a/bridges/primitives/parachains/src/lib.rs +++ b/bridges/primitives/parachains/src/lib.rs @@ -22,7 +22,7 @@ use bp_polkadot_core::{ parachains::{ParaHash, ParaHead, ParaId}, BlockNumber as RelayBlockNumber, }; -use bp_runtime::StorageDoubleMapKeyProvider; +use bp_runtime::{StorageDoubleMapKeyProvider, StorageMapKeyProvider}; use codec::{Decode, Encode}; use frame_support::{Blake2_128Concat, RuntimeDebug, Twox64Concat}; use scale_info::TypeInfo; @@ -44,6 +44,15 @@ pub struct BestParaHeadHash { pub head_hash: ParaHash, } +/// Best known parachain head as it is stored in the runtime storage. +#[derive(Decode, Encode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct ParaInfo { + /// Best known parachain head hash. + pub best_head_hash: BestParaHeadHash, + /// Current ring buffer position for this parachain. + pub next_imported_hash_position: u32, +} + /// Returns runtime storage key of given parachain head at the source chain. /// /// The head is stored by the `paras` pallet in the `Heads` map. @@ -54,18 +63,16 @@ pub fn parachain_head_storage_key_at_source( bp_runtime::storage_map_final_key::(paras_pallet_name, "Heads", ¶_id.encode()) } -/// Returns runtime storage key of best known parachain head at the target chain. +/// Can be use to access the runtime storage key of the parachains info at the target chain. /// -/// The head is stored by the `pallet-bridge-parachains` pallet in the `BestParaHeads` map. -pub fn best_parachain_head_hash_storage_key_at_target( - bridge_parachains_pallet_name: &str, - para_id: ParaId, -) -> StorageKey { - bp_runtime::storage_map_final_key::( - bridge_parachains_pallet_name, - "BestParaHeads", - ¶_id.encode(), - ) +/// The info is stored by the `pallet-bridge-parachains` pallet in the `ParasInfo` map. +pub struct ParasInfoKeyProvider; +impl StorageMapKeyProvider for ParasInfoKeyProvider { + const MAP_NAME: &'static str = "ParasInfo"; + + type Hasher = Blake2_128Concat; + type Key = ParaId; + type Value = ParaInfo; } /// Can be use to access the runtime storage key of the parachain head at the target chain. diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs index 7ca17fbd5a..8777919555 100644 --- a/bridges/primitives/runtime/src/lib.rs +++ b/bridges/primitives/runtime/src/lib.rs @@ -285,20 +285,42 @@ pub fn storage_value_key(pallet_prefix: &str, value_name: &str) -> StorageKey { StorageKey(final_key) } -/// Can be use to access the runtime storage key of a `StorageDoubleMap`. -pub trait StorageDoubleMapKeyProvider { - // The name of the variable that holds the `StorageDoubleMap` +/// Can be use to access the runtime storage key of a `StorageMap`. +pub trait StorageMapKeyProvider { + /// The name of the variable that holds the `StorageMap`. const MAP_NAME: &'static str; - // The same as `StorageDoubleMap::Hasher1` + /// The same as `StorageMap::Hasher1`. + type Hasher: StorageHasher; + /// The same as `StorageMap::Key1`. + type Key: FullCodec; + /// The same as `StorageMap::Value`. + type Value: FullCodec; + + /// This is a copy of the + /// `frame_support::storage::generator::StorageMap::storage_map_final_key`. + /// + /// 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. + fn final_key(pallet_prefix: &str, key: &Self::Key) -> StorageKey { + storage_map_final_key::(pallet_prefix, Self::MAP_NAME, &key.encode()) + } +} + +/// Can be use to access the runtime storage key of a `StorageDoubleMap`. +pub trait StorageDoubleMapKeyProvider { + /// The name of the variable that holds the `StorageDoubleMap`. + const MAP_NAME: &'static str; + + /// The same as `StorageDoubleMap::Hasher1`. type Hasher1: StorageHasher; - // The same as `StorageDoubleMap::Key1` + /// The same as `StorageDoubleMap::Key1`. type Key1: FullCodec; - // The same as `StorageDoubleMap::Hasher2` + /// The same as `StorageDoubleMap::Hasher2`. type Hasher2: StorageHasher; - // The same as `StorageDoubleMap::Key2` + /// The same as `StorageDoubleMap::Key2`. type Key2: FullCodec; - // The same as `StorageDoubleMap::Value` + /// The same as `StorageDoubleMap::Value`. type Value: FullCodec; /// This is a copy of the diff --git a/bridges/relays/client-substrate/src/client.rs b/bridges/relays/client-substrate/src/client.rs index 38e2a8b106..b1f0eb8587 100644 --- a/bridges/relays/client-substrate/src/client.rs +++ b/bridges/relays/client-substrate/src/client.rs @@ -29,7 +29,7 @@ use crate::{ use async_std::sync::{Arc, Mutex}; use async_trait::async_trait; -use bp_runtime::{HeaderIdProvider, StorageDoubleMapKeyProvider}; +use bp_runtime::{HeaderIdProvider, StorageDoubleMapKeyProvider, StorageMapKeyProvider}; use codec::{Decode, Encode}; use frame_system::AccountInfo; use futures::{SinkExt, StreamExt}; @@ -320,6 +320,23 @@ impl Client { .transpose() } + /// Read `MapStorage` value from runtime storage. + pub async fn storage_map_value( + &self, + pallet_prefix: &str, + key: &T::Key, + block_hash: Option, + ) -> Result> { + let storage_key = T::final_key(pallet_prefix, key); + + self.raw_storage_value(storage_key, block_hash) + .await? + .map(|encoded_value| { + T::Value::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed) + }) + .transpose() + } + /// Read `DoubleMapStorage` value from runtime storage. pub async fn storage_double_map_value( &self, diff --git a/bridges/relays/lib-substrate-relay/src/parachains/target.rs b/bridges/relays/lib-substrate-relay/src/parachains/target.rs index 355dd394c2..fa159cdeb7 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/target.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/target.rs @@ -24,9 +24,7 @@ use crate::{ }; use async_trait::async_trait; -use bp_parachains::{ - best_parachain_head_hash_storage_key_at_target, BestParaHeadHash, ImportedParaHeadsKeyProvider, -}; +use bp_parachains::{BestParaHeadHash, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider}; use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; use bp_runtime::HeaderIdProvider; use codec::Decode; @@ -122,12 +120,16 @@ where metrics: Option<&ParachainsLoopMetrics>, para_id: ParaId, ) -> Result, Self::Error> { - let best_para_head_hash_key = best_parachain_head_hash_storage_key_at_target( - P::SourceRelayChain::PARACHAINS_FINALITY_PALLET_NAME, - para_id, - ); - let best_para_head_hash: Option = - self.client.storage_value(best_para_head_hash_key, Some(at_block.1)).await?; + let best_para_head_hash: Option = self + .client + .storage_map_value::( + P::SourceRelayChain::PARACHAINS_FINALITY_PALLET_NAME, + ¶_id, + Some(at_block.1), + ) + .await? + .map(|para_info| para_info.best_head_hash); + if let (Some(metrics), &Some(ref best_para_head_hash)) = (metrics, &best_para_head_hash) { let imported_para_head = self .client