diff --git a/Cargo.lock b/Cargo.lock index a959ef0c98433..57ced506acf03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4870,6 +4870,7 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-state-machine", + "sp-storage 19.0.0", "sp-version", "thiserror 1.0.65", ] @@ -5003,6 +5004,8 @@ dependencies = [ "proptest", "sp-consensus-babe", "sp-core 28.0.0", + "sp-io", + "sp-keyring", "sp-runtime", "sp-state-machine", "sp-trie", @@ -5031,6 +5034,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", + "polkadot-primitives", "scale-info", "serde_json", "sp-api", diff --git a/cumulus/client/consensus/aura/src/collator.rs b/cumulus/client/consensus/aura/src/collator.rs index 3999352322e20..9a7cdadba5b36 100644 --- a/cumulus/client/consensus/aura/src/collator.rs +++ b/cumulus/client/consensus/aura/src/collator.rs @@ -33,6 +33,7 @@ use cumulus_client_consensus_common::{ use cumulus_client_parachain_inherent::{ParachainInherentData, ParachainInherentDataProvider}; use cumulus_primitives_core::{ relay_chain::Hash as PHash, DigestItem, ParachainBlockData, PersistedValidationData, + RelayProofRequest, }; use cumulus_relay_chain_interface::RelayChainInterface; use sc_client_api::BackendTransaction; @@ -177,6 +178,7 @@ where parent_hash: Block::Hash, timestamp: impl Into>, relay_parent_descendants: Option, + relay_proof_request: RelayProofRequest, collator_peer_id: PeerId, ) -> Result<(ParachainInherentData, InherentData), Box> { let paras_inherent_data = ParachainInherentDataProvider::create_at( @@ -188,6 +190,7 @@ where .map(RelayParentData::into_inherent_descendant_list) .unwrap_or_default(), Vec::new(), + relay_proof_request, collator_peer_id, ) .await; @@ -224,6 +227,7 @@ where validation_data: &PersistedValidationData, parent_hash: Block::Hash, timestamp: impl Into>, + relay_proof_request: RelayProofRequest, collator_peer_id: PeerId, ) -> Result<(ParachainInherentData, InherentData), Box> { self.create_inherent_data_with_rp_offset( @@ -232,6 +236,7 @@ where parent_hash, timestamp, None, + relay_proof_request, collator_peer_id, ) .await diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index 1f99e2f6e5cc0..532da7ede18e3 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -238,6 +238,7 @@ where &validation_data, parent_hash, claim.timestamp(), + Default::default(), params.collator_peer_id, ) .await diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 303b5268095c5..f20a64abdfc7b 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -36,7 +36,7 @@ use codec::{Codec, Encode}; use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterface; use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockImportMarker}; use cumulus_primitives_aura::AuraUnincludedSegmentApi; -use cumulus_primitives_core::{CollectCollationInfo, PersistedValidationData}; +use cumulus_primitives_core::{CollectCollationInfo, KeyToIncludeInRelayProof, PersistedValidationData}; use cumulus_relay_chain_interface::RelayChainInterface; use sp_consensus::Environment; @@ -164,8 +164,10 @@ where + Send + Sync + 'static, - Client::Api: - AuraApi + CollectCollationInfo + AuraUnincludedSegmentApi, + Client::Api: AuraApi + + CollectCollationInfo + + AuraUnincludedSegmentApi + + KeyToIncludeInRelayProof, Backend: sc_client_api::Backend + 'static, RClient: RelayChainInterface + Clone + 'static, CIDP: CreateInherentDataProviders + 'static, @@ -216,8 +218,10 @@ where + Send + Sync + 'static, - Client::Api: - AuraApi + CollectCollationInfo + AuraUnincludedSegmentApi, + Client::Api: AuraApi + + CollectCollationInfo + + AuraUnincludedSegmentApi + + KeyToIncludeInRelayProof, Backend: sc_client_api::Backend + 'static, RClient: RelayChainInterface + Clone + 'static, CIDP: CreateInherentDataProviders + 'static, @@ -392,12 +396,15 @@ where // Build and announce collations recursively until // `can_build_upon` fails or building a collation fails. + let relay_proof_request = super::get_relay_proof_request(&*params.para_client, parent_hash); + let (parachain_inherent_data, other_inherent_data) = match collator .create_inherent_data( relay_parent, &validation_data, parent_hash, slot_claim.timestamp(), + relay_proof_request, params.collator_peer_id, ) .await diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index d938dca69282f..fba1fa20f12b2 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -25,7 +25,9 @@ use crate::collator::SlotClaim; use codec::Codec; use cumulus_client_consensus_common::{self as consensus_common, ParentSearchParams}; use cumulus_primitives_aura::{AuraUnincludedSegmentApi, Slot}; -use cumulus_primitives_core::{relay_chain::Header as RelayHeader, BlockT}; +use cumulus_primitives_core::{ + relay_chain::Header as RelayHeader, BlockT, KeyToIncludeInRelayProof, RelayProofRequest, +}; use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; use polkadot_node_subsystem::messages::{CollatorProtocolMessage, RuntimeApiRequest}; use polkadot_node_subsystem_util::runtime::ClaimQueueSnapshot; @@ -662,6 +664,34 @@ mod tests { } } +/// Fetches relay chain storage proof requests from the parachain runtime. +/// +/// Queries the runtime API to determine which relay chain storage keys +/// (both top-level and child trie keys) should be included in the relay chain state proof. +/// +/// Falls back to an empty request if the runtime API call fails or is not implemented. +fn get_relay_proof_request( + client: &Client, + parent_hash: Block::Hash, +) -> RelayProofRequest +where + Block: BlockT, + Client: ProvideRuntimeApi, + Client::Api: KeyToIncludeInRelayProof, +{ + client + .runtime_api() + .keys_to_prove(parent_hash) + .unwrap_or_else(|e| { + tracing::debug!( + target: crate::LOG_TARGET, + error = ?e, + "Failed to fetch relay proof requests from runtime, using empty request" + ); + Default::default() + }) +} + /// Holds a relay parent and its descendants. pub struct RelayParentData { /// The relay parent block header diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs index 4a58ed81426af..173550d995b63 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs @@ -359,7 +359,6 @@ where relay_parent_storage_root: *relay_parent_header.state_root(), max_pov_size: *max_pov_size, }; - let (parachain_inherent_data, other_inherent_data) = match collator .create_inherent_data_with_rp_offset( relay_parent, @@ -367,6 +366,7 @@ where parent_hash, slot_claim.timestamp(), Some(rp_data), + Default::default(), collator_peer_id, ) .await diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/tests.rs b/cumulus/client/consensus/aura/src/collators/slot_based/tests.rs index e0ba35e558afe..ef4ed09c6dc66 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/tests.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/tests.rs @@ -566,6 +566,15 @@ impl RelayChainInterface for TestRelayClient { unimplemented!("Not needed for test") } + async fn prove_child_read( + &self, + _: RelayHash, + _: &cumulus_relay_chain_interface::ChildInfo, + _: &[Vec], + ) -> RelayChainResult { + unimplemented!("Not needed for test") + } + async fn wait_for_block(&self, _: RelayHash) -> RelayChainResult<()> { unimplemented!("Not needed for test") } diff --git a/cumulus/client/parachain-inherent/src/lib.rs b/cumulus/client/parachain-inherent/src/lib.rs index 5e994cd472f70..6c72e1774b1ca 100644 --- a/cumulus/client/parachain-inherent/src/lib.rs +++ b/cumulus/client/parachain-inherent/src/lib.rs @@ -24,25 +24,26 @@ use cumulus_primitives_core::{ self, ApprovedPeerId, Block as RelayBlock, Hash as PHash, Header as RelayHeader, HrmpChannelId, }, - ParaId, PersistedValidationData, + ParaId, PersistedValidationData, RelayProofRequest, RelayStorageKey, }; pub use cumulus_primitives_parachain_inherent::{ParachainInherentData, INHERENT_IDENTIFIER}; use cumulus_relay_chain_interface::RelayChainInterface; pub use mock::{MockValidationDataInherentDataProvider, MockXcmConfig}; use sc_network_types::PeerId; +use sp_state_machine::StorageProof; +use sp_storage::ChildInfo; const LOG_TARGET: &str = "parachain-inherent"; -/// Collect the relevant relay chain state in form of a proof for putting it into the validation -/// data inherent. -async fn collect_relay_storage_proof( +/// Builds the list of static relay chain storage keys that are always needed for parachain +/// validation. +async fn get_static_relay_storage_keys( relay_chain_interface: &impl RelayChainInterface, para_id: ParaId, relay_parent: PHash, include_authorities: bool, include_next_authorities: bool, - additional_relay_state_keys: Vec>, -) -> Option { +) -> Option>> { use relay_chain::well_known_keys as relay_well_known_keys; let ingress_channels = relay_chain_interface @@ -136,25 +137,95 @@ async fn collect_relay_storage_proof( relevant_keys.push(relay_well_known_keys::NEXT_AUTHORITIES.to_vec()); } - // Add additional relay state keys + Some(relevant_keys) +} + +/// Collect the relevant relay chain state in form of a proof for putting it into the validation +/// data inherent. +async fn collect_relay_storage_proof( + relay_chain_interface: &impl RelayChainInterface, + para_id: ParaId, + relay_parent: PHash, + include_authorities: bool, + include_next_authorities: bool, + additional_relay_state_keys: Vec>, + relay_proof_request: RelayProofRequest, +) -> Option { + // Get static keys that are always needed + let mut all_top_keys = get_static_relay_storage_keys( + relay_chain_interface, + para_id, + relay_parent, + include_authorities, + include_next_authorities, + ) + .await?; + + // Add additional_relay_state_keys let unique_keys: Vec> = additional_relay_state_keys .into_iter() - .filter(|key| !relevant_keys.contains(key)) + .filter(|key| !all_top_keys.contains(key)) .collect(); - relevant_keys.extend(unique_keys); + all_top_keys.extend(unique_keys); - relay_chain_interface - .prove_read(relay_parent, &relevant_keys) - .await - .map_err(|e| { + // Group requested keys by storage type + let RelayProofRequest { keys } = relay_proof_request; + let mut child_keys: std::collections::BTreeMap, Vec>> = + std::collections::BTreeMap::new(); + + for key in keys { + match key { + RelayStorageKey::Top(k) => { + if !all_top_keys.contains(&k) { + all_top_keys.push(k); + } + }, + RelayStorageKey::Child { storage_key, key } => { + child_keys.entry(storage_key).or_default().push(key); + }, + } + } + + // Collect all storage proofs + let mut all_proofs = Vec::new(); + + // Collect top-level storage proof. + match relay_chain_interface.prove_read(relay_parent, &all_top_keys).await { + Ok(top_proof) => { + all_proofs.push(top_proof); + }, + Err(e) => { tracing::error!( target: LOG_TARGET, relay_parent = ?relay_parent, error = ?e, - "Cannot obtain read proof from relay chain.", + "Cannot obtain relay chain storage proof.", ); - }) - .ok() + return None; + }, + } + + // Collect child trie proofs + for (storage_key, data_keys) in child_keys { + let child_info = ChildInfo::new_default(&storage_key); + match relay_chain_interface.prove_child_read(relay_parent, &child_info, &data_keys).await { + Ok(child_proof) => { + all_proofs.push(child_proof); + }, + Err(e) => { + tracing::error!( + target: LOG_TARGET, + relay_parent = ?relay_parent, + child_trie_id = ?child_info.storage_key(), + error = ?e, + "Cannot obtain child trie proof from relay chain.", + ); + }, + } + } + + // Merge all proofs + Some(StorageProof::merge(all_proofs)) } pub struct ParachainInherentDataProvider; @@ -170,6 +241,7 @@ impl ParachainInherentDataProvider { para_id: ParaId, relay_parent_descendants: Vec, additional_relay_state_keys: Vec>, + relay_proof_request: RelayProofRequest, collator_peer_id: PeerId, ) -> Option { let collator_peer_id = ApprovedPeerId::try_from(collator_peer_id.to_bytes()) @@ -195,6 +267,7 @@ impl ParachainInherentDataProvider { !relay_parent_descendants.is_empty(), include_next_authorities, additional_relay_state_keys, + relay_proof_request, ) .await?; diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index b989f81efd5dc..02726367b7fe5 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -33,7 +33,9 @@ use cumulus_primitives_core::{ }, InboundDownwardMessage, ParaId, PersistedValidationData, }; -use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface, RelayChainResult}; +use cumulus_relay_chain_interface::{ + ChildInfo, RelayChainError, RelayChainInterface, RelayChainResult, +}; use futures::{FutureExt, Stream, StreamExt}; use polkadot_primitives::CandidateEvent; use polkadot_service::{ @@ -240,6 +242,18 @@ impl RelayChainInterface for RelayChainInProcessInterface { .map_err(RelayChainError::StateMachineError) } + async fn prove_child_read( + &self, + relay_parent: PHash, + child_info: &ChildInfo, + child_keys: &[Vec], + ) -> RelayChainResult { + let state_backend = self.backend.state_at(relay_parent, TrieCacheContext::Untrusted)?; + + sp_state_machine::prove_child_read(state_backend, child_info, child_keys) + .map_err(RelayChainError::StateMachineError) + } + /// Wait for a given relay chain block in an async way. /// /// The caller needs to pass the hash of a block it waits for and the function will return when diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml index be19f99526659..db89a573b3537 100644 --- a/cumulus/client/relay-chain-interface/Cargo.toml +++ b/cumulus/client/relay-chain-interface/Cargo.toml @@ -21,6 +21,7 @@ sc-network = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } sp-version = { workspace = true } async-trait = { workspace = true } diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs index dd03738ed0029..8f87ccc6997b2 100644 --- a/cumulus/client/relay-chain-interface/src/lib.rs +++ b/cumulus/client/relay-chain-interface/src/lib.rs @@ -42,6 +42,7 @@ pub use cumulus_primitives_core::{ }; pub use polkadot_overseer::Handle as OverseerHandle; pub use sp_state_machine::StorageValue; +pub use sp_storage::ChildInfo; pub type RelayChainResult = Result; @@ -213,6 +214,14 @@ pub trait RelayChainInterface: Send + Sync { relevant_keys: &Vec>, ) -> RelayChainResult; + /// Generate a child trie storage read proof. + async fn prove_child_read( + &self, + relay_parent: PHash, + child_info: &ChildInfo, + child_keys: &[Vec], + ) -> RelayChainResult; + /// Returns the validation code hash for the given `para_id` using the given /// `occupied_core_assumption`. async fn validation_code_hash( @@ -354,6 +363,15 @@ where (**self).prove_read(relay_parent, relevant_keys).await } + async fn prove_child_read( + &self, + relay_parent: PHash, + child_info: &ChildInfo, + child_keys: &[Vec], + ) -> RelayChainResult { + (**self).prove_child_read(relay_parent, child_info, child_keys).await + } + async fn wait_for_block(&self, hash: PHash) -> RelayChainResult<()> { (**self).wait_for_block(hash).await } diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs index 84d22676789cf..b88d95e9c1778 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs @@ -26,7 +26,8 @@ use cumulus_primitives_core::{ InboundDownwardMessage, ParaId, PersistedValidationData, }; use cumulus_relay_chain_interface::{ - BlockNumber, CoreState, PHeader, RelayChainError, RelayChainInterface, RelayChainResult, + BlockNumber, ChildInfo, CoreIndex, CoreState, PHeader, RelayChainError, RelayChainInterface, + RelayChainResult, }; use futures::{FutureExt, Stream, StreamExt}; use polkadot_overseer::Handle; @@ -210,6 +211,24 @@ impl RelayChainInterface for RelayChainRpcInterface { }) } + async fn prove_child_read( + &self, + relay_parent: RelayHash, + child_info: &ChildInfo, + child_keys: &[Vec], + ) -> RelayChainResult { + let child_storage_key = child_info.prefixed_storage_key(); + let storage_keys: Vec = + child_keys.iter().map(|key| StorageKey(key.clone())).collect(); + + self.rpc_client + .state_get_child_read_proof(child_storage_key, storage_keys, Some(relay_parent)) + .await + .map(|read_proof| { + StorageProof::new(read_proof.proof.into_iter().map(|bytes| bytes.to_vec())) + }) + } + /// Wait for a given relay chain block /// /// The hash of the block to wait for is passed. We wait for the block to arrive or return after @@ -273,9 +292,7 @@ impl RelayChainInterface for RelayChainRpcInterface { async fn claim_queue( &self, relay_parent: RelayHash, - ) -> RelayChainResult< - BTreeMap>, - > { + ) -> RelayChainResult>> { self.rpc_client.parachain_host_claim_queue(relay_parent).await } diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index 80858a665cfaf..52039a4236a58 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -276,6 +276,17 @@ impl RelayChainRpcClient { self.request("state_getReadProof", params).await } + /// Get child trie read proof for `child_keys` + pub async fn state_get_child_read_proof( + &self, + child_storage_key: sp_core::storage::PrefixedStorageKey, + child_keys: Vec, + at: Option, + ) -> Result, RelayChainError> { + let params = rpc_params![child_storage_key, child_keys, at]; + self.request("state_getChildReadProof", params).await + } + /// Retrieve storage item at `storage_key` pub async fn state_get_storage( &self, diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 5ff4af131f565..ab62b0b1550cb 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -701,6 +701,10 @@ pub mod pallet { >::put(relevant_messaging_state.clone()); >::put(host_config); + total_weight.saturating_accrue( + ::on_relay_state_proof(&relay_state_proof), + ); + ::on_validation_data(&vfp); if let Some(collator_peer_id) = collator_peer_id { @@ -1766,13 +1770,31 @@ impl polkadot_runtime_parachains::EnsureForParachain for Pallet { /// Or like [`on_validation_code_applied`](Self::on_validation_code_applied) that is called /// when the new validation is written to the state. This means that /// from the next block the runtime is being using this new code. -#[impl_trait_for_tuples::impl_for_tuples(30)] pub trait OnSystemEvent { /// Called in each blocks once when the validation data is set by the inherent. fn on_validation_data(data: &PersistedValidationData); /// Called when the validation code is being applied, aka from the next block on this is the new /// runtime. fn on_validation_code_applied(); + /// Called to process keys from the verified relay chain state proof. + fn on_relay_state_proof(relay_state_proof: &relay_state_snapshot::RelayChainStateProof) -> Weight; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl OnSystemEvent for Tuple { + fn on_validation_data(data: &PersistedValidationData) { + for_tuples!( #( Tuple::on_validation_data(data); )* ); + } + + fn on_validation_code_applied() { + for_tuples!( #( Tuple::on_validation_code_applied(); )* ); + } + + fn on_relay_state_proof(relay_state_proof: &relay_state_snapshot::RelayChainStateProof) -> Weight { + let mut weight = Weight::zero(); + for_tuples!( #( weight = weight.saturating_add(Tuple::on_relay_state_proof(relay_state_proof)); )* ); + weight + } } /// Holds the most recent relay-parent state root and block number of the current parachain block. diff --git a/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs b/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs index 7138d61edd277..e9c32997f9760 100644 --- a/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs +++ b/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs @@ -383,4 +383,20 @@ impl RelayChainStateProof { { read_optional_entry(&self.trie_backend, key).map_err(Error::ReadOptionalEntry) } + + /// Read a value from a child trie in the relay chain state proof. + /// + /// Returns `Ok(Some(value))` if the key exists in the child trie, + /// `Ok(None)` if the key doesn't exist, + /// or `Err` if there was a proof error. + pub fn read_child_storage( + &self, + child_info: &sp_core::storage::ChildInfo, + key: &[u8], + ) -> Result>, Error> { + use sp_state_machine::Backend; + self.trie_backend + .child_storage(child_info, key) + .map_err(|_| Error::ReadEntry(ReadEntryErr::Proof)) + } } diff --git a/cumulus/pallets/solo-to-para/src/lib.rs b/cumulus/pallets/solo-to-para/src/lib.rs index ff68d1b63fe7f..0d6d82cf4590b 100644 --- a/cumulus/pallets/solo-to-para/src/lib.rs +++ b/cumulus/pallets/solo-to-para/src/lib.rs @@ -103,5 +103,10 @@ pub mod pallet { fn on_validation_code_applied() { crate::Pallet::::set_pending_custom_validation_head_data(); } + fn on_relay_state_proof( + _relay_state_proof: ¶chain_system::relay_state_snapshot::RelayChainStateProof, + ) -> frame_support::weights::Weight { + frame_support::weights::Weight::zero() + } } } diff --git a/cumulus/polkadot-omni-node/lib/src/common/aura.rs b/cumulus/polkadot-omni-node/lib/src/common/aura.rs index 9ca725ff3279a..b6f156f96dfdb 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/aura.rs @@ -18,6 +18,7 @@ use codec::Codec; use cumulus_primitives_aura::AuraUnincludedSegmentApi; +use cumulus_primitives_core::KeyToIncludeInRelayProof; use sp_consensus_aura::AuraApi; use sp_runtime::{ app_crypto::{AppCrypto, AppPair, AppSignature, Pair}, @@ -53,6 +54,7 @@ pub trait AuraRuntimeApi: sp_api::ApiExt + AuraApi::Public> + AuraUnincludedSegmentApi + + KeyToIncludeInRelayProof + Sized { /// Check if the runtime has the Aura API. @@ -66,5 +68,6 @@ impl AuraRuntimeApi for T wher T: sp_api::ApiExt + AuraApi::Public> + AuraUnincludedSegmentApi + + KeyToIncludeInRelayProof { } diff --git a/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs index 56eb3d2ae7602..1f6dfa177e96d 100644 --- a/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs +++ b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs @@ -15,7 +15,7 @@ // limitations under the License. pub(crate) mod imports { - pub use cumulus_primitives_core::ParaId; + pub use cumulus_primitives_core::{ParaId, RelayProofRequest}; pub use parachains_common_types::{AccountId, Balance, Nonce}; pub use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; pub use sp_runtime::{ @@ -175,6 +175,13 @@ macro_rules! impl_node_runtime_apis { unimplemented!() } } + + impl cumulus_primitives_core::KeyToIncludeInRelayProof<$block> for $runtime { + fn keys_to_prove() -> RelayProofRequest { + unimplemented!() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime<$block> for $runtime { fn on_runtime_upgrade( diff --git a/cumulus/primitives/core/src/lib.rs b/cumulus/primitives/core/src/lib.rs index 774961b6b7e6b..a97d58db7e0a9 100644 --- a/cumulus/primitives/core/src/lib.rs +++ b/cumulus/primitives/core/src/lib.rs @@ -466,6 +466,33 @@ pub struct CollationInfo { pub head_data: HeadData, } +/// A relay chain storage key to be included in the storage proof. +#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, Eq)] +pub enum RelayStorageKey { + /// Top-level relay chain storage key. + Top(Vec), + /// Child trie storage key. + Child { + /// Unprefixed storage key identifying the child trie root location. + /// Prefix `:child_storage:default:` is added when accessing storage. + /// Used to derive `ChildInfo` for reading child trie data. + /// Usage: let child_info = ChildInfo::new_default(&storage_key); + storage_key: Vec, + /// Key within the child trie. + key: Vec, + }, +} + +/// Request for proving relay chain storage data. +/// +/// Contains a list of storage keys (either top-level or child trie keys) +/// to be included in the relay chain state proof. +#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, Eq, Default)] +pub struct RelayProofRequest { + /// Storage keys to include in the relay chain state proof. + pub keys: Vec, +} + sp_api::decl_runtime_apis! { /// Runtime api to collect information about a collation. /// @@ -513,4 +540,15 @@ sp_api::decl_runtime_apis! { /// Returns the target number of blocks per relay chain slot. fn target_block_rate() -> u32; } + + /// API for specifying which relay chain storage data to include in storage proofs. + /// + /// This API allows parachains to request both top-level relay chain storage keys + /// and child trie storage keys to be included in the relay chain state proof. + pub trait KeyToIncludeInRelayProof { + /// Returns relay chain storage proof requests. + /// + /// The collator will include them in the relay chain proof that is passed alongside the parachain inherent into the runtime. + fn keys_to_prove() -> RelayProofRequest; + } } diff --git a/cumulus/test/relay-sproof-builder/Cargo.toml b/cumulus/test/relay-sproof-builder/Cargo.toml index 2ae2d9bf00034..a7ea2472bb290 100644 --- a/cumulus/test/relay-sproof-builder/Cargo.toml +++ b/cumulus/test/relay-sproof-builder/Cargo.toml @@ -17,6 +17,8 @@ codec = { features = ["derive"], workspace = true } # Substrate sp-consensus-babe = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-runtime = { workspace = true } sp-state-machine = { workspace = true } sp-trie = { workspace = true } @@ -38,6 +40,8 @@ std = [ "polkadot-primitives/std", "sp-consensus-babe/std", "sp-core/std", + "sp-io/std", + "sp-keyring/std", "sp-runtime/std", "sp-state-machine/std", "sp-trie/std", diff --git a/cumulus/test/relay-sproof-builder/src/lib.rs b/cumulus/test/relay-sproof-builder/src/lib.rs index ee8cd92fd9d93..0c19be49cfac8 100644 --- a/cumulus/test/relay-sproof-builder/src/lib.rs +++ b/cumulus/test/relay-sproof-builder/src/lib.rs @@ -247,6 +247,9 @@ impl RelayStateSproofBuilder { ); insert(relay_chain::well_known_keys::CURRENT_SLOT.to_vec(), self.current_slot.encode()); + let (alice_key, alice_data) = generate_alice_account(); + insert(alice_key, alice_data); + for (key, value) in self.additional_key_values { insert(key, value); } @@ -273,6 +276,21 @@ fn convert_to_authority_weight_pair( .collect() } +/// Include to avoid `KeyToIncludeInRelayProof` test on test-pallet to break unit tests. +fn generate_alice_account() -> (Vec, Vec) { + use codec::Encode; + use sp_keyring::Sr25519Keyring; + + let alice = Sr25519Keyring::Alice.to_account_id(); + let mut alice_key = sp_io::hashing::twox_128(b"System").to_vec(); + alice_key.extend_from_slice(&sp_io::hashing::twox_128(b"Account")); + alice_key.extend_from_slice(&sp_io::hashing::blake2_128(&alice.encode())); + alice_key.extend_from_slice(&alice.encode()); + + let alice_account = (0u32, 0u32, 1u32, 0u32, 25_000_000_000_000u128, 0u128, 0u128, 0u128); + (alice_key, alice_account.encode()) +} + /// Add a BABE pre-digest to a generic header fn add_babe_pre_digest(header: &mut Header, authority_index: u32, block_number: u64) { /// This method generates some vrf data, but only to make the compiler happy diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index cc8142ff2dedb..703e346582891 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -41,6 +41,9 @@ sp-session = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } +# Polkadot +polkadot-primitives = { workspace = true } + # Cumulus cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } @@ -75,6 +78,7 @@ std = [ "pallet-timestamp/std", "pallet-transaction-payment/std", "parachain-info/std", + "polkadot-primitives/std", "scale-info/std", "serde_json/std", "sp-api/std", diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index dfe37e10d05d7..ea000d975e753 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -87,7 +87,7 @@ use sp_runtime::{ use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use cumulus_primitives_core::ParaId; +use cumulus_primitives_core::{ParaId, RelayProofRequest}; // A few exports that help ease life for downstream crates. pub use frame_support::{ @@ -374,7 +374,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type WeightInfo = (); type SelfParaId = parachain_info::Pallet; type RuntimeEvent = RuntimeEvent; - type OnSystemEvent = (); + type OnSystemEvent = TestPallet; type OutboundXcmpMessageSource = (); // Ignore all DMP messages by enqueueing them into `()`: type DmpQueue = frame_support::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>; @@ -641,6 +641,19 @@ impl_runtime_apis! { 1 } } + + impl cumulus_primitives_core::KeyToIncludeInRelayProof for Runtime { + fn keys_to_prove() -> cumulus_primitives_core::RelayProofRequest { + use cumulus_primitives_core::RelayStorageKey; + + RelayProofRequest { + keys: vec![ + // Request a key to verify its inclusion in the proof. + RelayStorageKey::Top(test_pallet::relay_alice_account_key()), + ], + } + } + } } cumulus_pallet_parachain_system::register_validate_block! { diff --git a/cumulus/test/runtime/src/test_pallet.rs b/cumulus/test/runtime/src/test_pallet.rs index a972198c300d9..7f5d6b8cbd0ab 100644 --- a/cumulus/test/runtime/src/test_pallet.rs +++ b/cumulus/test/runtime/src/test_pallet.rs @@ -17,10 +17,25 @@ /// A special pallet that exposes dispatchables that are only useful for testing. pub use pallet::*; +use codec::Encode; + /// Some key that we set in genesis and only read in [`TestOnRuntimeUpgrade`] to ensure that /// [`OnRuntimeUpgrade`] works as expected. pub const TEST_RUNTIME_UPGRADE_KEY: &[u8] = b"+test_runtime_upgrade_key+"; +/// Generates the storage key for Alice's account on the relay chain. +pub fn relay_alice_account_key() -> alloc::vec::Vec { + use sp_keyring::Sr25519Keyring; + + let alice = Sr25519Keyring::Alice.to_account_id(); + + let mut key = sp_io::hashing::twox_128(b"System").to_vec(); + key.extend_from_slice(&sp_io::hashing::twox_128(b"Account")); + key.extend_from_slice(&sp_io::hashing::blake2_128(&alice.encode())); + key.extend_from_slice(&alice.encode()); + key +} + #[frame_support::pallet(dev_mode)] pub mod pallet { use crate::test_pallet::TEST_RUNTIME_UPGRADE_KEY; @@ -121,3 +136,31 @@ pub mod pallet { } } } + +impl cumulus_pallet_parachain_system::OnSystemEvent for Pallet { + fn on_validation_data(_data: &cumulus_primitives_core::PersistedValidationData) { + // Nothing to do here for tests + } + + fn on_validation_code_applied() { + // Nothing to do here for tests + } + + fn on_relay_state_proof( + relay_state_proof: &cumulus_pallet_parachain_system::relay_state_snapshot::RelayChainStateProof, + ) -> frame_support::weights::Weight { + use crate::{Balance, Nonce}; + use frame_system::AccountInfo; + use pallet_balances::AccountData; + + let alice_key = crate::test_pallet::relay_alice_account_key(); + + // Verify that Alice's account is included in the relay proof. + relay_state_proof + .read_optional_entry::>>(&alice_key) + .expect("Invalid relay chain state proof") + .expect("Alice's account must be present in the relay proof"); + + frame_support::weights::Weight::zero() + } +}