Skip to content

Conversation

@metricaez
Copy link

@metricaez metricaez commented Dec 17, 2025

Purpose

This pull request introduces a new runtime API and implements the full feature pipeline for requesting additional relay-chain storage proofs in lookahead collators. The API allows parachain runtimes to specify extra top-level storage keys or child-trie data that must be included in the relay-chain state proof. The collator collects these additional proofs and merges them into the relay-chain state proof provided to the runtime during block execution, enabling the runtime to later process custom relay-chain data.

Rationale

Immediate application in pubsub mechanism proposed in #9994

This is a narrow down of scope for easier review of PR #10679

Due to early exits when defaulted it adds no significant overhead to current flows.

What this PR adds

Runtime API

  • Introduces KeyToIncludeInRelayProofApi. (Suggestions for better naming are very welcome.)

  • Adds supporting types RelayProofRequest and RelayStorageKey.

  • Allows runtimes to declare which relay-chain storage entries must be included in the relay state proof.

Collator integration

  • The lookahead collator calls the runtime API before block production.

  • Requested relay-chain proofs are collected, batched, and merged in a single operation.

  • The additional proofs are merged into the existing relay-chain state proof and passed to the runtime via parachain inherent data.

Proof extraction

  • parachain-system exposes an extraction method for processing this additional proofs.

  • Uses a handler pattern:

    • parachain-system manages proof lifecycle and initial validation.

    • Application pallets consume proofs (data extraction or additional validation) by implementing ProcessRelayProofKeys.

  • Keeps extra proofs processing logic out of parachain-system.

About RelayStorageKey

RelayStorageKey is an enum with two variants:

  • Top: a Vec<u8> representing a top-level relay-chain storage key.

  • Child, which contains:

    • storage_key: an unprefixed identifier of the child trie root (the default :child_storage:default: prefix is applied automatically),

    • key: the specific key within that child trie.

On the client side, child trie access is performed via ChildInfo::new_default(&storage_key).

Why storage_key instead of ChildInfo:

  • ChildInfo from sp-storage does not implement TypeInfo, which runtime APIs require.

  • Adding TypeInfo to sp-storage (or introducing a wrapper to avoid bloating a critical core component like sp-storage) would significantly expand the scope of this PR.

As a result, the current design:

  • Uses raw storage_key bytes.

  • Is limited to child tries using the default prefix.

Future improvements

  • Full ChildInfo support if TypeInfo is added to sp-storage (directly or via a wrapper), enabling arbitrary child-trie prefixes.

  • Possible unification with additional_relay_state_keys for top-level proofs, subject to careful analysis of semantics and backward compatibility.

  • Integration with additional collator implementations beyond lookahead collators.

Copy link
Member

@bkchr bkchr left a comment

Choose a reason for hiding this comment

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

Mostly looking good, just some small improvements.

Comment on lines 220 to 224
// Not implemented: requires relay chain RPC to expose child trie proof method.
tracing::warn!(
target: "relay-chain-rpc-interface",
"prove_child_read not implemented for RPC interface, returning empty proof"
);
Copy link
Member

Choose a reason for hiding this comment

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

And it is not doing this?

state_getChildReadProof.

Copy link
Author

Choose a reason for hiding this comment

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

True, missed that one, done.

///
/// 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 KeyToIncludeInRelayProofApi {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pub trait KeyToIncludeInRelayProofApi {
pub trait KeyToIncludeInRelayProof {

Comment on lines 551 to 556
/// The returned `RelayProofRequest` contains a list of storage keys where each key
/// can be either:
/// - `RelayStorageKey::Top`: Top-level relay chain storage key
/// - `RelayStorageKey::Child`: Child trie storage, containing the child trie identifier
/// and the key to prove from that child trie
///
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// The returned `RelayProofRequest` contains a list of storage keys where each key
/// can be either:
/// - `RelayStorageKey::Top`: Top-level relay chain storage key
/// - `RelayStorageKey::Child`: Child trie storage, containing the child trie identifier
/// and the key to prove from that child trie
///

/// - `RelayStorageKey::Child`: Child trie storage, containing the child trie identifier
/// and the key to prove from that child trie
///
/// The collator generates proofs for these and includes them in the relay chain state proof.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// The collator generates proofs for these and includes them in the relay chain state proof.
/// The collator will include them in the relay chain proof that is passed alongside the parachain inherent into the runtime.

Comment on lines 169 to 173
async fn collect_additional_storage_proofs(
relay_chain_interface: &impl RelayChainInterface,
relay_parent: PHash,
relay_proof_request: RelayProofRequest,
) -> Option<StorageProof> {
Copy link
Member

Choose a reason for hiding this comment

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

I don't get why we have two functions doing almost the same.

IMO we should have one function that returns us the "static keys" (aka what we used so far and then we will join them with the additional keys to download all at once)

Copy link
Author

Choose a reason for hiding this comment

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

Did it just for the sake of keeping logics more isolated but I have unified with the suggested approach and we reduced a prove_read call which is nice.

Comment on lines 268 to 273
/// Processor for relay chain proof keys.
///
/// This allows parachains to process data from the relay chain state proof,
/// including both child trie keys and main trie keys that were requested
/// via `KeyToIncludeInRelayProofApi`.
type RelayProofKeysProcessor: relay_state_snapshot::ProcessRelayProofKeys;
Copy link
Member

Choose a reason for hiding this comment

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

Just merge this with OnSystemEvent. Then we don't need a new type here.

Copy link
Author

Choose a reason for hiding this comment

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

Done :)


impl cumulus_primitives_core::KeyToIncludeInRelayProofApi<Block> for Runtime {
fn keys_to_prove() -> RelayProofRequest {
Default::default()
Copy link
Member

Choose a reason for hiding this comment

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

Could you return here some key that isn't read up to now. And then add some logic to the test_pallet that asserts that the key is part of the proof.
As we use the test runtime in zombienet tests, it will be automatically tested there for us.

Copy link
Author

Choose a reason for hiding this comment

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

Done!

@metricaez metricaez requested a review from bkchr December 22, 2025 03:09
@metricaez metricaez force-pushed the feat/keys-to-proof-api branch from ceed7ce to d40f024 Compare December 22, 2025 13:25
Copy link
Member

@bkchr bkchr left a comment

Choose a reason for hiding this comment

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

Some last nitpicks, otherwise looks good.

Comment on lines 685 to 692
.unwrap_or_else(|e| {
tracing::warn!(
target: crate::LOG_TARGET,
error = ?e,
"Failed to fetch relay proof requests from runtime, using empty request"
);
Default::default()
})
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.unwrap_or_else(|e| {
tracing::warn!(
target: crate::LOG_TARGET,
error = ?e,
"Failed to fetch relay proof requests from runtime, using empty request"
);
Default::default()
})
.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()
})

The API may not exists and then we clearly don't want to see these warnings :)

Comment on lines 704 to 705
total_weight.saturating_accrue(
<T::OnSystemEvent as OnSystemEvent>::on_relay_state_proof(&relay_state_proof),
Copy link
Member

Choose a reason for hiding this comment

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

FMT

pub trait KeyToIncludeInRelayProof {
/// Returns relay chain storage proof requests.
///
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change


// Expect the requested key to be part of the proof.
relay_state_proof
.read_optional_entry::<u64>(RELAY_EPOCH_INDEX_KEY)
Copy link
Member

Choose a reason for hiding this comment

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

My point was to use a key that isn't by default part of the proof, this is already part of the proof. Let's take the balance key for ALICE for example.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I understand it, I picked EPOCH_INDEX because I didn't realize it was already included because it was not part of the static keys. Already switched to ALICE System key and had to add to sproof builder for it not to break any unit test. Also tested and working as expected :)

@github-actions github-actions bot requested a review from bkchr December 24, 2025 11:14
@github-actions
Copy link
Contributor

Review required! Latest push from author must always be reviewed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants