1#![forbid(unused_crate_dependencies)]
18#![forbid(unused_extern_crates)]
19
20use codec::{Decode, Encode};
26use polkadot_primitives::{
27 AppVerify, CandidateCommitments, CandidateDescriptorV2, CandidateHash, CandidateReceiptV2,
28 CollatorId, CollatorSignature, CommittedCandidateReceiptV2, CoreIndex, Hash, HashT, HeadData,
29 Id, Id as ParaId, InternalVersion, MutateDescriptorV2, PersistedValidationData, SessionIndex,
30 ValidationCode, ValidationCodeHash, ValidatorId,
31};
32pub use rand;
33use scale_info::TypeInfo;
34use sp_application_crypto::{sr25519, ByteArray};
35use sp_keyring::Sr25519Keyring;
36use sp_runtime::{generic::Digest, traits::BlakeTwo256};
37
38const MAX_POV_SIZE: u32 = 1_000_000;
39
40#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
42pub struct CandidateDescriptor<H = Hash> {
43 pub para_id: Id,
45 pub relay_parent: H,
47 pub collator: CollatorId,
49 pub persisted_validation_data_hash: Hash,
53 pub pov_hash: Hash,
55 pub erasure_root: Hash,
57 pub signature: CollatorSignature,
60 pub para_head: Hash,
62 pub validation_code_hash: ValidationCodeHash,
64}
65
66impl<H: AsRef<[u8]>> CandidateDescriptor<H> {
67 pub fn check_collator_signature(&self) -> Result<(), ()> {
69 check_collator_signature(
70 &self.relay_parent,
71 &self.para_id,
72 &self.persisted_validation_data_hash,
73 &self.pov_hash,
74 &self.validation_code_hash,
75 &self.collator,
76 &self.signature,
77 )
78 }
79}
80
81#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
83pub struct CandidateReceipt<H = Hash> {
84 pub descriptor: CandidateDescriptor<H>,
86 pub commitments_hash: Hash,
88}
89
90impl<H> CandidateReceipt<H> {
91 pub fn descriptor(&self) -> &CandidateDescriptor<H> {
93 &self.descriptor
94 }
95
96 pub fn hash(&self) -> CandidateHash
98 where
99 H: Encode,
100 {
101 CandidateHash(BlakeTwo256::hash_of(self))
102 }
103}
104
105impl<H: Copy> From<CandidateReceiptV2<H>> for CandidateReceipt<H> {
106 fn from(value: CandidateReceiptV2<H>) -> Self {
107 Self { descriptor: value.descriptor.into(), commitments_hash: value.commitments_hash }
108 }
109}
110
111impl<H: Copy + AsRef<[u8]>> From<CandidateReceipt<H>> for CandidateReceiptV2<H> {
112 fn from(value: CandidateReceipt<H>) -> Self {
113 Self { descriptor: value.descriptor.into(), commitments_hash: value.commitments_hash }
114 }
115}
116
117#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
119pub struct CommittedCandidateReceipt<H = Hash> {
120 pub descriptor: CandidateDescriptor<H>,
122 pub commitments: CandidateCommitments,
124}
125
126impl<H> CommittedCandidateReceipt<H> {
127 pub fn descriptor(&self) -> &CandidateDescriptor<H> {
129 &self.descriptor
130 }
131}
132
133impl<H: Clone> CommittedCandidateReceipt<H> {
134 pub fn to_plain(&self) -> CandidateReceipt<H> {
136 CandidateReceipt {
137 descriptor: self.descriptor.clone(),
138 commitments_hash: self.commitments.hash(),
139 }
140 }
141
142 pub fn hash(&self) -> CandidateHash
147 where
148 H: Encode,
149 {
150 self.to_plain().hash()
151 }
152
153 pub fn corresponds_to(&self, receipt: &CandidateReceipt<H>) -> bool
155 where
156 H: PartialEq,
157 {
158 receipt.descriptor == self.descriptor && receipt.commitments_hash == self.commitments.hash()
159 }
160}
161
162impl PartialOrd for CommittedCandidateReceipt {
163 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
164 Some(self.cmp(other))
165 }
166}
167
168impl Ord for CommittedCandidateReceipt {
169 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
170 self.descriptor()
173 .para_id
174 .cmp(&other.descriptor().para_id)
175 .then_with(|| self.commitments.head_data.cmp(&other.commitments.head_data))
176 }
177}
178
179impl<H: Copy> From<CommittedCandidateReceiptV2<H>> for CommittedCandidateReceipt<H> {
180 fn from(value: CommittedCandidateReceiptV2<H>) -> Self {
181 Self { descriptor: value.descriptor.into(), commitments: value.commitments }
182 }
183}
184
185impl<H: Copy> From<CandidateDescriptorV2<H>> for CandidateDescriptor<H> {
186 fn from(value: CandidateDescriptorV2<H>) -> Self {
187 Self {
188 para_id: value.para_id(),
189 relay_parent: value.relay_parent(),
190 collator: value.rebuild_collator_field_for_tests(),
191 persisted_validation_data_hash: value.persisted_validation_data_hash(),
192 pov_hash: value.pov_hash(),
193 erasure_root: value.erasure_root(),
194 signature: value.rebuild_signature_field_for_tests(),
195 para_head: value.para_head(),
196 validation_code_hash: value.validation_code_hash(),
197 }
198 }
199}
200
201fn clone_into_array<A, T>(slice: &[T]) -> A
202where
203 A: Default + AsMut<[T]>,
204 T: Clone,
205{
206 let mut a = A::default();
207 <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
208 a
209}
210
211impl<H: Copy + AsRef<[u8]>> From<CandidateDescriptor<H>> for CandidateDescriptorV2<H> {
212 fn from(value: CandidateDescriptor<H>) -> Self {
213 let collator = value.collator.as_slice();
214
215 CandidateDescriptorV2::new_from_raw(
216 value.para_id,
217 value.relay_parent,
218 InternalVersion(collator[0]),
219 u16::from_ne_bytes(clone_into_array(&collator[1..=2])),
220 SessionIndex::from_ne_bytes(clone_into_array(&collator[3..=6])),
221 clone_into_array(&collator[7..]),
222 value.persisted_validation_data_hash,
223 value.pov_hash,
224 value.erasure_root,
225 value.signature.into_inner().0,
226 value.para_head,
227 value.validation_code_hash,
228 )
229 }
230}
231
232impl<H: Copy + AsRef<[u8]>> From<CommittedCandidateReceipt<H>> for CommittedCandidateReceiptV2<H> {
233 fn from(value: CommittedCandidateReceipt<H>) -> Self {
234 Self { descriptor: value.descriptor.into(), commitments: value.commitments }
235 }
236}
237
238pub fn collator_signature_payload<H: AsRef<[u8]>>(
240 relay_parent: &H,
241 para_id: &Id,
242 persisted_validation_data_hash: &Hash,
243 pov_hash: &Hash,
244 validation_code_hash: &ValidationCodeHash,
245) -> [u8; 132] {
246 let mut payload = [0u8; 132];
248
249 payload[0..32].copy_from_slice(relay_parent.as_ref());
250 u32::from(*para_id).using_encoded(|s| payload[32..32 + s.len()].copy_from_slice(s));
251 payload[36..68].copy_from_slice(persisted_validation_data_hash.as_ref());
252 payload[68..100].copy_from_slice(pov_hash.as_ref());
253 payload[100..132].copy_from_slice(validation_code_hash.as_ref());
254
255 payload
256}
257
258pub(crate) fn check_collator_signature<H: AsRef<[u8]>>(
259 relay_parent: &H,
260 para_id: &Id,
261 persisted_validation_data_hash: &Hash,
262 pov_hash: &Hash,
263 validation_code_hash: &ValidationCodeHash,
264 collator: &CollatorId,
265 signature: &CollatorSignature,
266) -> Result<(), ()> {
267 let payload = collator_signature_payload(
268 relay_parent,
269 para_id,
270 persisted_validation_data_hash,
271 pov_hash,
272 validation_code_hash,
273 );
274
275 if signature.verify(&payload[..], collator) {
276 Ok(())
277 } else {
278 Err(())
279 }
280}
281
282pub fn dummy_candidate_receipt<H: AsRef<[u8]>>(relay_parent: H) -> CandidateReceipt<H> {
284 CandidateReceipt::<H> {
285 commitments_hash: dummy_candidate_commitments(dummy_head_data()).hash(),
286 descriptor: dummy_candidate_descriptor(relay_parent),
287 }
288}
289
290pub fn dummy_candidate_receipt_v2<H: AsRef<[u8]> + Copy>(relay_parent: H) -> CandidateReceiptV2<H> {
292 CandidateReceiptV2::<H> {
293 commitments_hash: dummy_candidate_commitments(dummy_head_data()).hash(),
294 descriptor: dummy_candidate_descriptor_v2(relay_parent),
295 }
296}
297
298pub fn dummy_committed_candidate_receipt<H: AsRef<[u8]>>(
300 relay_parent: H,
301) -> CommittedCandidateReceipt<H> {
302 CommittedCandidateReceipt::<H> {
303 descriptor: dummy_candidate_descriptor::<H>(relay_parent),
304 commitments: dummy_candidate_commitments(dummy_head_data()),
305 }
306}
307
308pub fn dummy_committed_candidate_receipt_v2<H: AsRef<[u8]> + Copy>(
310 relay_parent: H,
311) -> CommittedCandidateReceiptV2<H> {
312 CommittedCandidateReceiptV2 {
313 descriptor: dummy_candidate_descriptor_v2::<H>(relay_parent),
314 commitments: dummy_candidate_commitments(dummy_head_data()),
315 }
316}
317
318pub fn dummy_candidate_receipt_bad_sig(
321 relay_parent: Hash,
322 commitments: impl Into<Option<Hash>>,
323) -> CandidateReceipt<Hash> {
324 let commitments_hash = if let Some(commitments) = commitments.into() {
325 commitments
326 } else {
327 dummy_candidate_commitments(dummy_head_data()).hash()
328 };
329 CandidateReceipt::<Hash> {
330 commitments_hash,
331 descriptor: dummy_candidate_descriptor_bad_sig(relay_parent),
332 }
333}
334
335pub fn dummy_candidate_receipt_v2_bad_sig(
338 relay_parent: Hash,
339 commitments: impl Into<Option<Hash>>,
340) -> CandidateReceiptV2<Hash> {
341 let commitments_hash = if let Some(commitments) = commitments.into() {
342 commitments
343 } else {
344 dummy_candidate_commitments(dummy_head_data()).hash()
345 };
346 CandidateReceiptV2::<Hash> {
347 commitments_hash,
348 descriptor: dummy_candidate_descriptor_bad_sig(relay_parent).into(),
349 }
350}
351
352pub fn dummy_candidate_commitments(head_data: impl Into<Option<HeadData>>) -> CandidateCommitments {
354 CandidateCommitments {
355 head_data: head_data.into().unwrap_or(dummy_head_data()),
356 upward_messages: vec![].try_into().expect("empty vec fits within bounds"),
357 new_validation_code: None,
358 horizontal_messages: vec![].try_into().expect("empty vec fits within bounds"),
359 processed_downward_messages: 0,
360 hrmp_watermark: 0_u32,
361 }
362}
363
364pub fn dummy_hash() -> Hash {
366 Hash::zero()
367}
368
369pub fn dummy_digest() -> Digest {
371 Digest::default()
372}
373
374pub fn dummy_candidate_descriptor_bad_sig(relay_parent: Hash) -> CandidateDescriptor<Hash> {
376 let zeros = Hash::zero();
377 CandidateDescriptor::<Hash> {
378 para_id: 0.into(),
379 relay_parent,
380 collator: dummy_collator(),
381 persisted_validation_data_hash: zeros,
382 pov_hash: zeros,
383 erasure_root: zeros,
384 signature: dummy_collator_signature(),
385 para_head: zeros,
386 validation_code_hash: dummy_validation_code().hash(),
387 }
388}
389
390pub fn dummy_candidate_descriptor<H: AsRef<[u8]>>(relay_parent: H) -> CandidateDescriptor<H> {
392 let collator = sp_keyring::Sr25519Keyring::Ferdie;
393 let invalid = Hash::zero();
394 let descriptor = make_valid_candidate_descriptor(
395 1.into(),
396 relay_parent,
397 invalid,
398 invalid,
399 invalid,
400 invalid,
401 invalid,
402 collator,
403 );
404 descriptor
405}
406
407pub fn dummy_candidate_descriptor_v2<H: AsRef<[u8]> + Copy>(
409 relay_parent: H,
410) -> CandidateDescriptorV2<H> {
411 let invalid = Hash::zero();
412 let descriptor = make_valid_candidate_descriptor_v2(
413 1.into(),
414 relay_parent,
415 CoreIndex(1),
416 1,
417 invalid,
418 invalid,
419 invalid,
420 invalid,
421 invalid,
422 );
423 descriptor
424}
425
426pub fn dummy_validation_code() -> ValidationCode {
428 ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])
429}
430
431pub fn dummy_head_data() -> HeadData {
433 HeadData(vec![])
434}
435
436pub fn dummy_validator() -> ValidatorId {
438 ValidatorId::from(sr25519::Public::default())
439}
440
441pub fn dummy_collator() -> CollatorId {
443 CollatorId::from(sr25519::Public::default())
444}
445
446pub fn dummy_collator_signature() -> CollatorSignature {
449 CollatorSignature::from_slice(&mut (0..64).into_iter().collect::<Vec<_>>().as_slice())
450 .expect("64 bytes; qed")
451}
452
453pub fn zero_collator_signature() -> CollatorSignature {
455 CollatorSignature::from(sr25519::Signature::default())
456}
457
458pub fn dummy_pvd(parent_head: HeadData, relay_parent_number: u32) -> PersistedValidationData {
460 PersistedValidationData {
461 parent_head,
462 relay_parent_number,
463 max_pov_size: MAX_POV_SIZE,
464 relay_parent_storage_root: dummy_hash(),
465 }
466}
467
468pub fn dummy_signature() -> polkadot_primitives::ValidatorSignature {
470 sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64])
471}
472
473pub fn make_candidate(
475 relay_parent_hash: Hash,
476 relay_parent_number: u32,
477 para_id: ParaId,
478 parent_head: HeadData,
479 head_data: HeadData,
480 validation_code_hash: ValidationCodeHash,
481) -> (CommittedCandidateReceiptV2, PersistedValidationData) {
482 let pvd = dummy_pvd(parent_head, relay_parent_number);
483 let commitments = CandidateCommitments {
484 head_data,
485 horizontal_messages: Default::default(),
486 upward_messages: Default::default(),
487 new_validation_code: None,
488 processed_downward_messages: 0,
489 hrmp_watermark: relay_parent_number,
490 };
491
492 let mut candidate =
493 dummy_candidate_receipt_bad_sig(relay_parent_hash, Some(Default::default()));
494 candidate.commitments_hash = commitments.hash();
495 candidate.descriptor.para_id = para_id;
496 candidate.descriptor.persisted_validation_data_hash = pvd.hash();
497 candidate.descriptor.validation_code_hash = validation_code_hash;
498 let candidate =
499 CommittedCandidateReceiptV2 { descriptor: candidate.descriptor.into(), commitments };
500
501 (candidate, pvd)
502}
503
504pub fn make_candidate_v2(
506 relay_parent_hash: Hash,
507 relay_parent_number: u32,
508 para_id: ParaId,
509 parent_head: HeadData,
510 head_data: HeadData,
511 validation_code_hash: ValidationCodeHash,
512) -> (CommittedCandidateReceiptV2, PersistedValidationData) {
513 let pvd = dummy_pvd(parent_head, relay_parent_number);
514 let commitments = CandidateCommitments {
515 head_data,
516 horizontal_messages: Default::default(),
517 upward_messages: Default::default(),
518 new_validation_code: None,
519 processed_downward_messages: 0,
520 hrmp_watermark: relay_parent_number,
521 };
522
523 let mut descriptor = dummy_candidate_descriptor_v2(relay_parent_hash);
524 descriptor.set_para_id(para_id);
525 descriptor.set_persisted_validation_data_hash(pvd.hash());
526 descriptor.set_validation_code_hash(validation_code_hash);
527 let candidate = CommittedCandidateReceiptV2 { descriptor, commitments };
528
529 (candidate, pvd)
530}
531
532pub fn make_valid_candidate_descriptor<H: AsRef<[u8]>>(
535 para_id: ParaId,
536 relay_parent: H,
537 persisted_validation_data_hash: Hash,
538 pov_hash: Hash,
539 validation_code_hash: impl Into<ValidationCodeHash>,
540 para_head: Hash,
541 erasure_root: Hash,
542 collator: Sr25519Keyring,
543) -> CandidateDescriptor<H> {
544 let validation_code_hash = validation_code_hash.into();
545 let payload = collator_signature_payload::<H>(
546 &relay_parent,
547 ¶_id,
548 &persisted_validation_data_hash,
549 &pov_hash,
550 &validation_code_hash,
551 );
552
553 let signature = collator.sign(&payload).into();
554 let descriptor = CandidateDescriptor {
555 para_id,
556 relay_parent,
557 collator: collator.public().into(),
558 persisted_validation_data_hash,
559 pov_hash,
560 erasure_root,
561 signature,
562 para_head,
563 validation_code_hash,
564 };
565
566 assert!(descriptor.check_collator_signature().is_ok());
567 descriptor
568}
569
570pub fn make_valid_candidate_descriptor_v2<H: AsRef<[u8]> + Copy>(
572 para_id: ParaId,
573 relay_parent: H,
574 core_index: CoreIndex,
575 session_index: SessionIndex,
576 persisted_validation_data_hash: Hash,
577 pov_hash: Hash,
578 validation_code_hash: impl Into<ValidationCodeHash>,
579 para_head: Hash,
580 erasure_root: Hash,
581) -> CandidateDescriptorV2<H> {
582 let validation_code_hash = validation_code_hash.into();
583
584 let descriptor = CandidateDescriptorV2::new(
585 para_id,
586 relay_parent,
587 core_index,
588 session_index,
589 persisted_validation_data_hash,
590 pov_hash,
591 erasure_root,
592 para_head,
593 validation_code_hash,
594 );
595
596 descriptor
597}
598pub fn resign_candidate_descriptor_with_collator<H: AsRef<[u8]>>(
600 descriptor: &mut CandidateDescriptor<H>,
601 collator: Sr25519Keyring,
602) {
603 descriptor.collator = collator.public().into();
604 let payload = collator_signature_payload::<H>(
605 &descriptor.relay_parent,
606 &descriptor.para_id,
607 &descriptor.persisted_validation_data_hash,
608 &descriptor.pov_hash,
609 &descriptor.validation_code_hash,
610 );
611 let signature = collator.sign(&payload).into();
612 descriptor.signature = signature;
613}
614
615pub fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
617 val_ids.iter().map(|v| v.public().into()).collect()
618}
619
620pub struct TestCandidateBuilder {
622 pub para_id: ParaId,
623 pub pov_hash: Hash,
624 pub relay_parent: Hash,
625 pub commitments_hash: Hash,
626 pub core_index: CoreIndex,
627}
628
629impl std::default::Default for TestCandidateBuilder {
630 fn default() -> Self {
631 let zeros = Hash::zero();
632 Self {
633 para_id: 0.into(),
634 pov_hash: zeros,
635 relay_parent: zeros,
636 commitments_hash: zeros,
637 core_index: CoreIndex(0),
638 }
639 }
640}
641
642impl TestCandidateBuilder {
643 pub fn build(self) -> CandidateReceiptV2 {
645 let mut descriptor = dummy_candidate_descriptor_v2(self.relay_parent);
646 descriptor.set_para_id(self.para_id);
647 descriptor.set_pov_hash(self.pov_hash);
648 descriptor.set_core_index(self.core_index);
649 CandidateReceiptV2 { descriptor, commitments_hash: self.commitments_hash }
650 }
651}
652
653pub struct AlwaysZeroRng;
656
657impl Default for AlwaysZeroRng {
658 fn default() -> Self {
659 Self {}
660 }
661}
662impl rand::RngCore for AlwaysZeroRng {
663 fn next_u32(&mut self) -> u32 {
664 0_u32
665 }
666
667 fn next_u64(&mut self) -> u64 {
668 0_u64
669 }
670
671 fn fill_bytes(&mut self, dest: &mut [u8]) {
672 for element in dest.iter_mut() {
673 *element = 0_u8;
674 }
675 }
676
677 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
678 self.fill_bytes(dest);
679 Ok(())
680 }
681}
682
683#[cfg(test)]
684mod candidate_receipt_tests {
685
686 use super::*;
687 use bitvec::prelude::*;
688 use polkadot_primitives::{
689 transpose_claim_queue, v9::CandidateUMPSignals, BackedCandidate,
690 CandidateDescriptorVersion, ClaimQueueOffset, CommittedCandidateReceiptError, CoreSelector,
691 InternalVersion, UMPSignal, UMP_SEPARATOR,
692 };
693 use std::collections::BTreeMap;
694
695 #[test]
696 fn collator_signature_payload_is_valid() {
697 let h = Hash::default();
699 assert_eq!(h.as_ref().len(), 32);
700
701 let _payload = collator_signature_payload(
702 &Hash::repeat_byte(1),
703 &5u32.into(),
704 &Hash::repeat_byte(2),
705 &Hash::repeat_byte(3),
706 &Hash::repeat_byte(4).into(),
707 );
708 }
709
710 #[test]
711 fn is_binary_compatibile() {
712 let old_ccr = dummy_committed_candidate_receipt(Hash::default());
713 let new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
714
715 assert_eq!(old_ccr.encoded_size(), new_ccr.encoded_size());
716
717 let encoded_old = old_ccr.encode();
718
719 let new_ccr: CommittedCandidateReceiptV2 =
721 Decode::decode(&mut encoded_old.as_slice()).unwrap();
722
723 assert_eq!(old_ccr.hash(), new_ccr.hash());
725 }
726
727 #[test]
728 fn test_from_v1_descriptor() {
729 let mut old_ccr = dummy_committed_candidate_receipt(Hash::default()).to_plain();
730 old_ccr.descriptor.collator = dummy_collator();
731 old_ccr.descriptor.signature = dummy_collator_signature();
732
733 let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default()).to_plain();
734
735 new_ccr.descriptor = old_ccr.descriptor.clone().into();
737
738 assert_eq!(old_ccr.hash(), new_ccr.hash());
740
741 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
742 assert_eq!(old_ccr.descriptor.collator, new_ccr.descriptor.collator().unwrap());
743 assert_eq!(old_ccr.descriptor.signature, new_ccr.descriptor.signature().unwrap());
744 }
745
746 #[test]
747 fn invalid_version_descriptor() {
748 let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
749 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V2);
750 new_ccr.descriptor.set_version(InternalVersion(100));
752
753 let new_ccr: CommittedCandidateReceiptV2 =
755 Decode::decode(&mut new_ccr.encode().as_slice()).unwrap();
756
757 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::Unknown);
758 assert_eq!(
759 new_ccr.parse_ump_signals(&std::collections::BTreeMap::new()),
760 Err(CommittedCandidateReceiptError::UnknownVersion(InternalVersion(100)))
761 );
762 }
763
764 #[test]
765 fn test_version2_receipts_decoded_as_v1() {
766 let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
767 new_ccr.descriptor.set_core_index(CoreIndex(123));
768 new_ccr.descriptor.set_para_id(ParaId::new(1000));
769
770 new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
772 new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
773
774 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
776
777 new_ccr
779 .commitments
780 .upward_messages
781 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
782
783 let encoded_ccr = new_ccr.encode();
784 let decoded_ccr: CommittedCandidateReceipt =
785 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
786
787 assert_eq!(decoded_ccr.descriptor.relay_parent, new_ccr.descriptor.relay_parent());
788 assert_eq!(decoded_ccr.descriptor.para_id, new_ccr.descriptor.para_id());
789
790 assert_eq!(new_ccr.hash(), decoded_ccr.hash());
791
792 let encoded_ccr = new_ccr.encode();
794 let v2_ccr: CommittedCandidateReceiptV2 =
795 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
796
797 assert_eq!(v2_ccr.descriptor.core_index(), Some(CoreIndex(123)));
798
799 let mut cq = BTreeMap::new();
800 cq.insert(
801 CoreIndex(123),
802 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
803 );
804
805 assert!(new_ccr.parse_ump_signals(&transpose_claim_queue(cq)).is_ok());
806
807 assert_eq!(new_ccr.hash(), v2_ccr.hash());
808 }
809
810 #[test]
812 fn test_v1_descriptors_with_ump_signal() {
813 let mut ccr = dummy_committed_candidate_receipt(Hash::default());
814 ccr.descriptor.para_id = ParaId::new(1024);
815 ccr.descriptor.signature = dummy_collator_signature();
817 ccr.descriptor.collator = dummy_collator();
818
819 ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
820 ccr.commitments
821 .upward_messages
822 .force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode());
823
824 ccr.commitments
825 .upward_messages
826 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
827
828 let encoded_ccr: Vec<u8> = ccr.encode();
829
830 let v1_ccr: CommittedCandidateReceiptV2 =
831 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
832
833 assert_eq!(v1_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
834 assert!(!v1_ccr.commitments.ump_signals().unwrap().is_empty());
835
836 let mut cq = BTreeMap::new();
837 cq.insert(CoreIndex(0), vec![v1_ccr.descriptor.para_id()].into());
838 cq.insert(CoreIndex(1), vec![v1_ccr.descriptor.para_id()].into());
839
840 assert_eq!(v1_ccr.descriptor.core_index(), None);
841
842 assert_eq!(
843 v1_ccr.parse_ump_signals(&transpose_claim_queue(cq)),
844 Err(CommittedCandidateReceiptError::UMPSignalWithV1Decriptor)
845 );
846 }
847
848 #[test]
849 fn test_core_select_is_optional() {
850 let mut old_ccr = dummy_committed_candidate_receipt(Hash::default());
852 old_ccr.descriptor.para_id = ParaId::new(1000);
853 let encoded_ccr: Vec<u8> = old_ccr.encode();
854
855 let new_ccr: CommittedCandidateReceiptV2 =
856 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
857
858 let mut cq = BTreeMap::new();
859 cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
860
861 assert!(new_ccr.parse_ump_signals(&transpose_claim_queue(cq)).is_ok());
864
865 let mut cq = BTreeMap::new();
866 cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
867 cq.insert(CoreIndex(1), vec![new_ccr.descriptor.para_id()].into());
868
869 assert!(new_ccr.parse_ump_signals(&transpose_claim_queue(cq)).is_ok());
872
873 old_ccr.descriptor.signature = dummy_collator_signature();
875 old_ccr.descriptor.collator = dummy_collator();
876
877 let old_ccr_hash = old_ccr.hash();
878
879 let encoded_ccr: Vec<u8> = old_ccr.encode();
880
881 let new_ccr: CommittedCandidateReceiptV2 =
882 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
883
884 assert_eq!(new_ccr.descriptor.signature(), Some(old_ccr.descriptor.signature));
885 assert_eq!(new_ccr.descriptor.collator(), Some(old_ccr.descriptor.collator));
886
887 assert_eq!(new_ccr.descriptor.core_index(), None);
888 assert_eq!(new_ccr.descriptor.para_id(), ParaId::new(1000));
889
890 assert_eq!(old_ccr_hash, new_ccr.hash());
891 }
892
893 #[test]
894 fn test_ump_commitments() {
900 let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
901 new_ccr.descriptor.set_core_index(CoreIndex(123));
902 new_ccr.descriptor.set_para_id(ParaId::new(1000));
903
904 let mut cq = BTreeMap::new();
905 cq.insert(
906 CoreIndex(123),
907 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
908 );
909 let cq = transpose_claim_queue(cq);
910
911 new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
915 new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
916
917 assert_eq!(new_ccr.parse_ump_signals(&cq), Ok(CandidateUMPSignals::dummy(None, None)));
918
919 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
921
922 assert_eq!(new_ccr.parse_ump_signals(&cq), Ok(CandidateUMPSignals::dummy(None, None)));
923
924 {
926 let mut new_ccr = new_ccr.clone();
927 new_ccr
928 .commitments
929 .upward_messages
930 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
931
932 assert_eq!(
933 new_ccr.parse_ump_signals(&cq),
934 Ok(CandidateUMPSignals::dummy(Some((CoreSelector(0), ClaimQueueOffset(1))), None))
935 );
936 }
937
938 {
939 let mut new_ccr = new_ccr.clone();
940
941 new_ccr
943 .commitments
944 .upward_messages
945 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
946
947 assert_eq!(
948 new_ccr.parse_ump_signals(&cq),
949 Ok(CandidateUMPSignals::dummy(None, Some(vec![1, 2, 3].try_into().unwrap())))
950 );
951
952 new_ccr
955 .commitments
956 .upward_messages
957 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
958
959 assert_eq!(
960 new_ccr.parse_ump_signals(&cq),
961 Ok(CandidateUMPSignals::dummy(
962 Some((CoreSelector(0), ClaimQueueOffset(1))),
963 Some(vec![1, 2, 3].try_into().unwrap())
964 ))
965 );
966 }
967
968 new_ccr
970 .commitments
971 .upward_messages
972 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
973 new_ccr
974 .commitments
975 .upward_messages
976 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
977
978 assert_eq!(
979 new_ccr.parse_ump_signals(&cq),
980 Ok(CandidateUMPSignals::dummy(
981 Some((CoreSelector(0), ClaimQueueOffset(1))),
982 Some(vec![1, 2, 3].try_into().unwrap())
983 ))
984 );
985 }
986
987 #[test]
988 fn test_invalid_ump_commitments() {
989 let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
990 new_ccr.descriptor.set_core_index(CoreIndex(0));
991 new_ccr.descriptor.set_para_id(ParaId::new(1000));
992
993 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
994
995 let mut cq = BTreeMap::new();
996 cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
997 let cq = transpose_claim_queue(cq);
998
999 new_ccr
1001 .commitments
1002 .upward_messages
1003 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1004
1005 new_ccr.commitments.upward_messages.force_push(vec![0, 13, 200].encode());
1007
1008 assert_eq!(
1010 new_ccr.parse_ump_signals(&cq),
1011 Err(CommittedCandidateReceiptError::UmpSignalDecode)
1012 );
1013 assert_eq!(
1014 new_ccr.commitments.ump_signals(),
1015 Err(CommittedCandidateReceiptError::UmpSignalDecode)
1016 );
1017
1018 {
1020 new_ccr.commitments.upward_messages.clear();
1023 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1024 new_ccr
1025 .commitments
1026 .upward_messages
1027 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1028
1029 let mut cq = BTreeMap::new();
1030 cq.insert(
1031 CoreIndex(0),
1032 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1033 );
1034 cq.insert(
1035 CoreIndex(100),
1036 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1037 );
1038 let cq = transpose_claim_queue(cq);
1039
1040 assert_eq!(
1041 new_ccr.parse_ump_signals(&cq),
1042 Ok(CandidateUMPSignals::dummy(None, Some(vec![1, 2, 3].try_into().unwrap())))
1043 );
1044
1045 new_ccr.descriptor.set_core_index(CoreIndex(1));
1046 assert_eq!(
1047 new_ccr.parse_ump_signals(&cq),
1048 Err(CommittedCandidateReceiptError::InvalidCoreIndex)
1049 );
1050 new_ccr.descriptor.set_core_index(CoreIndex(0));
1051
1052 new_ccr
1053 .commitments
1054 .upward_messages
1055 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1056
1057 assert_eq!(
1059 new_ccr.parse_ump_signals(&transpose_claim_queue(Default::default())),
1060 Err(CommittedCandidateReceiptError::NoAssignment)
1061 );
1062
1063 new_ccr.descriptor.set_core_index(CoreIndex(1));
1065 assert_eq!(
1066 new_ccr.parse_ump_signals(&cq),
1067 Err(CommittedCandidateReceiptError::CoreIndexMismatch {
1068 descriptor: CoreIndex(1),
1069 commitments: CoreIndex(0),
1070 })
1071 );
1072 }
1073
1074 new_ccr.descriptor.set_core_index(CoreIndex(0));
1075
1076 new_ccr.commitments.upward_messages.clear();
1078 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1079 new_ccr
1080 .commitments
1081 .upward_messages
1082 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1083 new_ccr
1084 .commitments
1085 .upward_messages
1086 .force_push(UMPSignal::ApprovedPeer(vec![4, 5].try_into().unwrap()).encode());
1087
1088 assert_eq!(
1089 new_ccr.parse_ump_signals(&cq),
1090 Err(CommittedCandidateReceiptError::DuplicateUMPSignal)
1091 );
1092
1093 new_ccr.commitments.upward_messages.clear();
1095 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1096 new_ccr
1097 .commitments
1098 .upward_messages
1099 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1100 new_ccr
1101 .commitments
1102 .upward_messages
1103 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(0)).encode());
1104 new_ccr
1105 .commitments
1106 .upward_messages
1107 .force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1108
1109 assert_eq!(
1110 new_ccr.parse_ump_signals(&cq),
1111 Err(CommittedCandidateReceiptError::TooManyUMPSignals)
1112 );
1113 }
1114
1115 #[test]
1116 fn test_backed_candidate_injected_core_index() {
1117 let initial_validator_indices = bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1];
1118 let mut candidate = BackedCandidate::new(
1119 dummy_committed_candidate_receipt_v2(Hash::default()),
1120 vec![],
1121 initial_validator_indices.clone(),
1122 CoreIndex(10),
1123 );
1124
1125 candidate
1127 .set_validator_indices_and_core_index(initial_validator_indices.clone().into(), None);
1128 let (validator_indices, core_index) = candidate.validator_indices_and_core_index();
1129 assert_eq!(validator_indices, initial_validator_indices.as_bitslice());
1130 assert!(core_index.is_none());
1131
1132 candidate.set_validator_indices_and_core_index(
1135 bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1, 0, 1, 0, 1, 0].into(),
1136 None,
1137 );
1138
1139 let (validator_indices, core_index) = candidate.validator_indices_and_core_index();
1140 assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0].as_bitslice());
1141 assert!(core_index.is_some());
1142
1143 let mut candidate = BackedCandidate::new(
1145 dummy_committed_candidate_receipt_v2(Hash::default()),
1146 vec![],
1147 bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1],
1148 CoreIndex(10),
1149 );
1150 let (validator_indices, core_index) = candidate.validator_indices_and_core_index();
1151 assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1]);
1152 assert_eq!(core_index, Some(CoreIndex(10)));
1153
1154 let encoded_validator_indices = candidate.raw_validator_indices();
1155 candidate.set_validator_indices_and_core_index(validator_indices.into(), core_index);
1156 assert_eq!(candidate.raw_validator_indices(), encoded_validator_indices);
1157 }
1158}