1use core::fmt::Formatter;
19
20use crate::{slashing::DisputesTimeSlot, ValidatorId, ValidatorIndex, ValidityAttestation};
21
22use super::{
24 async_backing::{InboundHrmpLimitations, OutboundHrmpChannelLimitations},
25 BlakeTwo256, BlockNumber, CandidateCommitments, CandidateDescriptor, CandidateHash, CollatorId,
26 CollatorSignature, CoreIndex, GroupIndex, Hash, HashT, HeadData, Header, Id, Id as ParaId,
27 MultiDisputeStatementSet, ScheduledCore, UncheckedSignedAvailabilityBitfields,
28 UpgradeRestriction, ValidationCodeHash,
29};
30use alloc::{
31 collections::{BTreeMap, BTreeSet, VecDeque},
32 vec,
33 vec::Vec,
34};
35use bitvec::prelude::*;
36use bounded_collections::BoundedVec;
37use codec::{Decode, DecodeWithMemTracking, Encode};
38use scale_info::TypeInfo;
39use sp_application_crypto::ByteArray;
40use sp_core::{ConstU32, RuntimeDebug};
41use sp_runtime::traits::Header as HeaderT;
42use sp_staking::SessionIndex;
43
44pub mod async_backing;
46
47pub const DEFAULT_CLAIM_QUEUE_OFFSET: u8 = 0;
50
51#[derive(
53 PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, Clone, TypeInfo, RuntimeDebug, Copy,
54)]
55#[cfg_attr(feature = "std", derive(Hash))]
56pub struct InternalVersion(pub u8);
57
58#[derive(PartialEq, Eq, Clone, TypeInfo, RuntimeDebug)]
60#[cfg_attr(feature = "std", derive(Hash))]
61pub enum CandidateDescriptorVersion {
62 V1,
64 V2,
66 Unknown,
68}
69
70#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
72#[cfg_attr(feature = "std", derive(Hash))]
73pub struct CandidateDescriptorV2<H = Hash> {
74 para_id: ParaId,
76 relay_parent: H,
78 version: InternalVersion,
83 core_index: u16,
85 session_index: SessionIndex,
87 reserved1: [u8; 25],
89 persisted_validation_data_hash: Hash,
93 pov_hash: Hash,
95 erasure_root: Hash,
97 reserved2: [u8; 64],
99 para_head: Hash,
101 validation_code_hash: ValidationCodeHash,
103}
104impl<H> CandidateDescriptorV2<H> {
105 pub fn version(&self) -> CandidateDescriptorVersion {
110 if self.reserved2 != [0u8; 64] || self.reserved1 != [0u8; 25] {
111 return CandidateDescriptorVersion::V1
112 }
113
114 match self.version.0 {
115 0 => CandidateDescriptorVersion::V2,
116 _ => CandidateDescriptorVersion::Unknown,
117 }
118 }
119}
120
121impl<H> core::fmt::Debug for CandidateDescriptorV2<H>
122where
123 H: core::fmt::Debug,
124{
125 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
126 match self.version() {
127 CandidateDescriptorVersion::V1 => f
128 .debug_struct("CandidateDescriptorV1")
129 .field("para_id", &self.para_id)
130 .field("relay_parent", &self.relay_parent)
131 .field("persisted_validation_hash", &self.persisted_validation_data_hash)
132 .field("pov_hash", &self.pov_hash)
133 .field("erasure_root", &self.erasure_root)
134 .field("para_head", &self.para_head)
135 .field("validation_code_hash", &self.validation_code_hash)
136 .finish(),
137 CandidateDescriptorVersion::V2 => f
138 .debug_struct("CandidateDescriptorV2")
139 .field("para_id", &self.para_id)
140 .field("relay_parent", &self.relay_parent)
141 .field("core_index", &self.core_index)
142 .field("session_index", &self.session_index)
143 .field("persisted_validation_data_hash", &self.persisted_validation_data_hash)
144 .field("pov_hash", &self.pov_hash)
145 .field("erasure_root", &self.pov_hash)
146 .field("para_head", &self.para_head)
147 .field("validation_code_hash", &self.validation_code_hash)
148 .finish(),
149 CandidateDescriptorVersion::Unknown => {
150 write!(f, "Invalid CandidateDescriptorVersion")
151 },
152 }
153 }
154}
155
156impl<H: Copy> From<CandidateDescriptorV2<H>> for CandidateDescriptor<H> {
157 fn from(value: CandidateDescriptorV2<H>) -> Self {
158 Self {
159 para_id: value.para_id,
160 relay_parent: value.relay_parent,
161 collator: value.rebuild_collator_field(),
162 persisted_validation_data_hash: value.persisted_validation_data_hash,
163 pov_hash: value.pov_hash,
164 erasure_root: value.erasure_root,
165 signature: value.rebuild_signature_field(),
166 para_head: value.para_head,
167 validation_code_hash: value.validation_code_hash,
168 }
169 }
170}
171
172fn clone_into_array<A, T>(slice: &[T]) -> A
173where
174 A: Default + AsMut<[T]>,
175 T: Clone,
176{
177 let mut a = A::default();
178 <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
179 a
180}
181
182impl<H: Copy> From<CandidateDescriptor<H>> for CandidateDescriptorV2<H> {
183 fn from(value: CandidateDescriptor<H>) -> Self {
184 let collator = value.collator.as_slice();
185
186 Self {
187 para_id: value.para_id,
188 relay_parent: value.relay_parent,
189 version: InternalVersion(collator[0]),
191 core_index: u16::from_ne_bytes(clone_into_array(&collator[1..=2])),
193 session_index: SessionIndex::from_ne_bytes(clone_into_array(&collator[3..=6])),
195 reserved1: clone_into_array(&collator[7..]),
197 persisted_validation_data_hash: value.persisted_validation_data_hash,
198 pov_hash: value.pov_hash,
199 erasure_root: value.erasure_root,
200 reserved2: value.signature.into_inner().0,
201 para_head: value.para_head,
202 validation_code_hash: value.validation_code_hash,
203 }
204 }
205}
206
207impl<H: Copy + AsRef<[u8]>> CandidateDescriptorV2<H> {
208 pub fn new(
210 para_id: Id,
211 relay_parent: H,
212 core_index: CoreIndex,
213 session_index: SessionIndex,
214 persisted_validation_data_hash: Hash,
215 pov_hash: Hash,
216 erasure_root: Hash,
217 para_head: Hash,
218 validation_code_hash: ValidationCodeHash,
219 ) -> Self {
220 Self {
221 para_id,
222 relay_parent,
223 version: InternalVersion(0),
224 core_index: core_index.0 as u16,
225 session_index,
226 reserved1: [0; 25],
227 persisted_validation_data_hash,
228 pov_hash,
229 erasure_root,
230 reserved2: [0; 64],
231 para_head,
232 validation_code_hash,
233 }
234 }
235
236 pub fn check_collator_signature(&self) -> Result<(), ()> {
238 let Some(collator) = self.collator() else { return Ok(()) };
240
241 let Some(signature) = self.signature() else { return Ok(()) };
242
243 super::v8::check_collator_signature(
244 &self.relay_parent,
245 &self.para_id,
246 &self.persisted_validation_data_hash,
247 &self.pov_hash,
248 &self.validation_code_hash,
249 &collator,
250 &signature,
251 )
252 }
253}
254
255#[cfg(feature = "test")]
257
258pub trait MutateDescriptorV2<H> {
259 fn set_relay_parent(&mut self, relay_parent: H);
261 fn set_para_id(&mut self, para_id: Id);
263 fn set_pov_hash(&mut self, pov_hash: Hash);
265 fn set_version(&mut self, version: InternalVersion);
267 fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash);
269 fn set_validation_code_hash(&mut self, validation_code_hash: ValidationCodeHash);
271 fn set_erasure_root(&mut self, erasure_root: Hash);
273 fn set_para_head(&mut self, para_head: Hash);
275 fn set_core_index(&mut self, core_index: CoreIndex);
277 fn set_session_index(&mut self, session_index: SessionIndex);
279}
280
281#[cfg(feature = "test")]
282impl<H> MutateDescriptorV2<H> for CandidateDescriptorV2<H> {
283 fn set_para_id(&mut self, para_id: Id) {
284 self.para_id = para_id;
285 }
286
287 fn set_relay_parent(&mut self, relay_parent: H) {
288 self.relay_parent = relay_parent;
289 }
290
291 fn set_pov_hash(&mut self, pov_hash: Hash) {
292 self.pov_hash = pov_hash;
293 }
294
295 fn set_version(&mut self, version: InternalVersion) {
296 self.version = version;
297 }
298
299 fn set_core_index(&mut self, core_index: CoreIndex) {
300 self.core_index = core_index.0 as u16;
301 }
302
303 fn set_session_index(&mut self, session_index: SessionIndex) {
304 self.session_index = session_index;
305 }
306
307 fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash) {
308 self.persisted_validation_data_hash = persisted_validation_data_hash;
309 }
310
311 fn set_validation_code_hash(&mut self, validation_code_hash: ValidationCodeHash) {
312 self.validation_code_hash = validation_code_hash;
313 }
314
315 fn set_erasure_root(&mut self, erasure_root: Hash) {
316 self.erasure_root = erasure_root;
317 }
318
319 fn set_para_head(&mut self, para_head: Hash) {
320 self.para_head = para_head;
321 }
322}
323
324#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, RuntimeDebug)]
326#[cfg_attr(feature = "std", derive(Hash))]
327pub struct CandidateReceiptV2<H = Hash> {
328 pub descriptor: CandidateDescriptorV2<H>,
330 pub commitments_hash: Hash,
332}
333
334#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, RuntimeDebug)]
336#[cfg_attr(feature = "std", derive(Hash))]
337pub struct CommittedCandidateReceiptV2<H = Hash> {
338 pub descriptor: CandidateDescriptorV2<H>,
340 pub commitments: CandidateCommitments,
342}
343
344#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
346#[cfg_attr(feature = "std", derive(PartialEq))]
347pub enum CandidateEvent<H = Hash> {
348 #[codec(index = 0)]
351 CandidateBacked(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
352 #[codec(index = 1)]
356 CandidateIncluded(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
357 #[codec(index = 2)]
360 CandidateTimedOut(CandidateReceiptV2<H>, HeadData, CoreIndex),
361}
362
363impl<H: Encode + Copy> From<CandidateEvent<H>> for super::v8::CandidateEvent<H> {
364 fn from(value: CandidateEvent<H>) -> Self {
365 match value {
366 CandidateEvent::CandidateBacked(receipt, head_data, core_index, group_index) =>
367 super::v8::CandidateEvent::CandidateBacked(
368 receipt.into(),
369 head_data,
370 core_index,
371 group_index,
372 ),
373 CandidateEvent::CandidateIncluded(receipt, head_data, core_index, group_index) =>
374 super::v8::CandidateEvent::CandidateIncluded(
375 receipt.into(),
376 head_data,
377 core_index,
378 group_index,
379 ),
380 CandidateEvent::CandidateTimedOut(receipt, head_data, core_index) =>
381 super::v8::CandidateEvent::CandidateTimedOut(receipt.into(), head_data, core_index),
382 }
383 }
384}
385
386impl<H> CandidateReceiptV2<H> {
387 pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
389 &self.descriptor
390 }
391
392 pub fn hash(&self) -> CandidateHash
394 where
395 H: Encode,
396 {
397 CandidateHash(BlakeTwo256::hash_of(self))
398 }
399}
400
401impl<H: Copy> From<super::v8::CandidateReceipt<H>> for CandidateReceiptV2<H> {
402 fn from(value: super::v8::CandidateReceipt<H>) -> Self {
403 CandidateReceiptV2 {
404 descriptor: value.descriptor.into(),
405 commitments_hash: value.commitments_hash,
406 }
407 }
408}
409
410impl<H: Copy> From<super::v8::CommittedCandidateReceipt<H>> for CommittedCandidateReceiptV2<H> {
411 fn from(value: super::v8::CommittedCandidateReceipt<H>) -> Self {
412 CommittedCandidateReceiptV2 {
413 descriptor: value.descriptor.into(),
414 commitments: value.commitments,
415 }
416 }
417}
418
419impl<H: Clone> CommittedCandidateReceiptV2<H> {
420 pub fn to_plain(&self) -> CandidateReceiptV2<H> {
422 CandidateReceiptV2 {
423 descriptor: self.descriptor.clone(),
424 commitments_hash: self.commitments.hash(),
425 }
426 }
427
428 pub fn hash(&self) -> CandidateHash
433 where
434 H: Encode,
435 {
436 self.to_plain().hash()
437 }
438
439 pub fn corresponds_to(&self, receipt: &CandidateReceiptV2<H>) -> bool
441 where
442 H: PartialEq,
443 {
444 receipt.descriptor == self.descriptor && receipt.commitments_hash == self.commitments.hash()
445 }
446}
447
448impl PartialOrd for CommittedCandidateReceiptV2 {
449 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
450 Some(self.cmp(other))
451 }
452}
453
454impl Ord for CommittedCandidateReceiptV2 {
455 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
456 self.descriptor
457 .para_id
458 .cmp(&other.descriptor.para_id)
459 .then_with(|| self.commitments.head_data.cmp(&other.commitments.head_data))
460 }
461}
462
463impl<H: Copy> From<CommittedCandidateReceiptV2<H>> for super::v8::CommittedCandidateReceipt<H> {
464 fn from(value: CommittedCandidateReceiptV2<H>) -> Self {
465 Self { descriptor: value.descriptor.into(), commitments: value.commitments }
466 }
467}
468
469impl<H: Copy> From<CandidateReceiptV2<H>> for super::v8::CandidateReceipt<H> {
470 fn from(value: CandidateReceiptV2<H>) -> Self {
471 Self { descriptor: value.descriptor.into(), commitments_hash: value.commitments_hash }
472 }
473}
474
475#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug, Copy)]
478pub struct CoreSelector(pub u8);
479
480#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug, Copy)]
482pub struct ClaimQueueOffset(pub u8);
483
484pub type ApprovedPeerId = BoundedVec<u8, ConstU32<64>>;
488
489#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug)]
491pub enum UMPSignal {
492 SelectCore(CoreSelector, ClaimQueueOffset),
496 ApprovedPeer(ApprovedPeerId),
498}
499
500#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug, Default)]
501pub struct CandidateUMPSignals {
503 select_core: Option<(CoreSelector, ClaimQueueOffset)>,
504 approved_peer: Option<ApprovedPeerId>,
505}
506
507impl CandidateUMPSignals {
508 pub fn core_selector(&self) -> Option<(CoreSelector, ClaimQueueOffset)> {
510 self.select_core
511 }
512
513 pub fn approved_peer(&self) -> Option<&ApprovedPeerId> {
515 self.approved_peer.as_ref()
516 }
517
518 pub fn is_empty(&self) -> bool {
520 self.select_core.is_none() && self.approved_peer.is_none()
521 }
522
523 fn try_decode_signal(
524 &mut self,
525 buffer: &mut impl codec::Input,
526 ) -> Result<(), CommittedCandidateReceiptError> {
527 match UMPSignal::decode(buffer)
528 .map_err(|_| CommittedCandidateReceiptError::UmpSignalDecode)?
529 {
530 UMPSignal::ApprovedPeer(approved_peer_id) if self.approved_peer.is_none() => {
531 self.approved_peer = Some(approved_peer_id);
532 },
533 UMPSignal::SelectCore(core_selector, cq_offset) if self.select_core.is_none() => {
534 self.select_core = Some((core_selector, cq_offset));
535 },
536 _ => {
537 return Err(CommittedCandidateReceiptError::DuplicateUMPSignal)
539 },
540 };
541
542 Ok(())
543 }
544}
545
546pub const UMP_SEPARATOR: Vec<u8> = vec![];
548
549pub fn skip_ump_signals<'a>(
551 upward_messages: impl Iterator<Item = &'a Vec<u8>>,
552) -> impl Iterator<Item = &'a Vec<u8>> {
553 upward_messages.take_while(|message| *message != &UMP_SEPARATOR)
554}
555
556impl CandidateCommitments {
557 pub fn ump_signals(&self) -> Result<CandidateUMPSignals, CommittedCandidateReceiptError> {
560 let mut res = CandidateUMPSignals::default();
561
562 let mut signals_iter =
563 self.upward_messages.iter().skip_while(|message| *message != &UMP_SEPARATOR);
564
565 if signals_iter.next().is_none() {
566 return Ok(res)
568 }
569
570 let Some(first_signal) = signals_iter.next() else { return Ok(res) };
572 res.try_decode_signal(&mut first_signal.as_slice())?;
573
574 let Some(second_signal) = signals_iter.next() else { return Ok(res) };
576 res.try_decode_signal(&mut second_signal.as_slice())?;
577
578 if signals_iter.next().is_some() {
580 return Err(CommittedCandidateReceiptError::TooManyUMPSignals)
581 }
582
583 Ok(res)
584 }
585}
586
587#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
589#[cfg_attr(feature = "std", derive(thiserror::Error))]
590pub enum CommittedCandidateReceiptError {
591 #[cfg_attr(feature = "std", error("The specified core index is invalid"))]
593 InvalidCoreIndex,
594 #[cfg_attr(
596 feature = "std",
597 error("The core index in commitments ({commitments:?}) doesn't match the one in descriptor ({descriptor:?})")
598 )]
599 CoreIndexMismatch {
600 descriptor: CoreIndex,
602 commitments: CoreIndex,
604 },
605 #[cfg_attr(feature = "std", error("The core selector or claim queue offset is invalid"))]
607 InvalidSelectedCore,
608 #[cfg_attr(feature = "std", error("Could not decode UMP signal"))]
609 UmpSignalDecode,
611 #[cfg_attr(
613 feature = "std",
614 error("The parachain is not assigned to any core at specified claim queue offset")
615 )]
616 NoAssignment,
617 #[cfg_attr(feature = "std", error("Unknown internal version"))]
619 UnknownVersion(InternalVersion),
620 #[cfg_attr(feature = "std", error("Too many UMP signals"))]
622 TooManyUMPSignals,
623 #[cfg_attr(feature = "std", error("Duplicate UMP signal"))]
625 DuplicateUMPSignal,
626 #[cfg_attr(feature = "std", error("Version 1 receipt does not support ump signals"))]
629 UMPSignalWithV1Decriptor,
630}
631
632macro_rules! impl_getter {
633 ($field:ident, $type:ident) => {
634 pub fn $field(&self) -> $type {
636 self.$field
637 }
638 };
639}
640
641impl<H: Copy> CandidateDescriptorV2<H> {
642 impl_getter!(erasure_root, Hash);
643 impl_getter!(para_head, Hash);
644 impl_getter!(relay_parent, H);
645 impl_getter!(para_id, ParaId);
646 impl_getter!(persisted_validation_data_hash, Hash);
647 impl_getter!(pov_hash, Hash);
648 impl_getter!(validation_code_hash, ValidationCodeHash);
649
650 fn rebuild_collator_field(&self) -> CollatorId {
651 let mut collator_id = Vec::with_capacity(32);
652 let core_index: [u8; 2] = self.core_index.to_ne_bytes();
653 let session_index: [u8; 4] = self.session_index.to_ne_bytes();
654
655 collator_id.push(self.version.0);
656 collator_id.extend_from_slice(core_index.as_slice());
657 collator_id.extend_from_slice(session_index.as_slice());
658 collator_id.extend_from_slice(self.reserved1.as_slice());
659
660 CollatorId::from_slice(&collator_id.as_slice())
661 .expect("Slice size is exactly 32 bytes; qed")
662 }
663
664 pub fn collator(&self) -> Option<CollatorId> {
666 if self.version() == CandidateDescriptorVersion::V1 {
667 Some(self.rebuild_collator_field())
668 } else {
669 None
670 }
671 }
672
673 fn rebuild_signature_field(&self) -> CollatorSignature {
674 CollatorSignature::from_slice(self.reserved2.as_slice())
675 .expect("Slice size is exactly 64 bytes; qed")
676 }
677
678 pub fn signature(&self) -> Option<CollatorSignature> {
680 if self.version() == CandidateDescriptorVersion::V1 {
681 return Some(self.rebuild_signature_field())
682 }
683
684 None
685 }
686
687 pub fn core_index(&self) -> Option<CoreIndex> {
689 if self.version() == CandidateDescriptorVersion::V1 {
690 return None
691 }
692
693 Some(CoreIndex(self.core_index as u32))
694 }
695
696 pub fn session_index(&self) -> Option<SessionIndex> {
698 if self.version() == CandidateDescriptorVersion::V1 {
699 return None
700 }
701
702 Some(self.session_index)
703 }
704}
705
706impl<H: Copy> CommittedCandidateReceiptV2<H> {
707 pub fn parse_ump_signals(
715 &self,
716 cores_per_para: &TransposedClaimQueue,
717 ) -> Result<CandidateUMPSignals, CommittedCandidateReceiptError> {
718 let signals = self.commitments.ump_signals()?;
719
720 match self.descriptor.version() {
721 CandidateDescriptorVersion::V1 => {
722 if !signals.is_empty() {
725 return Err(CommittedCandidateReceiptError::UMPSignalWithV1Decriptor)
726 } else {
727 return Ok(CandidateUMPSignals::default())
729 }
730 },
731 CandidateDescriptorVersion::V2 => {},
732 CandidateDescriptorVersion::Unknown =>
733 return Err(CommittedCandidateReceiptError::UnknownVersion(self.descriptor.version)),
734 }
735
736 let (maybe_core_index_selector, cq_offset) = signals
738 .core_selector()
739 .map(|(selector, offset)| (Some(selector), offset))
740 .unwrap_or_else(|| (None, ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET)));
741
742 self.check_core_index(cores_per_para, maybe_core_index_selector, cq_offset)?;
743
744 Ok(signals)
747 }
748
749 fn check_core_index(
753 &self,
754 cores_per_para: &TransposedClaimQueue,
755 maybe_core_index_selector: Option<CoreSelector>,
756 cq_offset: ClaimQueueOffset,
757 ) -> Result<(), CommittedCandidateReceiptError> {
758 let assigned_cores = cores_per_para
759 .get(&self.descriptor.para_id())
760 .ok_or(CommittedCandidateReceiptError::NoAssignment)?
761 .get(&cq_offset.0)
762 .ok_or(CommittedCandidateReceiptError::NoAssignment)?;
763
764 if assigned_cores.is_empty() {
765 return Err(CommittedCandidateReceiptError::NoAssignment)
766 }
767
768 let descriptor_core_index = CoreIndex(self.descriptor.core_index as u32);
769
770 let core_index_selector = if let Some(core_index_selector) = maybe_core_index_selector {
771 core_index_selector
773 } else if assigned_cores.len() > 1 {
774 if !assigned_cores.contains(&descriptor_core_index) {
776 return Err(CommittedCandidateReceiptError::InvalidCoreIndex)
778 } else {
779 return Ok(())
782 }
783 } else {
784 CoreSelector(0)
786 };
787
788 let core_index = assigned_cores
789 .iter()
790 .nth(core_index_selector.0 as usize % assigned_cores.len())
791 .ok_or(CommittedCandidateReceiptError::InvalidSelectedCore)
792 .copied()?;
793
794 if core_index != descriptor_core_index {
795 return Err(CommittedCandidateReceiptError::CoreIndexMismatch {
796 descriptor: descriptor_core_index,
797 commitments: core_index,
798 })
799 }
800
801 Ok(())
802 }
803}
804
805#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
807pub struct BackedCandidate<H = Hash> {
808 candidate: CommittedCandidateReceiptV2<H>,
810 validity_votes: Vec<ValidityAttestation>,
812 validator_indices: BitVec<u8, bitvec::order::Lsb0>,
816}
817
818#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, RuntimeDebug, TypeInfo)]
820pub struct InherentData<HDR: HeaderT = Header> {
821 pub bitfields: UncheckedSignedAvailabilityBitfields,
823 pub backed_candidates: Vec<BackedCandidate<HDR::Hash>>,
825 pub disputes: MultiDisputeStatementSet,
827 pub parent_header: HDR,
829}
830
831impl<H> BackedCandidate<H> {
832 pub fn new(
834 candidate: CommittedCandidateReceiptV2<H>,
835 validity_votes: Vec<ValidityAttestation>,
836 validator_indices: BitVec<u8, bitvec::order::Lsb0>,
837 core_index: CoreIndex,
838 ) -> Self {
839 let mut instance = Self { candidate, validity_votes, validator_indices };
840 instance.inject_core_index(core_index);
841 instance
842 }
843
844 pub fn candidate(&self) -> &CommittedCandidateReceiptV2<H> {
846 &self.candidate
847 }
848
849 #[cfg(feature = "test")]
852 pub fn candidate_mut(&mut self) -> &mut CommittedCandidateReceiptV2<H> {
853 &mut self.candidate
854 }
855 pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
857 &self.candidate.descriptor
858 }
859
860 #[cfg(feature = "test")]
862 pub fn descriptor_mut(&mut self) -> &mut CandidateDescriptorV2<H> {
863 &mut self.candidate.descriptor
864 }
865
866 pub fn validity_votes(&self) -> &[ValidityAttestation] {
868 &self.validity_votes
869 }
870
871 pub fn validity_votes_mut(&mut self) -> &mut Vec<ValidityAttestation> {
873 &mut self.validity_votes
874 }
875
876 pub fn hash(&self) -> CandidateHash
878 where
879 H: Clone + Encode,
880 {
881 self.candidate.to_plain().hash()
882 }
883
884 pub fn receipt(&self) -> CandidateReceiptV2<H>
886 where
887 H: Clone,
888 {
889 self.candidate.to_plain()
890 }
891
892 pub fn validator_indices_and_core_index(
894 &self,
895 ) -> (&BitSlice<u8, bitvec::order::Lsb0>, Option<CoreIndex>) {
896 let core_idx_offset = self.validator_indices.len().saturating_sub(8);
898 if core_idx_offset > 0 {
899 let (validator_indices_slice, core_idx_slice) =
900 self.validator_indices.split_at(core_idx_offset);
901 return (validator_indices_slice, Some(CoreIndex(core_idx_slice.load::<u8>() as u32)));
902 }
903
904 (&self.validator_indices, None)
905 }
906
907 fn inject_core_index(&mut self, core_index: CoreIndex) {
909 let core_index_to_inject: BitVec<u8, bitvec::order::Lsb0> =
910 BitVec::from_vec(vec![core_index.0 as u8]);
911 self.validator_indices.extend(core_index_to_inject);
912 }
913
914 pub fn set_validator_indices_and_core_index(
916 &mut self,
917 new_indices: BitVec<u8, bitvec::order::Lsb0>,
918 maybe_core_index: Option<CoreIndex>,
919 ) {
920 self.validator_indices = new_indices;
921
922 if let Some(core_index) = maybe_core_index {
923 self.inject_core_index(core_index);
924 }
925 }
926}
927
928#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
930#[cfg_attr(feature = "std", derive(PartialEq))]
931pub struct ScrapedOnChainVotes<H: Encode + Decode = Hash> {
932 pub session: SessionIndex,
934 pub backing_validators_per_candidate:
937 Vec<(CandidateReceiptV2<H>, Vec<(ValidatorIndex, ValidityAttestation)>)>,
938 pub disputes: MultiDisputeStatementSet,
942}
943
944impl<H: Encode + Decode + Copy> From<ScrapedOnChainVotes<H>> for super::v8::ScrapedOnChainVotes<H> {
945 fn from(value: ScrapedOnChainVotes<H>) -> Self {
946 Self {
947 session: value.session,
948 backing_validators_per_candidate: value
949 .backing_validators_per_candidate
950 .into_iter()
951 .map(|(receipt, validators)| (receipt.into(), validators))
952 .collect::<Vec<_>>(),
953 disputes: value.disputes,
954 }
955 }
956}
957
958#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
960#[cfg_attr(feature = "std", derive(PartialEq))]
961pub struct OccupiedCore<H = Hash, N = BlockNumber> {
962 pub next_up_on_available: Option<ScheduledCore>,
966 pub occupied_since: N,
968 pub time_out_at: N,
970 pub next_up_on_time_out: Option<ScheduledCore>,
974 pub availability: BitVec<u8, bitvec::order::Lsb0>,
978 pub group_responsible: GroupIndex,
980 pub candidate_hash: CandidateHash,
982 pub candidate_descriptor: CandidateDescriptorV2<H>,
984}
985
986impl<H, N> OccupiedCore<H, N> {
987 pub fn para_id(&self) -> Id {
989 self.candidate_descriptor.para_id
990 }
991}
992
993#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
995#[cfg_attr(feature = "std", derive(PartialEq))]
996pub enum CoreState<H = Hash, N = BlockNumber> {
997 #[codec(index = 0)]
999 Occupied(OccupiedCore<H, N>),
1000 #[codec(index = 1)]
1006 Scheduled(ScheduledCore),
1007 #[codec(index = 2)]
1011 Free,
1012}
1013
1014impl<N> CoreState<N> {
1015 #[deprecated(
1020 note = "`para_id` will be removed. Use `ClaimQueue` to query the scheduled `para_id` instead."
1021 )]
1022 pub fn para_id(&self) -> Option<Id> {
1023 match self {
1024 Self::Occupied(ref core) => core.next_up_on_available.as_ref().map(|n| n.para_id),
1025 Self::Scheduled(core) => Some(core.para_id),
1026 Self::Free => None,
1027 }
1028 }
1029
1030 pub fn is_occupied(&self) -> bool {
1032 matches!(self, Self::Occupied(_))
1033 }
1034}
1035
1036impl<H: Copy> From<OccupiedCore<H>> for super::v8::OccupiedCore<H> {
1037 fn from(value: OccupiedCore<H>) -> Self {
1038 Self {
1039 next_up_on_available: value.next_up_on_available,
1040 occupied_since: value.occupied_since,
1041 time_out_at: value.time_out_at,
1042 next_up_on_time_out: value.next_up_on_time_out,
1043 availability: value.availability,
1044 group_responsible: value.group_responsible,
1045 candidate_hash: value.candidate_hash,
1046 candidate_descriptor: value.candidate_descriptor.into(),
1047 }
1048 }
1049}
1050
1051impl<H: Copy> From<CoreState<H>> for super::v8::CoreState<H> {
1052 fn from(value: CoreState<H>) -> Self {
1053 match value {
1054 CoreState::Free => super::v8::CoreState::Free,
1055 CoreState::Scheduled(core) => super::v8::CoreState::Scheduled(core),
1056 CoreState::Occupied(occupied_core) =>
1057 super::v8::CoreState::Occupied(occupied_core.into()),
1058 }
1059 }
1060}
1061
1062pub type TransposedClaimQueue = BTreeMap<ParaId, BTreeMap<u8, BTreeSet<CoreIndex>>>;
1064
1065pub fn transpose_claim_queue(
1068 claim_queue: BTreeMap<CoreIndex, VecDeque<Id>>,
1069) -> TransposedClaimQueue {
1070 let mut per_para_claim_queue = BTreeMap::new();
1071
1072 for (core, paras) in claim_queue {
1073 for (depth, para) in paras.into_iter().enumerate() {
1075 let depths: &mut BTreeMap<u8, BTreeSet<CoreIndex>> =
1076 per_para_claim_queue.entry(para).or_insert_with(|| Default::default());
1077
1078 depths.entry(depth as u8).or_default().insert(core);
1079 }
1080 }
1081
1082 per_para_claim_queue
1083}
1084
1085#[cfg(test)]
1086mod candidate_receipt_tests {
1087 use super::*;
1088 use crate::{
1089 v8::{
1090 tests::dummy_committed_candidate_receipt as dummy_old_committed_candidate_receipt,
1091 CommittedCandidateReceipt, Hash, HeadData, ValidationCode,
1092 },
1093 vstaging::{CandidateDescriptorV2, CommittedCandidateReceiptV2},
1094 };
1095
1096 fn dummy_collator_signature() -> CollatorSignature {
1097 CollatorSignature::from_slice(&mut (0..64).into_iter().collect::<Vec<_>>().as_slice())
1098 .expect("64 bytes; qed")
1099 }
1100
1101 fn dummy_collator_id() -> CollatorId {
1102 CollatorId::from_slice(&mut (0..32).into_iter().collect::<Vec<_>>().as_slice())
1103 .expect("32 bytes; qed")
1104 }
1105
1106 pub fn dummy_committed_candidate_receipt_v2() -> CommittedCandidateReceiptV2 {
1107 let zeros = Hash::zero();
1108 let reserved2 = [0; 64];
1109
1110 CommittedCandidateReceiptV2 {
1111 descriptor: CandidateDescriptorV2 {
1112 para_id: 0.into(),
1113 relay_parent: zeros,
1114 version: InternalVersion(0),
1115 core_index: 123,
1116 session_index: 1,
1117 reserved1: Default::default(),
1118 persisted_validation_data_hash: zeros,
1119 pov_hash: zeros,
1120 erasure_root: zeros,
1121 reserved2,
1122 para_head: zeros,
1123 validation_code_hash: ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).hash(),
1124 },
1125 commitments: CandidateCommitments {
1126 head_data: HeadData(vec![]),
1127 upward_messages: vec![].try_into().expect("empty vec fits within bounds"),
1128 new_validation_code: None,
1129 horizontal_messages: vec![].try_into().expect("empty vec fits within bounds"),
1130 processed_downward_messages: 0,
1131 hrmp_watermark: 0_u32,
1132 },
1133 }
1134 }
1135
1136 #[test]
1137 fn is_binary_compatibile() {
1138 let old_ccr = dummy_old_committed_candidate_receipt();
1139 let new_ccr = dummy_committed_candidate_receipt_v2();
1140
1141 assert_eq!(old_ccr.encoded_size(), new_ccr.encoded_size());
1142
1143 let encoded_old = old_ccr.encode();
1144
1145 let new_ccr: CommittedCandidateReceiptV2 =
1147 Decode::decode(&mut encoded_old.as_slice()).unwrap();
1148
1149 assert_eq!(old_ccr.hash(), new_ccr.hash());
1151 }
1152
1153 #[test]
1154 fn test_from_v1_descriptor() {
1155 let mut old_ccr = dummy_old_committed_candidate_receipt().to_plain();
1156 old_ccr.descriptor.collator = dummy_collator_id();
1157 old_ccr.descriptor.signature = dummy_collator_signature();
1158
1159 let mut new_ccr = dummy_committed_candidate_receipt_v2().to_plain();
1160
1161 new_ccr.descriptor = old_ccr.descriptor.clone().into();
1163
1164 assert_eq!(old_ccr.hash(), new_ccr.hash());
1166
1167 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
1168 assert_eq!(old_ccr.descriptor.collator, new_ccr.descriptor.collator().unwrap());
1169 assert_eq!(old_ccr.descriptor.signature, new_ccr.descriptor.signature().unwrap());
1170 }
1171
1172 #[test]
1173 fn invalid_version_descriptor() {
1174 let mut new_ccr = dummy_committed_candidate_receipt_v2();
1175 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V2);
1176 new_ccr.descriptor.version = InternalVersion(100);
1178
1179 let new_ccr: CommittedCandidateReceiptV2 =
1181 Decode::decode(&mut new_ccr.encode().as_slice()).unwrap();
1182
1183 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::Unknown);
1184 assert_eq!(
1185 new_ccr.parse_ump_signals(&BTreeMap::new()),
1186 Err(CommittedCandidateReceiptError::UnknownVersion(InternalVersion(100)))
1187 );
1188 }
1189
1190 #[test]
1191 fn test_ump_commitments() {
1197 let mut new_ccr = dummy_committed_candidate_receipt_v2();
1198 new_ccr.descriptor.core_index = 123;
1199 new_ccr.descriptor.para_id = ParaId::new(1000);
1200
1201 let mut cq = BTreeMap::new();
1202 cq.insert(
1203 CoreIndex(123),
1204 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1205 );
1206 let cq = transpose_claim_queue(cq);
1207
1208 new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
1212 new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
1213
1214 assert_eq!(
1215 new_ccr.parse_ump_signals(&cq),
1216 Ok(CandidateUMPSignals { select_core: None, approved_peer: None })
1217 );
1218
1219 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1221
1222 assert_eq!(
1223 new_ccr.parse_ump_signals(&cq),
1224 Ok(CandidateUMPSignals { select_core: None, approved_peer: None })
1225 );
1226
1227 {
1229 let mut new_ccr = new_ccr.clone();
1230 new_ccr
1231 .commitments
1232 .upward_messages
1233 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1234
1235 assert_eq!(
1236 new_ccr.parse_ump_signals(&cq),
1237 Ok(CandidateUMPSignals {
1238 select_core: Some((CoreSelector(0), ClaimQueueOffset(1))),
1239 approved_peer: None
1240 })
1241 );
1242 }
1243
1244 {
1245 let mut new_ccr = new_ccr.clone();
1246
1247 new_ccr
1249 .commitments
1250 .upward_messages
1251 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1252
1253 assert_eq!(
1254 new_ccr.parse_ump_signals(&cq),
1255 Ok(CandidateUMPSignals {
1256 select_core: None,
1257 approved_peer: Some(vec![1, 2, 3].try_into().unwrap())
1258 })
1259 );
1260
1261 new_ccr
1264 .commitments
1265 .upward_messages
1266 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1267
1268 assert_eq!(
1269 new_ccr.parse_ump_signals(&cq),
1270 Ok(CandidateUMPSignals {
1271 select_core: Some((CoreSelector(0), ClaimQueueOffset(1))),
1272 approved_peer: Some(vec![1, 2, 3].try_into().unwrap())
1273 })
1274 );
1275 }
1276
1277 new_ccr
1279 .commitments
1280 .upward_messages
1281 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1282 new_ccr
1283 .commitments
1284 .upward_messages
1285 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1286
1287 assert_eq!(
1288 new_ccr.parse_ump_signals(&cq),
1289 Ok(CandidateUMPSignals {
1290 select_core: Some((CoreSelector(0), ClaimQueueOffset(1))),
1291 approved_peer: Some(vec![1, 2, 3].try_into().unwrap())
1292 })
1293 );
1294 }
1295
1296 #[test]
1297 fn test_invalid_ump_commitments() {
1298 let mut new_ccr = dummy_committed_candidate_receipt_v2();
1299 new_ccr.descriptor.core_index = 0;
1300 new_ccr.descriptor.para_id = ParaId::new(1000);
1301
1302 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1303
1304 let mut cq = BTreeMap::new();
1305 cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
1306 let cq = transpose_claim_queue(cq);
1307
1308 new_ccr
1310 .commitments
1311 .upward_messages
1312 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1313
1314 new_ccr.commitments.upward_messages.force_push(vec![0, 13, 200].encode());
1316
1317 assert_eq!(
1319 new_ccr.parse_ump_signals(&cq),
1320 Err(CommittedCandidateReceiptError::UmpSignalDecode)
1321 );
1322 assert_eq!(
1323 new_ccr.commitments.ump_signals(),
1324 Err(CommittedCandidateReceiptError::UmpSignalDecode)
1325 );
1326
1327 {
1329 new_ccr.commitments.upward_messages.clear();
1332 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1333 new_ccr
1334 .commitments
1335 .upward_messages
1336 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1337
1338 let mut cq = BTreeMap::new();
1339 cq.insert(
1340 CoreIndex(0),
1341 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1342 );
1343 cq.insert(
1344 CoreIndex(100),
1345 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1346 );
1347 let cq = transpose_claim_queue(cq);
1348
1349 assert_eq!(
1350 new_ccr.parse_ump_signals(&cq),
1351 Ok(CandidateUMPSignals {
1352 select_core: None,
1353 approved_peer: Some(vec![1, 2, 3].try_into().unwrap())
1354 })
1355 );
1356
1357 new_ccr.descriptor.set_core_index(CoreIndex(1));
1358 assert_eq!(
1359 new_ccr.parse_ump_signals(&cq),
1360 Err(CommittedCandidateReceiptError::InvalidCoreIndex)
1361 );
1362 new_ccr.descriptor.set_core_index(CoreIndex(0));
1363
1364 new_ccr
1365 .commitments
1366 .upward_messages
1367 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1368
1369 assert_eq!(
1371 new_ccr.parse_ump_signals(&transpose_claim_queue(Default::default())),
1372 Err(CommittedCandidateReceiptError::NoAssignment)
1373 );
1374
1375 new_ccr.descriptor.set_core_index(CoreIndex(1));
1377 assert_eq!(
1378 new_ccr.parse_ump_signals(&cq),
1379 Err(CommittedCandidateReceiptError::CoreIndexMismatch {
1380 descriptor: CoreIndex(1),
1381 commitments: CoreIndex(0),
1382 })
1383 );
1384 }
1385
1386 new_ccr.descriptor.set_core_index(CoreIndex(0));
1387
1388 new_ccr.commitments.upward_messages.clear();
1390 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1391 new_ccr
1392 .commitments
1393 .upward_messages
1394 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1395 new_ccr
1396 .commitments
1397 .upward_messages
1398 .force_push(UMPSignal::ApprovedPeer(vec![4, 5].try_into().unwrap()).encode());
1399
1400 assert_eq!(
1401 new_ccr.parse_ump_signals(&cq),
1402 Err(CommittedCandidateReceiptError::DuplicateUMPSignal)
1403 );
1404
1405 new_ccr.commitments.upward_messages.clear();
1407 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1408 new_ccr
1409 .commitments
1410 .upward_messages
1411 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1412 new_ccr
1413 .commitments
1414 .upward_messages
1415 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(0)).encode());
1416 new_ccr
1417 .commitments
1418 .upward_messages
1419 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1420
1421 assert_eq!(
1422 new_ccr.parse_ump_signals(&cq),
1423 Err(CommittedCandidateReceiptError::TooManyUMPSignals)
1424 );
1425 }
1426
1427 #[test]
1428 fn test_version2_receipts_decoded_as_v1() {
1429 let mut new_ccr = dummy_committed_candidate_receipt_v2();
1430 new_ccr.descriptor.core_index = 123;
1431 new_ccr.descriptor.para_id = ParaId::new(1000);
1432
1433 new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
1435 new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
1436
1437 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1439
1440 new_ccr
1442 .commitments
1443 .upward_messages
1444 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1445
1446 let encoded_ccr = new_ccr.encode();
1447 let decoded_ccr: CommittedCandidateReceipt =
1448 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1449
1450 assert_eq!(decoded_ccr.descriptor.relay_parent, new_ccr.descriptor.relay_parent());
1451 assert_eq!(decoded_ccr.descriptor.para_id, new_ccr.descriptor.para_id());
1452
1453 assert_eq!(new_ccr.hash(), decoded_ccr.hash());
1454
1455 let encoded_ccr = new_ccr.encode();
1457 let v2_ccr: CommittedCandidateReceiptV2 =
1458 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1459
1460 assert_eq!(v2_ccr.descriptor.core_index(), Some(CoreIndex(123)));
1461
1462 let mut cq = BTreeMap::new();
1463 cq.insert(
1464 CoreIndex(123),
1465 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1466 );
1467
1468 assert!(new_ccr.parse_ump_signals(&transpose_claim_queue(cq)).is_ok());
1469
1470 assert_eq!(new_ccr.hash(), v2_ccr.hash());
1471 }
1472
1473 #[test]
1475 fn test_v1_descriptors_with_ump_signal() {
1476 let mut ccr = dummy_old_committed_candidate_receipt();
1477 ccr.descriptor.para_id = ParaId::new(1024);
1478 ccr.descriptor.signature = dummy_collator_signature();
1480 ccr.descriptor.collator = dummy_collator_id();
1481
1482 ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1483 ccr.commitments
1484 .upward_messages
1485 .force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode());
1486
1487 ccr.commitments
1488 .upward_messages
1489 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1490
1491 let encoded_ccr: Vec<u8> = ccr.encode();
1492
1493 let v1_ccr: CommittedCandidateReceiptV2 =
1494 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1495
1496 assert_eq!(v1_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
1497 assert!(!v1_ccr.commitments.ump_signals().unwrap().is_empty());
1498
1499 let mut cq = BTreeMap::new();
1500 cq.insert(CoreIndex(0), vec![v1_ccr.descriptor.para_id()].into());
1501 cq.insert(CoreIndex(1), vec![v1_ccr.descriptor.para_id()].into());
1502
1503 assert_eq!(v1_ccr.descriptor.core_index(), None);
1504
1505 assert_eq!(
1506 v1_ccr.parse_ump_signals(&transpose_claim_queue(cq)),
1507 Err(CommittedCandidateReceiptError::UMPSignalWithV1Decriptor)
1508 );
1509 }
1510
1511 #[test]
1512 fn test_core_select_is_optional() {
1513 let mut old_ccr = dummy_old_committed_candidate_receipt();
1515 old_ccr.descriptor.para_id = ParaId::new(1000);
1516 let encoded_ccr: Vec<u8> = old_ccr.encode();
1517
1518 let new_ccr: CommittedCandidateReceiptV2 =
1519 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1520
1521 let mut cq = BTreeMap::new();
1522 cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
1523
1524 assert!(new_ccr.parse_ump_signals(&transpose_claim_queue(cq)).is_ok());
1527
1528 let mut cq = BTreeMap::new();
1529 cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
1530 cq.insert(CoreIndex(1), vec![new_ccr.descriptor.para_id()].into());
1531
1532 assert!(new_ccr.parse_ump_signals(&transpose_claim_queue(cq)).is_ok());
1535
1536 old_ccr.descriptor.signature = dummy_collator_signature();
1538 old_ccr.descriptor.collator = dummy_collator_id();
1539
1540 let old_ccr_hash = old_ccr.hash();
1541
1542 let encoded_ccr: Vec<u8> = old_ccr.encode();
1543
1544 let new_ccr: CommittedCandidateReceiptV2 =
1545 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1546
1547 assert_eq!(new_ccr.descriptor.signature(), Some(old_ccr.descriptor.signature));
1548 assert_eq!(new_ccr.descriptor.collator(), Some(old_ccr.descriptor.collator));
1549
1550 assert_eq!(new_ccr.descriptor.core_index(), None);
1551 assert_eq!(new_ccr.descriptor.para_id(), ParaId::new(1000));
1552
1553 assert_eq!(old_ccr_hash, new_ccr.hash());
1554 }
1555}
1556
1557#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, TypeInfo, Debug)]
1560pub enum DisputeOffenceKind {
1561 #[codec(index = 0)]
1564 ForInvalidBacked,
1565 #[codec(index = 1)]
1568 AgainstValid,
1569 #[codec(index = 2)]
1572 ForInvalidApproved,
1573}
1574
1575impl From<super::v8::slashing::SlashingOffenceKind> for DisputeOffenceKind {
1579 fn from(value: super::v8::slashing::SlashingOffenceKind) -> Self {
1580 match value {
1581 super::v8::slashing::SlashingOffenceKind::ForInvalid => Self::ForInvalidBacked,
1582 super::v8::slashing::SlashingOffenceKind::AgainstValid => Self::AgainstValid,
1583 }
1584 }
1585}
1586
1587impl TryFrom<DisputeOffenceKind> for super::v8::slashing::SlashingOffenceKind {
1589 type Error = ();
1590
1591 fn try_from(value: DisputeOffenceKind) -> Result<Self, Self::Error> {
1592 match value {
1593 DisputeOffenceKind::ForInvalidBacked => Ok(Self::ForInvalid),
1594 DisputeOffenceKind::AgainstValid => Ok(Self::AgainstValid),
1595 DisputeOffenceKind::ForInvalidApproved => Err(()),
1596 }
1597 }
1598}
1599
1600#[derive(Encode, Decode, TypeInfo, Debug, Clone)]
1603pub struct PendingSlashes {
1604 pub keys: BTreeMap<ValidatorIndex, ValidatorId>,
1607 pub kind: DisputeOffenceKind,
1609}
1610
1611impl From<super::v8::slashing::PendingSlashes> for PendingSlashes {
1612 fn from(old: super::v8::slashing::PendingSlashes) -> Self {
1613 let keys = old.keys;
1614 let kind = old.kind.into();
1615 Self { keys, kind }
1616 }
1617}
1618
1619impl TryFrom<PendingSlashes> for super::v8::slashing::PendingSlashes {
1620 type Error = ();
1621
1622 fn try_from(value: PendingSlashes) -> Result<Self, Self::Error> {
1623 Ok(Self { keys: value.keys, kind: value.kind.try_into()? })
1624 }
1625}
1626
1627#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, Debug)]
1630pub struct DisputeProof {
1631 pub time_slot: DisputesTimeSlot,
1633 pub kind: DisputeOffenceKind,
1635 pub validator_index: ValidatorIndex,
1637 pub validator_id: ValidatorId,
1639}
1640
1641impl From<super::v8::slashing::DisputeProof> for DisputeProof {
1642 fn from(old: super::v8::slashing::DisputeProof) -> Self {
1643 let time_slot = old.time_slot;
1644 let kind = old.kind.into(); let validator_index = old.validator_index;
1646 let validator_id = old.validator_id;
1647 Self { time_slot, kind, validator_index, validator_id }
1648 }
1649}
1650
1651impl TryFrom<DisputeProof> for super::v8::slashing::DisputeProof {
1652 type Error = ();
1653
1654 fn try_from(value: DisputeProof) -> Result<Self, Self::Error> {
1655 Ok(Self {
1656 time_slot: value.time_slot,
1657 kind: value.kind.try_into()?,
1658 validator_index: value.validator_index,
1659 validator_id: value.validator_id,
1660 })
1661 }
1662}