cumulus_primitives_core/scheduling.rs
1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5//! V3 scheduling types for low-latency parachain block production.
6//!
7//! V3 candidates separate the relay parent (execution context) from the scheduling
8//! parent (a recent relay chain tip used for core assignment). This enables building
9//! on older relay parents while still being scheduled based on recent relay state.
10//!
11//! # Resubmission
12//!
13//! When a candidate fails to get backed in time, a different collator can resubmit
14//! it with a new `scheduling_parent` (fresh relay tip) without re-executing the blocks.
15//! The `relay_parent` stays the same since the execution context hasn't changed.
16//!
17//! For resubmission, `signed_scheduling_info` must be provided. The resubmitting
18//! collator signs the core selection, proving they are the eligible author for the
19//! slot derived from the `internal_scheduling_parent`.
20
21use alloc::vec::Vec;
22use codec::{Decode, Encode};
23use polkadot_primitives::{ApprovedPeerId, CoreSelector, Header as RelayChainHeader};
24use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
25
26/// Payload signed by a collator for resubmission.
27///
28/// This binds the core selection and reputation-credit peer to a specific internal
29/// scheduling parent, preventing replay attacks across different scheduling contexts.
30#[derive(Clone, Encode, Decode, Debug, PartialEq, Eq)]
31pub struct SchedulingInfoPayload {
32 /// Which core to use (indexes into the parachain's assigned cores).
33 pub core_selector: CoreSelector,
34 /// The claim queue offset.
35 pub claim_queue_offset: u8,
36 /// Peer ID to receive reputation credit for successful collation delivery.
37 pub peer_id: ApprovedPeerId,
38 /// The internal scheduling parent whom's slot decides the
39 /// eligible block author that must sign the payload.
40 pub internal_scheduling_parent: polkadot_primitives::Hash,
41}
42
43/// Signed scheduling information for candidate resubmission.
44///
45/// When a collator resubmits a candidate (with a newer `scheduling_parent` but same
46/// `relay_parent`), they must sign the core selection to prove eligibility for the
47/// slot at `internal_scheduling_parent`.
48///
49/// The `claim_queue_offset` is derived from the runtime's `relay_parent_offset`
50/// configuration and is not part of this struct - it cannot be overridden by the
51/// collator.
52#[derive(Clone, Encode, Decode, Debug, PartialEq, Eq)]
53pub struct SignedSchedulingInfo {
54 /// The scheduling information.
55 pub payload: SchedulingInfoPayload,
56 /// Signature by the eligible collator over the SCALE-encoded
57 /// `SchedulingInfoPayload`.
58 ///
59 /// Stored as a fixed 64-byte blob so the verifier can decode it as either an sr25519
60 /// or ed25519 signature. Both schemes produce 64-byte signatures.
61 pub signature: [u8; 64],
62}
63
64impl SchedulingInfoPayload {
65 /// Create a new scheduling info payload.
66 pub fn new(
67 core_selector: CoreSelector,
68 claim_queue_offset: u8,
69 peer_id: ApprovedPeerId,
70 internal_scheduling_parent: polkadot_primitives::Hash,
71 ) -> Self {
72 Self { core_selector, claim_queue_offset, peer_id, internal_scheduling_parent }
73 }
74}
75
76/// V3 scheduling proof included in the POV.
77///
78/// Provides the ancestry from scheduling_parent back to the internal scheduling
79/// parent. The PVF validates this against the relay_parent and scheduling_parent
80/// from the candidate descriptor extension.
81#[derive(Clone, Encode, Decode, Debug, PartialEq, Eq)]
82pub struct SchedulingProof {
83 /// Relay chain headers proving ancestry from scheduling_parent backward.
84 ///
85 /// Forms a chain where each header's parent_hash equals the next header's hash.
86 /// The first header's hash must equal the candidate's scheduling_parent.
87 /// The last header's parent_hash is the internal scheduling parent.
88 /// Length is defined by the parachain runtime config (RelayParentOffset).
89 pub header_chain: Vec<RelayChainHeader>,
90 /// The relay chain header at `internal_scheduling_parent`. Its hash must equal the
91 /// `internal_scheduling_parent` derived from `header_chain` (the parent of the chain's
92 /// last header, or `scheduling_parent` if the chain is empty).
93 pub internal_scheduling_parent_header: RelayChainHeader,
94 /// Signed scheduling info for core selection override.
95 ///
96 /// - `None` with `relay_parent == internal_scheduling_parent`: Initial submission. Core
97 /// selection comes from the parachain block's UMP signals.
98 ///
99 /// - `Some` with `relay_parent == internal_scheduling_parent`: Initial submission with
100 /// explicit core selection. This is optional but legal. Collators should refuse to
101 /// acknowledge blocks with invalid scheduling info, so providing a signature is not required
102 /// for initial submissions.
103 ///
104 /// - `Some` with `relay_parent != internal_scheduling_parent`: Resubmission (required). The
105 /// resubmitting collator signs the core selection, overriding the block's UMP signals.
106 /// Signature is verified against the eligible author for the slot at
107 /// `internal_scheduling_parent`.
108 pub signed_scheduling_info: Option<SignedSchedulingInfo>,
109}
110
111impl SchedulingProof {
112 /// Derive the scheduling parent hash.
113 ///
114 /// Returns the hash of the first/newest header in `header_chain` if non-empty, otherwise
115 /// falls back to `internal_scheduling_parent_header.hash()` (the ISP coincides with the
116 /// scheduling parent when the parachain runs with `relay_parent_offset = 0`).
117 pub fn scheduling_parent(&self) -> polkadot_primitives::Hash {
118 self.header_chain
119 .first()
120 .map(BlakeTwo256::hash_of)
121 .unwrap_or_else(|| self.internal_scheduling_parent_header.hash())
122 }
123}
124
125/// Verifier for V3 scheduling.
126///
127/// Reports whether V3 scheduling is enabled for the parachain (via
128/// [`Self::V3_SCHEDULING_ENABLED`]) and, when it is, verifies the [`SignedSchedulingInfo`]
129/// attached to a candidate (via [`Self::verify`]).
130pub trait VerifySchedulingSignature {
131 /// Whether V3 scheduling validation is enabled.
132 const V3_SCHEDULING_ENABLED: bool;
133
134 /// Verifies `signed_info` against `internal_scheduling_parent_header`.
135 fn verify(
136 signed_info: &SignedSchedulingInfo,
137 internal_scheduling_parent_header: &RelayChainHeader,
138 ) -> bool;
139}
140
141/// Default no-op wiring: V3 scheduling disabled, scheduling info accepted unconditionally.
142///
143/// Replacing it with a real verifier should also turn V3 on.
144impl VerifySchedulingSignature for () {
145 const V3_SCHEDULING_ENABLED: bool = false;
146
147 fn verify(
148 _signed_info: &SignedSchedulingInfo,
149 _internal_scheduling_parent_header: &RelayChainHeader,
150 ) -> bool {
151 true
152 }
153}