1use crate::{ValidatorIndex, ValidityAttestation};
19
20use super::{
22 async_backing::Constraints, BlakeTwo256, BlockNumber, CandidateCommitments,
23 CandidateDescriptor, CandidateHash, CollatorId, CollatorSignature, CoreIndex, GroupIndex, Hash,
24 HashT, HeadData, Header, Id, Id as ParaId, MultiDisputeStatementSet, ScheduledCore,
25 UncheckedSignedAvailabilityBitfields, ValidationCodeHash,
26};
27use bitvec::prelude::*;
28use sp_application_crypto::ByteArray;
29
30use alloc::{vec, vec::Vec};
31use codec::{Decode, Encode};
32use scale_info::TypeInfo;
33use sp_core::RuntimeDebug;
34use sp_runtime::traits::Header as HeaderT;
35use sp_staking::SessionIndex;
36pub mod async_backing;
38
39#[derive(PartialEq, Eq, Encode, Decode, Clone, TypeInfo, RuntimeDebug, Copy)]
41#[cfg_attr(feature = "std", derive(Hash))]
42pub struct InternalVersion(pub u8);
43
44#[derive(PartialEq, Eq, Clone, TypeInfo, RuntimeDebug)]
46#[cfg_attr(feature = "std", derive(Hash))]
47pub enum CandidateDescriptorVersion {
48 V1,
50 V2,
52 Unknown,
54}
55
56#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
58#[cfg_attr(feature = "std", derive(Hash))]
59pub struct CandidateDescriptorV2<H = Hash> {
60 para_id: ParaId,
62 relay_parent: H,
64 version: InternalVersion,
69 core_index: u16,
71 session_index: SessionIndex,
73 reserved1: [u8; 25],
75 persisted_validation_data_hash: Hash,
79 pov_hash: Hash,
81 erasure_root: Hash,
83 reserved2: [u8; 64],
85 para_head: Hash,
87 validation_code_hash: ValidationCodeHash,
89}
90
91impl<H: Copy> From<CandidateDescriptorV2<H>> for CandidateDescriptor<H> {
92 fn from(value: CandidateDescriptorV2<H>) -> Self {
93 Self {
94 para_id: value.para_id,
95 relay_parent: value.relay_parent,
96 collator: value.rebuild_collator_field(),
97 persisted_validation_data_hash: value.persisted_validation_data_hash,
98 pov_hash: value.pov_hash,
99 erasure_root: value.erasure_root,
100 signature: value.rebuild_signature_field(),
101 para_head: value.para_head,
102 validation_code_hash: value.validation_code_hash,
103 }
104 }
105}
106
107#[cfg(any(feature = "runtime-benchmarks", feature = "test"))]
108impl<H: Encode + Decode + Copy> From<CandidateDescriptor<H>> for CandidateDescriptorV2<H> {
109 fn from(value: CandidateDescriptor<H>) -> Self {
110 Decode::decode(&mut value.encode().as_slice()).unwrap()
111 }
112}
113
114impl<H> CandidateDescriptorV2<H> {
115 pub fn new(
117 para_id: Id,
118 relay_parent: H,
119 core_index: CoreIndex,
120 session_index: SessionIndex,
121 persisted_validation_data_hash: Hash,
122 pov_hash: Hash,
123 erasure_root: Hash,
124 para_head: Hash,
125 validation_code_hash: ValidationCodeHash,
126 ) -> Self {
127 Self {
128 para_id,
129 relay_parent,
130 version: InternalVersion(0),
131 core_index: core_index.0 as u16,
132 session_index,
133 reserved1: [0; 25],
134 persisted_validation_data_hash,
135 pov_hash,
136 erasure_root,
137 reserved2: [0; 64],
138 para_head,
139 validation_code_hash,
140 }
141 }
142
143 #[cfg(feature = "test")]
145 pub fn set_pov_hash(&mut self, pov_hash: Hash) {
146 self.pov_hash = pov_hash;
147 }
148
149 #[cfg(feature = "test")]
151 pub fn set_version(&mut self, version: InternalVersion) {
152 self.version = version;
153 }
154}
155
156#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
158#[cfg_attr(feature = "std", derive(Hash))]
159pub struct CandidateReceiptV2<H = Hash> {
160 pub descriptor: CandidateDescriptorV2<H>,
162 pub commitments_hash: Hash,
164}
165
166#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
168#[cfg_attr(feature = "std", derive(Hash))]
169pub struct CommittedCandidateReceiptV2<H = Hash> {
170 pub descriptor: CandidateDescriptorV2<H>,
172 pub commitments: CandidateCommitments,
174}
175
176#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
178#[cfg_attr(feature = "std", derive(PartialEq))]
179pub enum CandidateEvent<H = Hash> {
180 #[codec(index = 0)]
183 CandidateBacked(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
184 #[codec(index = 1)]
188 CandidateIncluded(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
189 #[codec(index = 2)]
192 CandidateTimedOut(CandidateReceiptV2<H>, HeadData, CoreIndex),
193}
194
195impl<H: Encode + Copy> From<CandidateEvent<H>> for super::v8::CandidateEvent<H> {
196 fn from(value: CandidateEvent<H>) -> Self {
197 match value {
198 CandidateEvent::CandidateBacked(receipt, head_data, core_index, group_index) =>
199 super::v8::CandidateEvent::CandidateBacked(
200 receipt.into(),
201 head_data,
202 core_index,
203 group_index,
204 ),
205 CandidateEvent::CandidateIncluded(receipt, head_data, core_index, group_index) =>
206 super::v8::CandidateEvent::CandidateIncluded(
207 receipt.into(),
208 head_data,
209 core_index,
210 group_index,
211 ),
212 CandidateEvent::CandidateTimedOut(receipt, head_data, core_index) =>
213 super::v8::CandidateEvent::CandidateTimedOut(receipt.into(), head_data, core_index),
214 }
215 }
216}
217
218impl<H> CandidateReceiptV2<H> {
219 pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
221 &self.descriptor
222 }
223
224 pub fn hash(&self) -> CandidateHash
226 where
227 H: Encode,
228 {
229 CandidateHash(BlakeTwo256::hash_of(self))
230 }
231}
232
233impl<H: Clone> CommittedCandidateReceiptV2<H> {
234 pub fn to_plain(&self) -> CandidateReceiptV2<H> {
236 CandidateReceiptV2 {
237 descriptor: self.descriptor.clone(),
238 commitments_hash: self.commitments.hash(),
239 }
240 }
241
242 pub fn hash(&self) -> CandidateHash
247 where
248 H: Encode,
249 {
250 self.to_plain().hash()
251 }
252
253 pub fn corresponds_to(&self, receipt: &CandidateReceiptV2<H>) -> bool
255 where
256 H: PartialEq,
257 {
258 receipt.descriptor == self.descriptor && receipt.commitments_hash == self.commitments.hash()
259 }
260}
261
262impl PartialOrd for CommittedCandidateReceiptV2 {
263 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
264 Some(self.cmp(other))
265 }
266}
267
268impl Ord for CommittedCandidateReceiptV2 {
269 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
270 self.descriptor
271 .para_id
272 .cmp(&other.descriptor.para_id)
273 .then_with(|| self.commitments.head_data.cmp(&other.commitments.head_data))
274 }
275}
276
277impl<H: Copy> From<CommittedCandidateReceiptV2<H>> for super::v8::CommittedCandidateReceipt<H> {
278 fn from(value: CommittedCandidateReceiptV2<H>) -> Self {
279 Self { descriptor: value.descriptor.into(), commitments: value.commitments }
280 }
281}
282
283impl<H: Copy> From<CandidateReceiptV2<H>> for super::v8::CandidateReceipt<H> {
284 fn from(value: CandidateReceiptV2<H>) -> Self {
285 Self { descriptor: value.descriptor.into(), commitments_hash: value.commitments_hash }
286 }
287}
288
289#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
292pub struct CoreSelector(pub u8);
293
294#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
296pub struct ClaimQueueOffset(pub u8);
297
298#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
300pub enum UMPSignal {
301 SelectCore(CoreSelector, ClaimQueueOffset),
305}
306pub const UMP_SEPARATOR: Vec<u8> = vec![];
308
309impl CandidateCommitments {
310 pub fn selected_core(&self) -> Option<(CoreSelector, ClaimQueueOffset)> {
312 if self.upward_messages.len() < 2 {
314 return None
315 }
316
317 let separator_pos =
318 self.upward_messages.iter().rposition(|message| message == &UMP_SEPARATOR)?;
319
320 let message = self.upward_messages.get(separator_pos + 1)?;
322
323 match UMPSignal::decode(&mut message.as_slice()).ok()? {
324 UMPSignal::SelectCore(core_selector, cq_offset) => Some((core_selector, cq_offset)),
325 }
326 }
327}
328
329#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
331pub enum CandidateReceiptError {
332 InvalidCoreIndex,
334 CoreIndexMismatch,
336 InvalidSelectedCore,
338 NoAssignment,
340 NoCoreSelected,
342 UnknownVersion(InternalVersion),
344}
345
346macro_rules! impl_getter {
347 ($field:ident, $type:ident) => {
348 pub fn $field(&self) -> $type {
350 self.$field
351 }
352 };
353}
354
355impl<H: Copy> CandidateDescriptorV2<H> {
356 impl_getter!(erasure_root, Hash);
357 impl_getter!(para_head, Hash);
358 impl_getter!(relay_parent, H);
359 impl_getter!(para_id, ParaId);
360 impl_getter!(persisted_validation_data_hash, Hash);
361 impl_getter!(pov_hash, Hash);
362 impl_getter!(validation_code_hash, ValidationCodeHash);
363
364 pub fn version(&self) -> CandidateDescriptorVersion {
368 if self.reserved2 != [0u8; 64] || self.reserved1 != [0u8; 25] {
369 return CandidateDescriptorVersion::V1
370 }
371
372 match self.version.0 {
373 0 => CandidateDescriptorVersion::V2,
374 _ => CandidateDescriptorVersion::Unknown,
375 }
376 }
377
378 fn rebuild_collator_field(&self) -> CollatorId {
379 let mut collator_id = Vec::with_capacity(32);
380 let core_index: [u8; 2] = self.core_index.to_ne_bytes();
381 let session_index: [u8; 4] = self.session_index.to_ne_bytes();
382
383 collator_id.push(self.version.0);
384 collator_id.extend_from_slice(core_index.as_slice());
385 collator_id.extend_from_slice(session_index.as_slice());
386 collator_id.extend_from_slice(self.reserved1.as_slice());
387
388 CollatorId::from_slice(&collator_id.as_slice())
389 .expect("Slice size is exactly 32 bytes; qed")
390 }
391
392 pub fn collator(&self) -> Option<CollatorId> {
394 if self.version() == CandidateDescriptorVersion::V1 {
395 Some(self.rebuild_collator_field())
396 } else {
397 None
398 }
399 }
400
401 fn rebuild_signature_field(&self) -> CollatorSignature {
402 CollatorSignature::from_slice(self.reserved2.as_slice())
403 .expect("Slice size is exactly 64 bytes; qed")
404 }
405
406 pub fn signature(&self) -> Option<CollatorSignature> {
408 if self.version() == CandidateDescriptorVersion::V1 {
409 return Some(self.rebuild_signature_field())
410 }
411
412 None
413 }
414
415 pub fn core_index(&self) -> Option<CoreIndex> {
417 if self.version() == CandidateDescriptorVersion::V1 {
418 return None
419 }
420
421 Some(CoreIndex(self.core_index as u32))
422 }
423
424 pub fn session_index(&self) -> Option<SessionIndex> {
426 if self.version() == CandidateDescriptorVersion::V1 {
427 return None
428 }
429
430 Some(self.session_index)
431 }
432}
433
434impl<H: Copy> CommittedCandidateReceiptV2<H> {
435 pub fn check(&self, assigned_cores: &[CoreIndex]) -> Result<(), CandidateReceiptError> {
439 if self.descriptor.version() == CandidateDescriptorVersion::V1 {
441 return Ok(())
442 }
443
444 if self.descriptor.version() == CandidateDescriptorVersion::Unknown {
445 return Err(CandidateReceiptError::UnknownVersion(self.descriptor.version))
446 }
447
448 if assigned_cores.is_empty() {
449 return Err(CandidateReceiptError::NoAssignment)
450 }
451
452 let descriptor_core_index = CoreIndex(self.descriptor.core_index as u32);
453
454 let (core_selector, _cq_offset) =
455 self.commitments.selected_core().ok_or(CandidateReceiptError::NoCoreSelected)?;
456
457 let core_index = assigned_cores
458 .get(core_selector.0 as usize % assigned_cores.len())
459 .ok_or(CandidateReceiptError::InvalidCoreIndex)?;
460
461 if *core_index != descriptor_core_index {
462 return Err(CandidateReceiptError::CoreIndexMismatch)
463 }
464
465 Ok(())
466 }
467}
468
469#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
471pub struct BackedCandidate<H = Hash> {
472 candidate: CommittedCandidateReceiptV2<H>,
474 validity_votes: Vec<ValidityAttestation>,
476 validator_indices: BitVec<u8, bitvec::order::Lsb0>,
480}
481
482#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)]
484pub struct InherentData<HDR: HeaderT = Header> {
485 pub bitfields: UncheckedSignedAvailabilityBitfields,
487 pub backed_candidates: Vec<BackedCandidate<HDR::Hash>>,
489 pub disputes: MultiDisputeStatementSet,
491 pub parent_header: HDR,
493}
494
495impl<H> BackedCandidate<H> {
496 pub fn new(
498 candidate: CommittedCandidateReceiptV2<H>,
499 validity_votes: Vec<ValidityAttestation>,
500 validator_indices: BitVec<u8, bitvec::order::Lsb0>,
501 core_index: Option<CoreIndex>,
502 ) -> Self {
503 let mut instance = Self { candidate, validity_votes, validator_indices };
504 if let Some(core_index) = core_index {
505 instance.inject_core_index(core_index);
506 }
507 instance
508 }
509
510 pub fn candidate(&self) -> &CommittedCandidateReceiptV2<H> {
512 &self.candidate
513 }
514
515 pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
517 &self.candidate.descriptor
518 }
519
520 #[cfg(feature = "test")]
522 pub fn descriptor_mut(&mut self) -> &mut CandidateDescriptorV2<H> {
523 &mut self.candidate.descriptor
524 }
525
526 pub fn validity_votes(&self) -> &[ValidityAttestation] {
528 &self.validity_votes
529 }
530
531 pub fn validity_votes_mut(&mut self) -> &mut Vec<ValidityAttestation> {
533 &mut self.validity_votes
534 }
535
536 pub fn hash(&self) -> CandidateHash
538 where
539 H: Clone + Encode,
540 {
541 self.candidate.to_plain().hash()
542 }
543
544 pub fn receipt(&self) -> CandidateReceiptV2<H>
546 where
547 H: Clone,
548 {
549 self.candidate.to_plain()
550 }
551
552 pub fn validator_indices_and_core_index(
554 &self,
555 core_index_enabled: bool,
556 ) -> (&BitSlice<u8, bitvec::order::Lsb0>, Option<CoreIndex>) {
557 if core_index_enabled {
560 let core_idx_offset = self.validator_indices.len().saturating_sub(8);
561 if core_idx_offset > 0 {
562 let (validator_indices_slice, core_idx_slice) =
563 self.validator_indices.split_at(core_idx_offset);
564 return (
565 validator_indices_slice,
566 Some(CoreIndex(core_idx_slice.load::<u8>() as u32)),
567 );
568 }
569 }
570
571 (&self.validator_indices, None)
572 }
573
574 fn inject_core_index(&mut self, core_index: CoreIndex) {
576 let core_index_to_inject: BitVec<u8, bitvec::order::Lsb0> =
577 BitVec::from_vec(vec![core_index.0 as u8]);
578 self.validator_indices.extend(core_index_to_inject);
579 }
580
581 pub fn set_validator_indices_and_core_index(
583 &mut self,
584 new_indices: BitVec<u8, bitvec::order::Lsb0>,
585 maybe_core_index: Option<CoreIndex>,
586 ) {
587 self.validator_indices = new_indices;
588
589 if let Some(core_index) = maybe_core_index {
590 self.inject_core_index(core_index);
591 }
592 }
593}
594
595#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
597#[cfg_attr(feature = "std", derive(PartialEq))]
598pub struct ScrapedOnChainVotes<H: Encode + Decode = Hash> {
599 pub session: SessionIndex,
601 pub backing_validators_per_candidate:
604 Vec<(CandidateReceiptV2<H>, Vec<(ValidatorIndex, ValidityAttestation)>)>,
605 pub disputes: MultiDisputeStatementSet,
609}
610
611impl<H: Encode + Decode + Copy> From<ScrapedOnChainVotes<H>> for super::v8::ScrapedOnChainVotes<H> {
612 fn from(value: ScrapedOnChainVotes<H>) -> Self {
613 Self {
614 session: value.session,
615 backing_validators_per_candidate: value
616 .backing_validators_per_candidate
617 .into_iter()
618 .map(|(receipt, validators)| (receipt.into(), validators))
619 .collect::<Vec<_>>(),
620 disputes: value.disputes,
621 }
622 }
623}
624
625#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
627#[cfg_attr(feature = "std", derive(PartialEq))]
628pub struct OccupiedCore<H = Hash, N = BlockNumber> {
629 pub next_up_on_available: Option<ScheduledCore>,
633 pub occupied_since: N,
635 pub time_out_at: N,
637 pub next_up_on_time_out: Option<ScheduledCore>,
641 pub availability: BitVec<u8, bitvec::order::Lsb0>,
645 pub group_responsible: GroupIndex,
647 pub candidate_hash: CandidateHash,
649 pub candidate_descriptor: CandidateDescriptorV2<H>,
651}
652
653#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
655#[cfg_attr(feature = "std", derive(PartialEq))]
656pub enum CoreState<H = Hash, N = BlockNumber> {
657 #[codec(index = 0)]
659 Occupied(OccupiedCore<H, N>),
660 #[codec(index = 1)]
666 Scheduled(ScheduledCore),
667 #[codec(index = 2)]
671 Free,
672}
673
674impl<H: Copy> From<OccupiedCore<H>> for super::v8::OccupiedCore<H> {
675 fn from(value: OccupiedCore<H>) -> Self {
676 Self {
677 next_up_on_available: value.next_up_on_available,
678 occupied_since: value.occupied_since,
679 time_out_at: value.time_out_at,
680 next_up_on_time_out: value.next_up_on_time_out,
681 availability: value.availability,
682 group_responsible: value.group_responsible,
683 candidate_hash: value.candidate_hash,
684 candidate_descriptor: value.candidate_descriptor.into(),
685 }
686 }
687}
688
689impl<H: Copy> From<CoreState<H>> for super::v8::CoreState<H> {
690 fn from(value: CoreState<H>) -> Self {
691 match value {
692 CoreState::Free => super::v8::CoreState::Free,
693 CoreState::Scheduled(core) => super::v8::CoreState::Scheduled(core),
694 CoreState::Occupied(occupied_core) =>
695 super::v8::CoreState::Occupied(occupied_core.into()),
696 }
697 }
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703 use crate::{
704 v8::{
705 tests::dummy_committed_candidate_receipt as dummy_old_committed_candidate_receipt,
706 CommittedCandidateReceipt, Hash, HeadData, ValidationCode,
707 },
708 vstaging::{CandidateDescriptorV2, CommittedCandidateReceiptV2},
709 };
710
711 fn dummy_collator_signature() -> CollatorSignature {
712 CollatorSignature::from_slice(&mut (0..64).into_iter().collect::<Vec<_>>().as_slice())
713 .expect("64 bytes; qed")
714 }
715
716 fn dummy_collator_id() -> CollatorId {
717 CollatorId::from_slice(&mut (0..32).into_iter().collect::<Vec<_>>().as_slice())
718 .expect("32 bytes; qed")
719 }
720
721 pub fn dummy_committed_candidate_receipt_v2() -> CommittedCandidateReceiptV2 {
722 let zeros = Hash::zero();
723 let reserved2 = [0; 64];
724
725 CommittedCandidateReceiptV2 {
726 descriptor: CandidateDescriptorV2 {
727 para_id: 0.into(),
728 relay_parent: zeros,
729 version: InternalVersion(0),
730 core_index: 123,
731 session_index: 1,
732 reserved1: Default::default(),
733 persisted_validation_data_hash: zeros,
734 pov_hash: zeros,
735 erasure_root: zeros,
736 reserved2,
737 para_head: zeros,
738 validation_code_hash: ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).hash(),
739 },
740 commitments: CandidateCommitments {
741 head_data: HeadData(vec![]),
742 upward_messages: vec![].try_into().expect("empty vec fits within bounds"),
743 new_validation_code: None,
744 horizontal_messages: vec![].try_into().expect("empty vec fits within bounds"),
745 processed_downward_messages: 0,
746 hrmp_watermark: 0_u32,
747 },
748 }
749 }
750
751 #[test]
752 fn is_binary_compatibile() {
753 let old_ccr = dummy_old_committed_candidate_receipt();
754 let new_ccr = dummy_committed_candidate_receipt_v2();
755
756 assert_eq!(old_ccr.encoded_size(), new_ccr.encoded_size());
757
758 let encoded_old = old_ccr.encode();
759
760 let new_ccr: CommittedCandidateReceiptV2 =
762 Decode::decode(&mut encoded_old.as_slice()).unwrap();
763
764 assert_eq!(old_ccr.hash(), new_ccr.hash());
766 }
767
768 #[test]
769 fn invalid_version_descriptor() {
770 let mut new_ccr = dummy_committed_candidate_receipt_v2();
771 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V2);
772 new_ccr.descriptor.version = InternalVersion(100);
774
775 let new_ccr: CommittedCandidateReceiptV2 =
777 Decode::decode(&mut new_ccr.encode().as_slice()).unwrap();
778
779 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::Unknown);
780 assert_eq!(
781 new_ccr.check(&vec![].as_slice()),
782 Err(CandidateReceiptError::UnknownVersion(InternalVersion(100)))
783 )
784 }
785
786 #[test]
787 fn test_ump_commitment() {
788 let mut new_ccr = dummy_committed_candidate_receipt_v2();
789 new_ccr.descriptor.core_index = 123;
790 new_ccr.descriptor.para_id = ParaId::new(1000);
791
792 new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
794 new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
795
796 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
798
799 new_ccr
801 .commitments
802 .upward_messages
803 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
804
805 assert_eq!(new_ccr.check(&vec![CoreIndex(123)]), Ok(()));
806 }
807
808 #[test]
809 fn test_invalid_ump_commitment() {
810 let mut new_ccr = dummy_committed_candidate_receipt_v2();
811 new_ccr.descriptor.core_index = 0;
812 new_ccr.descriptor.para_id = ParaId::new(1000);
813
814 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
815 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
816
817 assert_eq!(
819 new_ccr.check(&vec![CoreIndex(0), CoreIndex(100)]),
820 Err(CandidateReceiptError::NoCoreSelected)
821 );
822
823 new_ccr.commitments.upward_messages.force_push(vec![0, 13, 200].encode());
825
826 assert_eq!(new_ccr.commitments.selected_core(), None);
828
829 assert_eq!(
831 new_ccr.check(&vec![CoreIndex(0), CoreIndex(100)]),
832 Err(CandidateReceiptError::NoCoreSelected)
833 );
834
835 new_ccr.commitments.upward_messages.clear();
836 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
837
838 new_ccr
839 .commitments
840 .upward_messages
841 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
842
843 new_ccr
845 .commitments
846 .upward_messages
847 .force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode());
848
849 assert_eq!(new_ccr.check(&vec![CoreIndex(0), CoreIndex(100)]), Ok(()));
851 }
852
853 #[test]
854 fn test_version2_receipts_decoded_as_v1() {
855 let mut new_ccr = dummy_committed_candidate_receipt_v2();
856 new_ccr.descriptor.core_index = 123;
857 new_ccr.descriptor.para_id = ParaId::new(1000);
858
859 new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
861 new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
862
863 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
865
866 new_ccr
868 .commitments
869 .upward_messages
870 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
871
872 let encoded_ccr = new_ccr.encode();
873 let decoded_ccr: CommittedCandidateReceipt =
874 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
875
876 assert_eq!(decoded_ccr.descriptor.relay_parent, new_ccr.descriptor.relay_parent());
877 assert_eq!(decoded_ccr.descriptor.para_id, new_ccr.descriptor.para_id());
878
879 assert_eq!(new_ccr.hash(), decoded_ccr.hash());
880
881 let encoded_ccr = new_ccr.encode();
883 let v2_ccr: CommittedCandidateReceiptV2 =
884 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
885
886 assert_eq!(v2_ccr.descriptor.core_index(), Some(CoreIndex(123)));
887 assert_eq!(new_ccr.check(&vec![CoreIndex(123)]), Ok(()));
888
889 assert_eq!(new_ccr.hash(), v2_ccr.hash());
890 }
891
892 #[test]
893 fn test_core_select_is_mandatory() {
894 let mut old_ccr = dummy_old_committed_candidate_receipt();
896 old_ccr.descriptor.para_id = ParaId::new(1000);
897 let encoded_ccr: Vec<u8> = old_ccr.encode();
898
899 let new_ccr: CommittedCandidateReceiptV2 =
900 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
901
902 assert_eq!(new_ccr.check(&vec![CoreIndex(0)]), Err(CandidateReceiptError::NoCoreSelected));
907
908 old_ccr.descriptor.signature = dummy_collator_signature();
910 old_ccr.descriptor.collator = dummy_collator_id();
911
912 let old_ccr_hash = old_ccr.hash();
913
914 let encoded_ccr: Vec<u8> = old_ccr.encode();
915
916 let new_ccr: CommittedCandidateReceiptV2 =
917 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
918
919 assert_eq!(new_ccr.descriptor.signature(), Some(old_ccr.descriptor.signature));
920 assert_eq!(new_ccr.descriptor.collator(), Some(old_ccr.descriptor.collator));
921
922 assert_eq!(new_ccr.descriptor.core_index(), None);
923 assert_eq!(new_ccr.descriptor.para_id(), ParaId::new(1000));
924
925 assert_eq!(old_ccr_hash, new_ccr.hash());
926 }
927}