1use alloc::vec::Vec;
19use codec::{Decode, Encode};
20use polkadot_primitives::{
21 ApprovedPeerId, ClaimQueueOffset, CoreSelector, Header as RelayChainHeader, Slot, UMPSignal,
22 UMP_SEPARATOR,
23};
24use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
25
26#[derive(Clone, Encode, Decode, Debug, PartialEq, Eq)]
31pub struct SchedulingInfoPayload {
32 pub core_selector: CoreSelector,
34 pub claim_queue_offset: u8,
36 pub peer_id: ApprovedPeerId,
38 pub internal_scheduling_parent: polkadot_primitives::Hash,
41}
42
43#[derive(Clone, Encode, Decode, Debug, PartialEq, Eq)]
49pub struct SignedSchedulingInfo {
50 pub payload: SchedulingInfoPayload,
52 pub signature: [u8; 64],
56}
57
58impl SchedulingInfoPayload {
59 pub fn new(
61 core_selector: CoreSelector,
62 claim_queue_offset: u8,
63 peer_id: ApprovedPeerId,
64 internal_scheduling_parent: polkadot_primitives::Hash,
65 ) -> Self {
66 Self { core_selector, claim_queue_offset, peer_id, internal_scheduling_parent }
67 }
68}
69
70#[derive(Debug, Default, PartialEq, Eq)]
77pub struct SchedulingSignals {
78 select_core: Option<(CoreSelector, ClaimQueueOffset)>,
79 approved_peer: Option<ApprovedPeerId>,
80}
81
82impl SchedulingSignals {
83 pub fn from_block_signals(raw: &[Vec<u8>]) -> Self {
88 let mut signals = Self::default();
89 for bytes in raw {
90 match UMPSignal::decode(&mut &bytes[..]).expect("Failed to decode `UMPSignal`") {
93 UMPSignal::SelectCore(selector, offset) => {
94 if signals.select_core.replace((selector, offset)).is_some() {
95 panic!("Parachain emitted more than one `SelectCore` UMP signal");
96 }
97 },
98 UMPSignal::ApprovedPeer(peer_id) => {
99 if signals.approved_peer.replace(peer_id).is_some() {
100 panic!("Parachain emitted more than one `ApprovedPeer` UMP signal");
101 }
102 },
103 }
104 }
105 signals
106 }
107
108 pub fn from_scheduling_info(signed_info: &SignedSchedulingInfo) -> Self {
112 let payload = &signed_info.payload;
113 Self {
114 select_core: Some((
115 payload.core_selector,
116 ClaimQueueOffset(payload.claim_queue_offset),
117 )),
118 approved_peer: Some(payload.peer_id.clone()),
119 }
120 }
121
122 fn is_empty(&self) -> bool {
123 self.select_core.is_none() && self.approved_peer.is_none()
124 }
125
126 pub fn into_ump_messages(self) -> Vec<Vec<u8>> {
131 if self.is_empty() {
132 return Vec::new();
133 }
134 let mut messages = Vec::with_capacity(3);
135 messages.push(UMP_SEPARATOR);
136 if let Some((selector, offset)) = self.select_core {
137 messages.push(UMPSignal::SelectCore(selector, offset).encode());
138 }
139 if let Some(peer_id) = self.approved_peer {
140 messages.push(UMPSignal::ApprovedPeer(peer_id).encode());
141 }
142 messages
143 }
144}
145
146#[derive(Clone, Encode, Decode, Debug, PartialEq, Eq)]
151pub struct SchedulingProof {
152 pub header_chain: Vec<RelayChainHeader>,
157 pub internal_scheduling_parent_header: RelayChainHeader,
161 pub signed_scheduling_info: Option<SignedSchedulingInfo>,
171}
172
173impl SchedulingProof {
174 pub fn new(
176 header_chain: Vec<RelayChainHeader>,
177 internal_scheduling_parent_header: RelayChainHeader,
178 signed_scheduling_info: Option<SignedSchedulingInfo>,
179 ) -> Self {
180 Self { header_chain, internal_scheduling_parent_header, signed_scheduling_info }
181 }
182
183 pub fn scheduling_parent(&self) -> polkadot_primitives::Hash {
187 self.header_chain
188 .first()
189 .map(BlakeTwo256::hash_of)
190 .unwrap_or_else(|| self.internal_scheduling_parent_header.hash())
191 }
192}
193
194pub trait VerifySchedulingSignature {
197 const V3_SCHEDULING_ENABLED: bool;
199
200 fn verify(signed_info: &SignedSchedulingInfo, relay_slot: Slot) -> bool;
203}
204
205impl VerifySchedulingSignature for () {
208 const V3_SCHEDULING_ENABLED: bool = false;
209
210 fn verify(_signed_info: &SignedSchedulingInfo, _relay_slot: Slot) -> bool {
211 true
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::{SchedulingInfoPayload, SchedulingSignals, SignedSchedulingInfo};
218 use alloc::vec;
219 use codec::Encode;
220 use polkadot_primitives::{
221 ApprovedPeerId, ClaimQueueOffset, CoreSelector, UMPSignal, UMP_SEPARATOR,
222 };
223
224 fn peer(byte: u8) -> ApprovedPeerId {
225 ApprovedPeerId::try_from(vec![byte; 4]).expect("4 bytes fits the bound; qed")
226 }
227
228 fn signed_with(
229 core_selector: CoreSelector,
230 claim_queue_offset: u8,
231 peer_id: ApprovedPeerId,
232 ) -> SignedSchedulingInfo {
233 SignedSchedulingInfo {
234 payload: SchedulingInfoPayload::new(
235 core_selector,
236 claim_queue_offset,
237 peer_id,
238 Default::default(),
239 ),
240 signature: [0u8; 64],
241 }
242 }
243
244 #[test]
245 fn from_block_signals_roundtrips_select_core_and_approved_peer() {
246 let raw = vec![
249 UMPSignal::SelectCore(CoreSelector(7), ClaimQueueOffset(1)).encode(),
250 UMPSignal::ApprovedPeer(peer(0xAA)).encode(),
251 ];
252 assert_eq!(
253 SchedulingSignals::from_block_signals(&raw).into_ump_messages(),
254 vec![
255 UMP_SEPARATOR,
256 UMPSignal::SelectCore(CoreSelector(7), ClaimQueueOffset(1)).encode(),
257 UMPSignal::ApprovedPeer(peer(0xAA)).encode(),
258 ]
259 );
260 }
261
262 #[test]
263 fn from_block_signals_select_core_only() {
264 let raw = vec![UMPSignal::SelectCore(CoreSelector(3), ClaimQueueOffset(0)).encode()];
266 assert_eq!(
267 SchedulingSignals::from_block_signals(&raw).into_ump_messages(),
268 vec![
269 UMP_SEPARATOR,
270 UMPSignal::SelectCore(CoreSelector(3), ClaimQueueOffset(0)).encode()
271 ]
272 );
273 }
274
275 #[test]
276 #[should_panic(expected = "more than one `SelectCore`")]
277 fn from_block_signals_panics_on_duplicate_select_core_same_value() {
278 let raw = vec![
281 UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(0)).encode(),
282 UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(0)).encode(),
283 ];
284 let _ = SchedulingSignals::from_block_signals(&raw);
285 }
286
287 #[test]
288 #[should_panic(expected = "more than one `SelectCore`")]
289 fn from_block_signals_panics_on_duplicate_select_core_different_value() {
290 let raw = vec![
291 UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(0)).encode(),
292 UMPSignal::SelectCore(CoreSelector(2), ClaimQueueOffset(0)).encode(),
293 ];
294 let _ = SchedulingSignals::from_block_signals(&raw);
295 }
296
297 #[test]
298 #[should_panic(expected = "more than one `ApprovedPeer`")]
299 fn from_block_signals_panics_on_duplicate_approved_peer() {
300 let raw = vec![
301 UMPSignal::ApprovedPeer(peer(0xAA)).encode(),
302 UMPSignal::ApprovedPeer(peer(0xBB)).encode(),
303 ];
304 let _ = SchedulingSignals::from_block_signals(&raw);
305 }
306
307 #[test]
308 fn from_block_signals_empty_emits_nothing() {
309 assert!(SchedulingSignals::from_block_signals(&[]).into_ump_messages().is_empty());
311 }
312
313 #[test]
314 fn from_scheduling_info_sources_all_fields() {
315 let signed = signed_with(CoreSelector(7), 3, peer(0xAA));
319 assert_eq!(
320 SchedulingSignals::from_scheduling_info(&signed).into_ump_messages(),
321 vec![
322 UMP_SEPARATOR,
323 UMPSignal::SelectCore(CoreSelector(7), ClaimQueueOffset(3)).encode(),
324 UMPSignal::ApprovedPeer(peer(0xAA)).encode(),
325 ]
326 );
327 }
328
329 #[test]
330 fn from_scheduling_info_emits_peer_verbatim_even_if_empty() {
331 let signed = signed_with(CoreSelector(5), 1, ApprovedPeerId::default());
334 assert_eq!(
335 SchedulingSignals::from_scheduling_info(&signed).into_ump_messages(),
336 vec![
337 UMP_SEPARATOR,
338 UMPSignal::SelectCore(CoreSelector(5), ClaimQueueOffset(1)).encode(),
339 UMPSignal::ApprovedPeer(ApprovedPeerId::default()).encode(),
340 ]
341 );
342 }
343
344 #[test]
345 fn override_matches_block_signals_when_values_agree() {
346 let selector = CoreSelector(7);
351 let offset = ClaimQueueOffset(3);
352 let peer_id = peer(0xAA);
353
354 let from_block = SchedulingSignals::from_block_signals(&[
355 UMPSignal::SelectCore(selector, offset).encode(),
356 UMPSignal::ApprovedPeer(peer_id.clone()).encode(),
357 ])
358 .into_ump_messages();
359
360 let from_signed =
361 SchedulingSignals::from_scheduling_info(&signed_with(selector, offset.0, peer_id))
362 .into_ump_messages();
363
364 assert_eq!(from_block, from_signed);
365 }
366
367 #[test]
368 fn from_scheduling_info_emits_even_when_block_emitted_nothing() {
369 let signed = signed_with(CoreSelector(0), 0, peer(0xCC));
372 assert!(!SchedulingSignals::from_scheduling_info(&signed).into_ump_messages().is_empty());
373 }
374
375 #[test]
382 fn all_ump_signals_are_scheduling_signals() {
383 fn classify(signal: UMPSignal) {
384 match signal {
385 UMPSignal::SelectCore(..) | UMPSignal::ApprovedPeer(..) => {},
386 }
387 }
388 let _ = classify;
389 }
390}