Skip to content
Open
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions cumulus/client/consensus/aura/src/collator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -177,6 +178,7 @@ where
parent_hash: Block::Hash,
timestamp: impl Into<Option<Timestamp>>,
relay_parent_descendants: Option<RelayParentData>,
relay_proof_request: RelayProofRequest,
collator_peer_id: PeerId,
) -> Result<(ParachainInherentData, InherentData), Box<dyn Error + Send + Sync + 'static>> {
let paras_inherent_data = ParachainInherentDataProvider::create_at(
Expand All @@ -188,6 +190,7 @@ where
.map(RelayParentData::into_inherent_descendant_list)
.unwrap_or_default(),
Vec::new(),
relay_proof_request,
collator_peer_id,
)
.await;
Expand Down Expand Up @@ -224,6 +227,7 @@ where
validation_data: &PersistedValidationData,
parent_hash: Block::Hash,
timestamp: impl Into<Option<Timestamp>>,
relay_proof_request: RelayProofRequest,
collator_peer_id: PeerId,
) -> Result<(ParachainInherentData, InherentData), Box<dyn Error + Send + Sync + 'static>> {
self.create_inherent_data_with_rp_offset(
Expand All @@ -232,6 +236,7 @@ where
parent_hash,
timestamp,
None,
relay_proof_request,
collator_peer_id,
)
.await
Expand Down
1 change: 1 addition & 0 deletions cumulus/client/consensus/aura/src/collators/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ where
&validation_data,
parent_hash,
claim.timestamp(),
Default::default(),
params.collator_peer_id,
)
.await
Expand Down
17 changes: 12 additions & 5 deletions cumulus/client/consensus/aura/src/collators/lookahead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -164,8 +164,10 @@ where
+ Send
+ Sync
+ 'static,
Client::Api:
AuraApi<Block, P::Public> + CollectCollationInfo<Block> + AuraUnincludedSegmentApi<Block>,
Client::Api: AuraApi<Block, P::Public>
+ CollectCollationInfo<Block>
+ AuraUnincludedSegmentApi<Block>
+ KeyToIncludeInRelayProof<Block>,
Backend: sc_client_api::Backend<Block> + 'static,
RClient: RelayChainInterface + Clone + 'static,
CIDP: CreateInherentDataProviders<Block, ()> + 'static,
Expand Down Expand Up @@ -216,8 +218,10 @@ where
+ Send
+ Sync
+ 'static,
Client::Api:
AuraApi<Block, P::Public> + CollectCollationInfo<Block> + AuraUnincludedSegmentApi<Block>,
Client::Api: AuraApi<Block, P::Public>
+ CollectCollationInfo<Block>
+ AuraUnincludedSegmentApi<Block>
+ KeyToIncludeInRelayProof<Block>,
Backend: sc_client_api::Backend<Block> + 'static,
RClient: RelayChainInterface + Clone + 'static,
CIDP: CreateInherentDataProviders<Block, ()> + 'static,
Expand Down Expand Up @@ -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
Expand Down
32 changes: 31 additions & 1 deletion cumulus/client/consensus/aura/src/collators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Block, Client>(
client: &Client,
parent_hash: Block::Hash,
) -> RelayProofRequest
where
Block: BlockT,
Client: ProvideRuntimeApi<Block>,
Client::Api: KeyToIncludeInRelayProof<Block>,
{
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,14 +359,14 @@ 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,
&validation_data,
parent_hash,
slot_claim.timestamp(),
Some(rp_data),
Default::default(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should this not be properly implemented for the slot-based collator?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks for the review.
The reasoning was that while the API is generic, its development is driven by a concrete use case (#10679 ). I didn’t include a slot-based implementation, as I saw no immediate application and the goal was to keep the PR functional and as lean as possible.
It is something that could be added in a future iteration if needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the use-case. But all parachains that use elastic scaling like asset-hub use the slot-based collator. An implementation for it is absolutely necessary. Both lookahead and slot-based are currently in use for parachains.

collator_peer_id,
)
.await
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>],
) -> RelayChainResult<sc_client_api::StorageProof> {
unimplemented!("Not needed for test")
}

async fn wait_for_block(&self, _: RelayHash) -> RelayChainResult<()> {
unimplemented!("Not needed for test")
}
Expand Down
105 changes: 89 additions & 16 deletions cumulus/client/parachain-inherent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>>,
) -> Option<sp_state_machine::StorageProof> {
) -> Option<Vec<Vec<u8>>> {
use relay_chain::well_known_keys as relay_well_known_keys;

let ingress_channels = relay_chain_interface
Expand Down Expand Up @@ -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<Vec<u8>>,
relay_proof_request: RelayProofRequest,
Comment on lines +151 to +152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it would make sense to merge additional_relay_state_keys into relay_proof_request. I get that they have different origins, but this method here does not really case. Both of these arguments take a list of key and want them added to the proof. That they do the same thing just causes confusion IMO

Copy link
Author

@metricaez metricaez Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid, and I did consider this during the implementation, but kept them separate due to their different origins and trying to minimize impact on previous flows.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end both are about adding new storage keys. Downstream users need to adjust anyway due to the new type and argument here, keeping both is just redundant.

) -> Option<StorageProof> {
// 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<Vec<u8>> = 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<u8>, Vec<Vec<u8>>> =
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;
Expand All @@ -170,6 +241,7 @@ impl ParachainInherentDataProvider {
para_id: ParaId,
relay_parent_descendants: Vec<RelayHeader>,
additional_relay_state_keys: Vec<Vec<u8>>,
relay_proof_request: RelayProofRequest,
collator_peer_id: PeerId,
) -> Option<ParachainInherentData> {
let collator_peer_id = ApprovedPeerId::try_from(collator_peer_id.to_bytes())
Expand All @@ -195,6 +267,7 @@ impl ParachainInherentDataProvider {
!relay_parent_descendants.is_empty(),
include_next_authorities,
additional_relay_state_keys,
relay_proof_request,
)
.await?;

Expand Down
16 changes: 15 additions & 1 deletion cumulus/client/relay-chain-inprocess-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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<u8>],
) -> RelayChainResult<StorageProof> {
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
Expand Down
1 change: 1 addition & 0 deletions cumulus/client/relay-chain-interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
Loading
Loading