diff --git a/Cargo.lock b/Cargo.lock index fe5d436aa5e53..7f221270db13b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4877,6 +4877,7 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-state-machine", + "sp-storage 19.0.0", "sp-version", "thiserror 1.0.65", ] @@ -5010,6 +5011,8 @@ dependencies = [ "proptest", "sp-consensus-babe", "sp-core 28.0.0", + "sp-io", + "sp-keyring", "sp-runtime", "sp-state-machine", "sp-trie", @@ -5038,6 +5041,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 17c95bed2f2e5..c3fae7567cd21 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( @@ -187,7 +189,7 @@ where relay_parent_descendants .map(RelayParentData::into_inherent_descendant_list) .unwrap_or_default(), - Vec::new(), + relay_proof_request, collator_peer_id, ) .await; @@ -225,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( @@ -233,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 55b35e2930941..83ff355594115 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -239,6 +239,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 8c11c726a5a24..7bce12a31618b 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -36,7 +36,9 @@ 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 +166,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 +220,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 +398,16 @@ 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 dfd97963331b0..5af3ac81465f2 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; @@ -649,6 +651,31 @@ 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 4ea245d669808..816c6c39f9135 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 @@ -35,7 +35,7 @@ use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockIm use cumulus_primitives_aura::{AuraUnincludedSegmentApi, Slot}; use cumulus_primitives_core::{ extract_relay_parent, rpsr_digest, ClaimQueueOffset, CoreInfo, CoreSelector, CumulusDigestItem, - PersistedValidationData, RelayParentOffsetApi, + KeyToIncludeInRelayProof, PersistedValidationData, RelayParentOffsetApi, }; use cumulus_relay_chain_interface::RelayChainInterface; use futures::prelude::*; @@ -127,8 +127,10 @@ where + Send + Sync + 'static, - Client::Api: - AuraApi + RelayParentOffsetApi + AuraUnincludedSegmentApi, + Client::Api: AuraApi + + RelayParentOffsetApi + + AuraUnincludedSegmentApi + + KeyToIncludeInRelayProof, Backend: sc_client_api::Backend + 'static, RelayClient: RelayChainInterface + Clone + 'static, CIDP: CreateInherentDataProviders + 'static, @@ -360,6 +362,9 @@ where max_pov_size: *max_pov_size, }; + let relay_proof_request = + super::super::get_relay_proof_request(&*para_client, parent_hash); + let (parachain_inherent_data, other_inherent_data) = match collator .create_inherent_data_with_rp_offset( relay_parent, @@ -367,6 +372,7 @@ where parent_hash, slot_claim.timestamp(), Some(rp_data), + relay_proof_request, collator_peer_id, ) .await diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs index 39e0bbdb41d73..71d1f5d931904 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs @@ -73,7 +73,7 @@ use consensus_common::ParachainCandidate; 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::RelayParentOffsetApi; +use cumulus_primitives_core::{KeyToIncludeInRelayProof, RelayParentOffsetApi}; use cumulus_relay_chain_interface::RelayChainInterface; use futures::FutureExt; use polkadot_primitives::{ @@ -165,8 +165,10 @@ pub fn run + AuraUnincludedSegmentApi + RelayParentOffsetApi, + Client::Api: AuraApi + + AuraUnincludedSegmentApi + + RelayParentOffsetApi + + KeyToIncludeInRelayProof, Backend: sc_client_api::Backend + 'static, RClient: RelayChainInterface + Clone + 'static, CIDP: CreateInherentDataProviders + 'static, 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/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs index 3434a2fedc46d..c26bbe0ba6ef8 100644 --- a/cumulus/client/consensus/common/src/tests.rs +++ b/cumulus/client/consensus/common/src/tests.rs @@ -26,8 +26,9 @@ use cumulus_primitives_core::{ CumulusDigestItem, InboundDownwardMessage, InboundHrmpMessage, PersistedValidationData, }; use cumulus_relay_chain_interface::{ - CommittedCandidateReceipt, CoreIndex, OccupiedCoreAssumption, OverseerHandle, PHeader, ParaId, - RelayChainInterface, RelayChainResult, SessionIndex, StorageValue, ValidatorId, + ChildInfo, CommittedCandidateReceipt, CoreIndex, OccupiedCoreAssumption, OverseerHandle, + PHeader, ParaId, RelayChainInterface, RelayChainResult, SessionIndex, StorageValue, + ValidatorId, }; use cumulus_test_client::{ runtime::{Block, Hash, Header}, @@ -218,6 +219,15 @@ impl RelayChainInterface for Relaychain { unimplemented!("Not needed for test") } + async fn prove_child_read( + &self, + _: PHash, + _: &ChildInfo, + _: &[Vec], + ) -> RelayChainResult { + unimplemented!("Not needed for test") + } + async fn wait_for_block(&self, _: PHash) -> RelayChainResult<()> { Ok(()) } diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index d0c22de457a06..a622fe93dd409 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -20,7 +20,7 @@ use async_trait::async_trait; use cumulus_primitives_core::relay_chain::{BlockId, CoreIndex}; use cumulus_relay_chain_inprocess_interface::{check_block_in_chain, BlockCheckStatus}; use cumulus_relay_chain_interface::{ - OverseerHandle, PHeader, ParaId, RelayChainError, RelayChainResult, + ChildInfo, OverseerHandle, PHeader, ParaId, RelayChainError, RelayChainResult, }; use cumulus_test_service::runtime::{Block, Hash, Header}; use futures::{executor::block_on, poll, task::Poll, FutureExt, Stream, StreamExt}; @@ -245,6 +245,15 @@ impl RelayChainInterface for DummyRelayChainInterface { unimplemented!("Not needed for test") } + async fn prove_child_read( + &self, + _: PHash, + _: &ChildInfo, + _: &[Vec], + ) -> RelayChainResult { + unimplemented!("Not needed for test") + } + async fn wait_for_block(&self, hash: PHash) -> RelayChainResult<()> { let mut listener = match check_block_in_chain( self.relay_backend.clone(), diff --git a/cumulus/client/parachain-inherent/src/lib.rs b/cumulus/client/parachain-inherent/src/lib.rs index 5e994cd472f70..62da74ddc2c04 100644 --- a/cumulus/client/parachain-inherent/src/lib.rs +++ b/cumulus/client/parachain-inherent/src/lib.rs @@ -18,31 +18,34 @@ mod mock; +use std::collections::{HashMap, HashSet}; + use codec::Decode; use cumulus_primitives_core::{ relay_chain::{ 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 @@ -102,7 +105,7 @@ async fn collect_relay_storage_proof( .ok()? .unwrap_or_default(); - let mut relevant_keys = vec![ + let mut relevant_keys: HashSet> = HashSet::from([ relay_well_known_keys::CURRENT_BLOCK_RANDOMNESS.to_vec(), relay_well_known_keys::ONE_EPOCH_AGO_RANDOMNESS.to_vec(), relay_well_known_keys::TWO_EPOCHS_AGO_RANDOMNESS.to_vec(), @@ -120,7 +123,7 @@ async fn collect_relay_storage_proof( relay_well_known_keys::upgrade_go_ahead_signal(para_id), relay_well_known_keys::upgrade_restriction_signal(para_id), relay_well_known_keys::para_head(para_id), - ]; + ]); relevant_keys.extend(ingress_channels.into_iter().map(|sender| { relay_well_known_keys::hrmp_channels(HrmpChannelId { sender, recipient: para_id }) })); @@ -129,32 +132,96 @@ async fn collect_relay_storage_proof( })); if include_authorities { - relevant_keys.push(relay_well_known_keys::AUTHORITIES.to_vec()); + relevant_keys.insert(relay_well_known_keys::AUTHORITIES.to_vec()); } if include_next_authorities { - relevant_keys.push(relay_well_known_keys::NEXT_AUTHORITIES.to_vec()); + relevant_keys.insert(relay_well_known_keys::NEXT_AUTHORITIES.to_vec()); } - // Add additional relay state keys - let unique_keys: Vec> = additional_relay_state_keys - .into_iter() - .filter(|key| !relevant_keys.contains(key)) - .collect(); - relevant_keys.extend(unique_keys); + Some(relevant_keys) +} - relay_chain_interface - .prove_read(relay_parent, &relevant_keys) - .await - .map_err(|e| { +/// 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, + 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?; + + // Group requested keys by storage type. + let RelayProofRequest { keys } = relay_proof_request; + let mut child_keys: HashMap, HashSet>> = HashMap::new(); + + for key in keys { + match key { + RelayStorageKey::Top(k) => { + all_top_keys.insert(k); + }, + RelayStorageKey::Child { storage_key, key } => { + child_keys.entry(storage_key).or_default().insert(key); + }, + } + } + + // Collect all storage proofs. + let mut all_proofs = Vec::new(); + + // Collect top-level storage proof. + let top_keys_vec: Vec> = all_top_keys.into_iter().collect(); + match relay_chain_interface.prove_read(relay_parent, &top_keys_vec).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); + let data_keys_vec: Vec> = data_keys.into_iter().collect(); + match relay_chain_interface + .prove_child_read(relay_parent, &child_info, &data_keys_vec) + .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; @@ -169,7 +236,7 @@ impl ParachainInherentDataProvider { validation_data: &PersistedValidationData, 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()) @@ -194,7 +261,7 @@ impl ParachainInherentDataProvider { relay_parent, !relay_parent_descendants.is_empty(), include_next_authorities, - additional_relay_state_keys, + relay_proof_request, ) .await?; diff --git a/cumulus/client/pov-recovery/src/tests.rs b/cumulus/client/pov-recovery/src/tests.rs index 574632c96454d..25de9884ebfb8 100644 --- a/cumulus/client/pov-recovery/src/tests.rs +++ b/cumulus/client/pov-recovery/src/tests.rs @@ -22,7 +22,7 @@ use cumulus_primitives_core::relay_chain::{ BlockId, CandidateCommitments, CandidateDescriptorV2, CoreIndex, CoreState, }; use cumulus_relay_chain_interface::{ - InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, PHash, PHeader, + ChildInfo, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, PHash, PHeader, PersistedValidationData, RelayChainResult, StorageValue, ValidationCodeHash, ValidatorId, }; use cumulus_test_client::runtime::{Block, Header}; @@ -470,6 +470,15 @@ impl RelayChainInterface for Relaychain { unimplemented!("Not needed for test") } + async fn prove_child_read( + &self, + _: PHash, + _: &ChildInfo, + _: &[Vec], + ) -> RelayChainResult { + unimplemented!("Not needed for test") + } + async fn wait_for_block(&self, _: PHash) -> RelayChainResult<()> { unimplemented!("Not needed for test"); } diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 9fb50a88c73ee..af20bafe921d1 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::{ @@ -241,6 +243,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 e523a47f69351..daaf75bc76898 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 97edf9ded40cd..6f8f37f7b8121 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -702,6 +702,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 { @@ -1812,13 +1816,35 @@ 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 ea6cae459355d..75687d0c3e481 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 e97af794c38e6..9dcb807571e04 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 bbb45cc4f30b5..f16598e6e0224 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/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 011fbc1613c0e..8d17cdfe9b711 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..81a31b15ab970 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,30 @@ 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"); + + frame_support::weights::Weight::zero() + } +} diff --git a/prdoc/pr_10678.prdoc b/prdoc/pr_10678.prdoc new file mode 100644 index 0000000000000..46cb8db03e273 --- /dev/null +++ b/prdoc/pr_10678.prdoc @@ -0,0 +1,43 @@ +title: Add relay chain state proof API for parachains +doc: + - audience: Runtime Dev + description: | + Adds `KeyToIncludeInRelayProof` runtime API allowing parachains to request specific + relay chain storage keys, including child tries, to be proven. Parachains can access + this verified state via `OnSystemEvent::on_relay_state_proof()` hook. + + - audience: Node Dev + description: | + Collators now query the runtime for additional relay chain keys to prove. Adds + `prove_child_read()` to relay chain interfaces for child trie proofs. + + `additional_relay_state_keys` parameter removed from parachain inherent data creation. + If needed, extend `RelayProofRequest.keys` directly at collator level, for example: + ```rust + relay_proof_request.keys.extend( + my_additional_keys.into_iter().map(RelayStorageKey::Top) + ); + ``` +crates: + - name: cumulus-primitives-core + bump: minor + - name: cumulus-relay-chain-interface + bump: major + - name: cumulus-relay-chain-rpc-interface + bump: minor + - name: cumulus-relay-chain-inprocess-interface + bump: minor + - name: cumulus-client-parachain-inherent + bump: major + - name: cumulus-client-consensus-aura + bump: major + - name: cumulus-pallet-parachain-system + bump: major + - name: cumulus-pallet-solo-to-para + bump: minor + - name: cumulus-test-relay-sproof-builder + bump: patch + - name: cumulus-test-runtime + bump: patch + - name: polkadot-omni-node-lib + bump: major diff --git a/templates/parachain/runtime/src/apis.rs b/templates/parachain/runtime/src/apis.rs index 74b5d4abd5213..e029827debaac 100644 --- a/templates/parachain/runtime/src/apis.rs +++ b/templates/parachain/runtime/src/apis.rs @@ -332,4 +332,10 @@ impl_runtime_apis! { parachain_info::Pallet::::parachain_id() } } + + impl cumulus_primitives_core::KeyToIncludeInRelayProof for Runtime { + fn keys_to_prove() -> cumulus_primitives_core::RelayProofRequest { + Default::default() + } + } }