1use crate::{ValidatorIndex, ValidityAttestation};
19
20use super::{
22 async_backing::{InboundHrmpLimitations, OutboundHrmpChannelLimitations},
23 BlakeTwo256, BlockNumber, CandidateCommitments, CandidateDescriptor, CandidateHash, CollatorId,
24 CollatorSignature, CoreIndex, GroupIndex, Hash, HashT, HeadData, Header, Id, Id as ParaId,
25 MultiDisputeStatementSet, ScheduledCore, UncheckedSignedAvailabilityBitfields,
26 UpgradeRestriction, ValidationCodeHash,
27};
28use alloc::{
29 collections::{BTreeMap, BTreeSet, VecDeque},
30 vec,
31 vec::Vec,
32};
33use bitvec::prelude::*;
34use codec::{Decode, DecodeWithMemTracking, Encode};
35use scale_info::TypeInfo;
36use sp_application_crypto::ByteArray;
37use sp_core::RuntimeDebug;
38use sp_runtime::traits::Header as HeaderT;
39use sp_staking::SessionIndex;
40pub mod async_backing;
42
43pub const DEFAULT_CLAIM_QUEUE_OFFSET: u8 = 0;
46
47#[derive(
49 PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, Clone, TypeInfo, RuntimeDebug, Copy,
50)]
51#[cfg_attr(feature = "std", derive(Hash))]
52pub struct InternalVersion(pub u8);
53
54#[derive(PartialEq, Eq, Clone, TypeInfo, RuntimeDebug)]
56#[cfg_attr(feature = "std", derive(Hash))]
57pub enum CandidateDescriptorVersion {
58 V1,
60 V2,
62 Unknown,
64}
65
66#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, RuntimeDebug)]
68#[cfg_attr(feature = "std", derive(Hash))]
69pub struct CandidateDescriptorV2<H = Hash> {
70 para_id: ParaId,
72 relay_parent: H,
74 version: InternalVersion,
79 core_index: u16,
81 session_index: SessionIndex,
83 reserved1: [u8; 25],
85 persisted_validation_data_hash: Hash,
89 pov_hash: Hash,
91 erasure_root: Hash,
93 reserved2: [u8; 64],
95 para_head: Hash,
97 validation_code_hash: ValidationCodeHash,
99}
100
101impl<H: Copy> From<CandidateDescriptorV2<H>> for CandidateDescriptor<H> {
102 fn from(value: CandidateDescriptorV2<H>) -> Self {
103 Self {
104 para_id: value.para_id,
105 relay_parent: value.relay_parent,
106 collator: value.rebuild_collator_field(),
107 persisted_validation_data_hash: value.persisted_validation_data_hash,
108 pov_hash: value.pov_hash,
109 erasure_root: value.erasure_root,
110 signature: value.rebuild_signature_field(),
111 para_head: value.para_head,
112 validation_code_hash: value.validation_code_hash,
113 }
114 }
115}
116
117fn clone_into_array<A, T>(slice: &[T]) -> A
118where
119 A: Default + AsMut<[T]>,
120 T: Clone,
121{
122 let mut a = A::default();
123 <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
124 a
125}
126
127impl<H: Copy> From<CandidateDescriptor<H>> for CandidateDescriptorV2<H> {
128 fn from(value: CandidateDescriptor<H>) -> Self {
129 let collator = value.collator.as_slice();
130
131 Self {
132 para_id: value.para_id,
133 relay_parent: value.relay_parent,
134 version: InternalVersion(collator[0]),
136 core_index: u16::from_ne_bytes(clone_into_array(&collator[1..=2])),
138 session_index: SessionIndex::from_ne_bytes(clone_into_array(&collator[3..=6])),
140 reserved1: clone_into_array(&collator[7..]),
142 persisted_validation_data_hash: value.persisted_validation_data_hash,
143 pov_hash: value.pov_hash,
144 erasure_root: value.erasure_root,
145 reserved2: value.signature.into_inner().0,
146 para_head: value.para_head,
147 validation_code_hash: value.validation_code_hash,
148 }
149 }
150}
151
152impl<H: Copy + AsRef<[u8]>> CandidateDescriptorV2<H> {
153 pub fn new(
155 para_id: Id,
156 relay_parent: H,
157 core_index: CoreIndex,
158 session_index: SessionIndex,
159 persisted_validation_data_hash: Hash,
160 pov_hash: Hash,
161 erasure_root: Hash,
162 para_head: Hash,
163 validation_code_hash: ValidationCodeHash,
164 ) -> Self {
165 Self {
166 para_id,
167 relay_parent,
168 version: InternalVersion(0),
169 core_index: core_index.0 as u16,
170 session_index,
171 reserved1: [0; 25],
172 persisted_validation_data_hash,
173 pov_hash,
174 erasure_root,
175 reserved2: [0; 64],
176 para_head,
177 validation_code_hash,
178 }
179 }
180
181 pub fn check_collator_signature(&self) -> Result<(), ()> {
183 let Some(collator) = self.collator() else { return Ok(()) };
185
186 let Some(signature) = self.signature() else { return Ok(()) };
187
188 super::v8::check_collator_signature(
189 &self.relay_parent,
190 &self.para_id,
191 &self.persisted_validation_data_hash,
192 &self.pov_hash,
193 &self.validation_code_hash,
194 &collator,
195 &signature,
196 )
197 }
198}
199
200#[cfg(feature = "test")]
202
203pub trait MutateDescriptorV2<H> {
204 fn set_relay_parent(&mut self, relay_parent: H);
206 fn set_para_id(&mut self, para_id: Id);
208 fn set_pov_hash(&mut self, pov_hash: Hash);
210 fn set_version(&mut self, version: InternalVersion);
212 fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash);
214 fn set_validation_code_hash(&mut self, validation_code_hash: ValidationCodeHash);
216 fn set_erasure_root(&mut self, erasure_root: Hash);
218 fn set_para_head(&mut self, para_head: Hash);
220 fn set_core_index(&mut self, core_index: CoreIndex);
222 fn set_session_index(&mut self, session_index: SessionIndex);
224}
225
226#[cfg(feature = "test")]
227impl<H> MutateDescriptorV2<H> for CandidateDescriptorV2<H> {
228 fn set_para_id(&mut self, para_id: Id) {
229 self.para_id = para_id;
230 }
231
232 fn set_relay_parent(&mut self, relay_parent: H) {
233 self.relay_parent = relay_parent;
234 }
235
236 fn set_pov_hash(&mut self, pov_hash: Hash) {
237 self.pov_hash = pov_hash;
238 }
239
240 fn set_version(&mut self, version: InternalVersion) {
241 self.version = version;
242 }
243
244 fn set_core_index(&mut self, core_index: CoreIndex) {
245 self.core_index = core_index.0 as u16;
246 }
247
248 fn set_session_index(&mut self, session_index: SessionIndex) {
249 self.session_index = session_index;
250 }
251
252 fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash) {
253 self.persisted_validation_data_hash = persisted_validation_data_hash;
254 }
255
256 fn set_validation_code_hash(&mut self, validation_code_hash: ValidationCodeHash) {
257 self.validation_code_hash = validation_code_hash;
258 }
259
260 fn set_erasure_root(&mut self, erasure_root: Hash) {
261 self.erasure_root = erasure_root;
262 }
263
264 fn set_para_head(&mut self, para_head: Hash) {
265 self.para_head = para_head;
266 }
267}
268
269#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, RuntimeDebug)]
271#[cfg_attr(feature = "std", derive(Hash))]
272pub struct CandidateReceiptV2<H = Hash> {
273 pub descriptor: CandidateDescriptorV2<H>,
275 pub commitments_hash: Hash,
277}
278
279#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, RuntimeDebug)]
281#[cfg_attr(feature = "std", derive(Hash))]
282pub struct CommittedCandidateReceiptV2<H = Hash> {
283 pub descriptor: CandidateDescriptorV2<H>,
285 pub commitments: CandidateCommitments,
287}
288
289#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
291#[cfg_attr(feature = "std", derive(PartialEq))]
292pub enum CandidateEvent<H = Hash> {
293 #[codec(index = 0)]
296 CandidateBacked(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
297 #[codec(index = 1)]
301 CandidateIncluded(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
302 #[codec(index = 2)]
305 CandidateTimedOut(CandidateReceiptV2<H>, HeadData, CoreIndex),
306}
307
308impl<H: Encode + Copy> From<CandidateEvent<H>> for super::v8::CandidateEvent<H> {
309 fn from(value: CandidateEvent<H>) -> Self {
310 match value {
311 CandidateEvent::CandidateBacked(receipt, head_data, core_index, group_index) =>
312 super::v8::CandidateEvent::CandidateBacked(
313 receipt.into(),
314 head_data,
315 core_index,
316 group_index,
317 ),
318 CandidateEvent::CandidateIncluded(receipt, head_data, core_index, group_index) =>
319 super::v8::CandidateEvent::CandidateIncluded(
320 receipt.into(),
321 head_data,
322 core_index,
323 group_index,
324 ),
325 CandidateEvent::CandidateTimedOut(receipt, head_data, core_index) =>
326 super::v8::CandidateEvent::CandidateTimedOut(receipt.into(), head_data, core_index),
327 }
328 }
329}
330
331impl<H> CandidateReceiptV2<H> {
332 pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
334 &self.descriptor
335 }
336
337 pub fn hash(&self) -> CandidateHash
339 where
340 H: Encode,
341 {
342 CandidateHash(BlakeTwo256::hash_of(self))
343 }
344}
345
346impl<H: Copy> From<super::v8::CandidateReceipt<H>> for CandidateReceiptV2<H> {
347 fn from(value: super::v8::CandidateReceipt<H>) -> Self {
348 CandidateReceiptV2 {
349 descriptor: value.descriptor.into(),
350 commitments_hash: value.commitments_hash,
351 }
352 }
353}
354
355impl<H: Copy> From<super::v8::CommittedCandidateReceipt<H>> for CommittedCandidateReceiptV2<H> {
356 fn from(value: super::v8::CommittedCandidateReceipt<H>) -> Self {
357 CommittedCandidateReceiptV2 {
358 descriptor: value.descriptor.into(),
359 commitments: value.commitments,
360 }
361 }
362}
363
364impl<H: Clone> CommittedCandidateReceiptV2<H> {
365 pub fn to_plain(&self) -> CandidateReceiptV2<H> {
367 CandidateReceiptV2 {
368 descriptor: self.descriptor.clone(),
369 commitments_hash: self.commitments.hash(),
370 }
371 }
372
373 pub fn hash(&self) -> CandidateHash
378 where
379 H: Encode,
380 {
381 self.to_plain().hash()
382 }
383
384 pub fn corresponds_to(&self, receipt: &CandidateReceiptV2<H>) -> bool
386 where
387 H: PartialEq,
388 {
389 receipt.descriptor == self.descriptor && receipt.commitments_hash == self.commitments.hash()
390 }
391}
392
393impl PartialOrd for CommittedCandidateReceiptV2 {
394 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
395 Some(self.cmp(other))
396 }
397}
398
399impl Ord for CommittedCandidateReceiptV2 {
400 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
401 self.descriptor
402 .para_id
403 .cmp(&other.descriptor.para_id)
404 .then_with(|| self.commitments.head_data.cmp(&other.commitments.head_data))
405 }
406}
407
408impl<H: Copy> From<CommittedCandidateReceiptV2<H>> for super::v8::CommittedCandidateReceipt<H> {
409 fn from(value: CommittedCandidateReceiptV2<H>) -> Self {
410 Self { descriptor: value.descriptor.into(), commitments: value.commitments }
411 }
412}
413
414impl<H: Copy> From<CandidateReceiptV2<H>> for super::v8::CandidateReceipt<H> {
415 fn from(value: CandidateReceiptV2<H>) -> Self {
416 Self { descriptor: value.descriptor.into(), commitments_hash: value.commitments_hash }
417 }
418}
419
420#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
423pub struct CoreSelector(pub u8);
424
425#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
427pub struct ClaimQueueOffset(pub u8);
428
429#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
431pub enum UMPSignal {
432 SelectCore(CoreSelector, ClaimQueueOffset),
436}
437pub const UMP_SEPARATOR: Vec<u8> = vec![];
439
440pub fn skip_ump_signals<'a>(
442 upward_messages: impl Iterator<Item = &'a Vec<u8>>,
443) -> impl Iterator<Item = &'a Vec<u8>> {
444 upward_messages.take_while(|message| *message != &UMP_SEPARATOR)
445}
446
447impl CandidateCommitments {
448 pub fn core_selector(
451 &self,
452 ) -> Result<Option<(CoreSelector, ClaimQueueOffset)>, CommittedCandidateReceiptError> {
453 let mut signals_iter =
454 self.upward_messages.iter().skip_while(|message| *message != &UMP_SEPARATOR);
455
456 if signals_iter.next().is_some() {
457 let Some(core_selector_message) = signals_iter.next() else { return Ok(None) };
458 if signals_iter.next().is_some() {
460 return Err(CommittedCandidateReceiptError::TooManyUMPSignals)
461 }
462
463 match UMPSignal::decode(&mut core_selector_message.as_slice())
464 .map_err(|_| CommittedCandidateReceiptError::UmpSignalDecode)?
465 {
466 UMPSignal::SelectCore(core_index_selector, cq_offset) =>
467 Ok(Some((core_index_selector, cq_offset))),
468 }
469 } else {
470 Ok(None)
471 }
472 }
473}
474
475#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
477#[cfg_attr(feature = "std", derive(thiserror::Error))]
478pub enum CommittedCandidateReceiptError {
479 #[cfg_attr(feature = "std", error("The specified core index is invalid"))]
481 InvalidCoreIndex,
482 #[cfg_attr(
484 feature = "std",
485 error("The core index in commitments doesn't match the one in descriptor")
486 )]
487 CoreIndexMismatch,
488 #[cfg_attr(feature = "std", error("The core selector or claim queue offset is invalid"))]
490 InvalidSelectedCore,
491 #[cfg_attr(feature = "std", error("Could not decode UMP signal"))]
492 UmpSignalDecode,
494 #[cfg_attr(
496 feature = "std",
497 error("The parachain is not assigned to any core at specified claim queue offset")
498 )]
499 NoAssignment,
500 #[cfg_attr(feature = "std", error("Core selector not present"))]
503 NoCoreSelected,
504 #[cfg_attr(feature = "std", error("Unknown internal version"))]
506 UnknownVersion(InternalVersion),
507 #[cfg_attr(feature = "std", error("Too many UMP signals"))]
510 TooManyUMPSignals,
511 #[cfg_attr(feature = "std", error("Version 1 receipt does not support core selectors"))]
514 CoreSelectorWithV1Decriptor,
515}
516
517macro_rules! impl_getter {
518 ($field:ident, $type:ident) => {
519 pub fn $field(&self) -> $type {
521 self.$field
522 }
523 };
524}
525
526impl<H: Copy> CandidateDescriptorV2<H> {
527 impl_getter!(erasure_root, Hash);
528 impl_getter!(para_head, Hash);
529 impl_getter!(relay_parent, H);
530 impl_getter!(para_id, ParaId);
531 impl_getter!(persisted_validation_data_hash, Hash);
532 impl_getter!(pov_hash, Hash);
533 impl_getter!(validation_code_hash, ValidationCodeHash);
534
535 pub fn version(&self) -> CandidateDescriptorVersion {
539 if self.reserved2 != [0u8; 64] || self.reserved1 != [0u8; 25] {
540 return CandidateDescriptorVersion::V1
541 }
542
543 match self.version.0 {
544 0 => CandidateDescriptorVersion::V2,
545 _ => CandidateDescriptorVersion::Unknown,
546 }
547 }
548
549 fn rebuild_collator_field(&self) -> CollatorId {
550 let mut collator_id = Vec::with_capacity(32);
551 let core_index: [u8; 2] = self.core_index.to_ne_bytes();
552 let session_index: [u8; 4] = self.session_index.to_ne_bytes();
553
554 collator_id.push(self.version.0);
555 collator_id.extend_from_slice(core_index.as_slice());
556 collator_id.extend_from_slice(session_index.as_slice());
557 collator_id.extend_from_slice(self.reserved1.as_slice());
558
559 CollatorId::from_slice(&collator_id.as_slice())
560 .expect("Slice size is exactly 32 bytes; qed")
561 }
562
563 pub fn collator(&self) -> Option<CollatorId> {
565 if self.version() == CandidateDescriptorVersion::V1 {
566 Some(self.rebuild_collator_field())
567 } else {
568 None
569 }
570 }
571
572 fn rebuild_signature_field(&self) -> CollatorSignature {
573 CollatorSignature::from_slice(self.reserved2.as_slice())
574 .expect("Slice size is exactly 64 bytes; qed")
575 }
576
577 pub fn signature(&self) -> Option<CollatorSignature> {
579 if self.version() == CandidateDescriptorVersion::V1 {
580 return Some(self.rebuild_signature_field())
581 }
582
583 None
584 }
585
586 pub fn core_index(&self) -> Option<CoreIndex> {
588 if self.version() == CandidateDescriptorVersion::V1 {
589 return None
590 }
591
592 Some(CoreIndex(self.core_index as u32))
593 }
594
595 pub fn session_index(&self) -> Option<SessionIndex> {
597 if self.version() == CandidateDescriptorVersion::V1 {
598 return None
599 }
600
601 Some(self.session_index)
602 }
603}
604
605impl<H: Copy> CommittedCandidateReceiptV2<H> {
606 pub fn check_core_index(
610 &self,
611 cores_per_para: &TransposedClaimQueue,
612 ) -> Result<(), CommittedCandidateReceiptError> {
613 let maybe_core_selector = self.commitments.core_selector()?;
614
615 match self.descriptor.version() {
616 CandidateDescriptorVersion::V1 => {
617 if maybe_core_selector.is_some() {
620 return Err(CommittedCandidateReceiptError::CoreSelectorWithV1Decriptor)
621 } else {
622 return Ok(())
624 }
625 },
626 CandidateDescriptorVersion::V2 => {},
627 CandidateDescriptorVersion::Unknown =>
628 return Err(CommittedCandidateReceiptError::UnknownVersion(self.descriptor.version)),
629 }
630
631 let (maybe_core_index_selector, cq_offset) = maybe_core_selector.map_or_else(
632 || (None, ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET)),
633 |(sel, off)| (Some(sel), off),
634 );
635
636 let assigned_cores = cores_per_para
637 .get(&self.descriptor.para_id())
638 .ok_or(CommittedCandidateReceiptError::NoAssignment)?
639 .get(&cq_offset.0)
640 .ok_or(CommittedCandidateReceiptError::NoAssignment)?;
641
642 if assigned_cores.is_empty() {
643 return Err(CommittedCandidateReceiptError::NoAssignment)
644 }
645
646 let descriptor_core_index = CoreIndex(self.descriptor.core_index as u32);
647
648 let core_index_selector = if let Some(core_index_selector) = maybe_core_index_selector {
649 core_index_selector
651 } else if assigned_cores.len() > 1 {
652 if !assigned_cores.contains(&descriptor_core_index) {
654 return Err(CommittedCandidateReceiptError::InvalidCoreIndex)
656 } else {
657 return Ok(())
660 }
661 } else {
662 CoreSelector(0)
664 };
665
666 let core_index = assigned_cores
667 .iter()
668 .nth(core_index_selector.0 as usize % assigned_cores.len())
669 .ok_or(CommittedCandidateReceiptError::InvalidSelectedCore)
670 .copied()?;
671
672 if core_index != descriptor_core_index {
673 return Err(CommittedCandidateReceiptError::CoreIndexMismatch)
674 }
675
676 Ok(())
677 }
678}
679
680#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
682pub struct BackedCandidate<H = Hash> {
683 candidate: CommittedCandidateReceiptV2<H>,
685 validity_votes: Vec<ValidityAttestation>,
687 validator_indices: BitVec<u8, bitvec::order::Lsb0>,
691}
692
693#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, RuntimeDebug, TypeInfo)]
695pub struct InherentData<HDR: HeaderT = Header> {
696 pub bitfields: UncheckedSignedAvailabilityBitfields,
698 pub backed_candidates: Vec<BackedCandidate<HDR::Hash>>,
700 pub disputes: MultiDisputeStatementSet,
702 pub parent_header: HDR,
704}
705
706impl<H> BackedCandidate<H> {
707 pub fn new(
709 candidate: CommittedCandidateReceiptV2<H>,
710 validity_votes: Vec<ValidityAttestation>,
711 validator_indices: BitVec<u8, bitvec::order::Lsb0>,
712 core_index: CoreIndex,
713 ) -> Self {
714 let mut instance = Self { candidate, validity_votes, validator_indices };
715 instance.inject_core_index(core_index);
716 instance
717 }
718
719 pub fn candidate(&self) -> &CommittedCandidateReceiptV2<H> {
721 &self.candidate
722 }
723
724 #[cfg(feature = "test")]
727 pub fn candidate_mut(&mut self) -> &mut CommittedCandidateReceiptV2<H> {
728 &mut self.candidate
729 }
730 pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
732 &self.candidate.descriptor
733 }
734
735 #[cfg(feature = "test")]
737 pub fn descriptor_mut(&mut self) -> &mut CandidateDescriptorV2<H> {
738 &mut self.candidate.descriptor
739 }
740
741 pub fn validity_votes(&self) -> &[ValidityAttestation] {
743 &self.validity_votes
744 }
745
746 pub fn validity_votes_mut(&mut self) -> &mut Vec<ValidityAttestation> {
748 &mut self.validity_votes
749 }
750
751 pub fn hash(&self) -> CandidateHash
753 where
754 H: Clone + Encode,
755 {
756 self.candidate.to_plain().hash()
757 }
758
759 pub fn receipt(&self) -> CandidateReceiptV2<H>
761 where
762 H: Clone,
763 {
764 self.candidate.to_plain()
765 }
766
767 pub fn validator_indices_and_core_index(
769 &self,
770 ) -> (&BitSlice<u8, bitvec::order::Lsb0>, Option<CoreIndex>) {
771 let core_idx_offset = self.validator_indices.len().saturating_sub(8);
773 if core_idx_offset > 0 {
774 let (validator_indices_slice, core_idx_slice) =
775 self.validator_indices.split_at(core_idx_offset);
776 return (validator_indices_slice, Some(CoreIndex(core_idx_slice.load::<u8>() as u32)));
777 }
778
779 (&self.validator_indices, None)
780 }
781
782 fn inject_core_index(&mut self, core_index: CoreIndex) {
784 let core_index_to_inject: BitVec<u8, bitvec::order::Lsb0> =
785 BitVec::from_vec(vec![core_index.0 as u8]);
786 self.validator_indices.extend(core_index_to_inject);
787 }
788
789 pub fn set_validator_indices_and_core_index(
791 &mut self,
792 new_indices: BitVec<u8, bitvec::order::Lsb0>,
793 maybe_core_index: Option<CoreIndex>,
794 ) {
795 self.validator_indices = new_indices;
796
797 if let Some(core_index) = maybe_core_index {
798 self.inject_core_index(core_index);
799 }
800 }
801}
802
803#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
805#[cfg_attr(feature = "std", derive(PartialEq))]
806pub struct ScrapedOnChainVotes<H: Encode + Decode = Hash> {
807 pub session: SessionIndex,
809 pub backing_validators_per_candidate:
812 Vec<(CandidateReceiptV2<H>, Vec<(ValidatorIndex, ValidityAttestation)>)>,
813 pub disputes: MultiDisputeStatementSet,
817}
818
819impl<H: Encode + Decode + Copy> From<ScrapedOnChainVotes<H>> for super::v8::ScrapedOnChainVotes<H> {
820 fn from(value: ScrapedOnChainVotes<H>) -> Self {
821 Self {
822 session: value.session,
823 backing_validators_per_candidate: value
824 .backing_validators_per_candidate
825 .into_iter()
826 .map(|(receipt, validators)| (receipt.into(), validators))
827 .collect::<Vec<_>>(),
828 disputes: value.disputes,
829 }
830 }
831}
832
833#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
835#[cfg_attr(feature = "std", derive(PartialEq))]
836pub struct OccupiedCore<H = Hash, N = BlockNumber> {
837 pub next_up_on_available: Option<ScheduledCore>,
841 pub occupied_since: N,
843 pub time_out_at: N,
845 pub next_up_on_time_out: Option<ScheduledCore>,
849 pub availability: BitVec<u8, bitvec::order::Lsb0>,
853 pub group_responsible: GroupIndex,
855 pub candidate_hash: CandidateHash,
857 pub candidate_descriptor: CandidateDescriptorV2<H>,
859}
860
861impl<H, N> OccupiedCore<H, N> {
862 pub fn para_id(&self) -> Id {
864 self.candidate_descriptor.para_id
865 }
866}
867
868#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
870#[cfg_attr(feature = "std", derive(PartialEq))]
871pub enum CoreState<H = Hash, N = BlockNumber> {
872 #[codec(index = 0)]
874 Occupied(OccupiedCore<H, N>),
875 #[codec(index = 1)]
881 Scheduled(ScheduledCore),
882 #[codec(index = 2)]
886 Free,
887}
888
889impl<N> CoreState<N> {
890 #[deprecated(
895 note = "`para_id` will be removed. Use `ClaimQueue` to query the scheduled `para_id` instead."
896 )]
897 pub fn para_id(&self) -> Option<Id> {
898 match self {
899 Self::Occupied(ref core) => core.next_up_on_available.as_ref().map(|n| n.para_id),
900 Self::Scheduled(core) => Some(core.para_id),
901 Self::Free => None,
902 }
903 }
904
905 pub fn is_occupied(&self) -> bool {
907 matches!(self, Self::Occupied(_))
908 }
909}
910
911impl<H: Copy> From<OccupiedCore<H>> for super::v8::OccupiedCore<H> {
912 fn from(value: OccupiedCore<H>) -> Self {
913 Self {
914 next_up_on_available: value.next_up_on_available,
915 occupied_since: value.occupied_since,
916 time_out_at: value.time_out_at,
917 next_up_on_time_out: value.next_up_on_time_out,
918 availability: value.availability,
919 group_responsible: value.group_responsible,
920 candidate_hash: value.candidate_hash,
921 candidate_descriptor: value.candidate_descriptor.into(),
922 }
923 }
924}
925
926impl<H: Copy> From<CoreState<H>> for super::v8::CoreState<H> {
927 fn from(value: CoreState<H>) -> Self {
928 match value {
929 CoreState::Free => super::v8::CoreState::Free,
930 CoreState::Scheduled(core) => super::v8::CoreState::Scheduled(core),
931 CoreState::Occupied(occupied_core) =>
932 super::v8::CoreState::Occupied(occupied_core.into()),
933 }
934 }
935}
936
937pub type TransposedClaimQueue = BTreeMap<ParaId, BTreeMap<u8, BTreeSet<CoreIndex>>>;
939
940pub fn transpose_claim_queue(
943 claim_queue: BTreeMap<CoreIndex, VecDeque<Id>>,
944) -> TransposedClaimQueue {
945 let mut per_para_claim_queue = BTreeMap::new();
946
947 for (core, paras) in claim_queue {
948 for (depth, para) in paras.into_iter().enumerate() {
950 let depths: &mut BTreeMap<u8, BTreeSet<CoreIndex>> =
951 per_para_claim_queue.entry(para).or_insert_with(|| Default::default());
952
953 depths.entry(depth as u8).or_default().insert(core);
954 }
955 }
956
957 per_para_claim_queue
958}
959
960#[cfg(test)]
961mod tests {
962 use super::*;
963 use crate::{
964 v8::{
965 tests::dummy_committed_candidate_receipt as dummy_old_committed_candidate_receipt,
966 CommittedCandidateReceipt, Hash, HeadData, ValidationCode,
967 },
968 vstaging::{CandidateDescriptorV2, CommittedCandidateReceiptV2},
969 };
970
971 fn dummy_collator_signature() -> CollatorSignature {
972 CollatorSignature::from_slice(&mut (0..64).into_iter().collect::<Vec<_>>().as_slice())
973 .expect("64 bytes; qed")
974 }
975
976 fn dummy_collator_id() -> CollatorId {
977 CollatorId::from_slice(&mut (0..32).into_iter().collect::<Vec<_>>().as_slice())
978 .expect("32 bytes; qed")
979 }
980
981 pub fn dummy_committed_candidate_receipt_v2() -> CommittedCandidateReceiptV2 {
982 let zeros = Hash::zero();
983 let reserved2 = [0; 64];
984
985 CommittedCandidateReceiptV2 {
986 descriptor: CandidateDescriptorV2 {
987 para_id: 0.into(),
988 relay_parent: zeros,
989 version: InternalVersion(0),
990 core_index: 123,
991 session_index: 1,
992 reserved1: Default::default(),
993 persisted_validation_data_hash: zeros,
994 pov_hash: zeros,
995 erasure_root: zeros,
996 reserved2,
997 para_head: zeros,
998 validation_code_hash: ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).hash(),
999 },
1000 commitments: CandidateCommitments {
1001 head_data: HeadData(vec![]),
1002 upward_messages: vec![].try_into().expect("empty vec fits within bounds"),
1003 new_validation_code: None,
1004 horizontal_messages: vec![].try_into().expect("empty vec fits within bounds"),
1005 processed_downward_messages: 0,
1006 hrmp_watermark: 0_u32,
1007 },
1008 }
1009 }
1010
1011 #[test]
1012 fn is_binary_compatibile() {
1013 let old_ccr = dummy_old_committed_candidate_receipt();
1014 let new_ccr = dummy_committed_candidate_receipt_v2();
1015
1016 assert_eq!(old_ccr.encoded_size(), new_ccr.encoded_size());
1017
1018 let encoded_old = old_ccr.encode();
1019
1020 let new_ccr: CommittedCandidateReceiptV2 =
1022 Decode::decode(&mut encoded_old.as_slice()).unwrap();
1023
1024 assert_eq!(old_ccr.hash(), new_ccr.hash());
1026 }
1027
1028 #[test]
1029 fn test_from_v1_descriptor() {
1030 let mut old_ccr = dummy_old_committed_candidate_receipt().to_plain();
1031 old_ccr.descriptor.collator = dummy_collator_id();
1032 old_ccr.descriptor.signature = dummy_collator_signature();
1033
1034 let mut new_ccr = dummy_committed_candidate_receipt_v2().to_plain();
1035
1036 new_ccr.descriptor = old_ccr.descriptor.clone().into();
1038
1039 assert_eq!(old_ccr.hash(), new_ccr.hash());
1041
1042 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
1043 assert_eq!(old_ccr.descriptor.collator, new_ccr.descriptor.collator().unwrap());
1044 assert_eq!(old_ccr.descriptor.signature, new_ccr.descriptor.signature().unwrap());
1045 }
1046
1047 #[test]
1048 fn invalid_version_descriptor() {
1049 let mut new_ccr = dummy_committed_candidate_receipt_v2();
1050 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V2);
1051 new_ccr.descriptor.version = InternalVersion(100);
1053
1054 let new_ccr: CommittedCandidateReceiptV2 =
1056 Decode::decode(&mut new_ccr.encode().as_slice()).unwrap();
1057
1058 assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::Unknown);
1059 assert_eq!(
1060 new_ccr.check_core_index(&BTreeMap::new()),
1061 Err(CommittedCandidateReceiptError::UnknownVersion(InternalVersion(100)))
1062 )
1063 }
1064
1065 #[test]
1066 fn test_ump_commitment() {
1067 let mut new_ccr = dummy_committed_candidate_receipt_v2();
1068 new_ccr.descriptor.core_index = 123;
1069 new_ccr.descriptor.para_id = ParaId::new(1000);
1070
1071 new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
1073 new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
1074
1075 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1077
1078 new_ccr
1080 .commitments
1081 .upward_messages
1082 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1083
1084 let mut cq = BTreeMap::new();
1085 cq.insert(
1086 CoreIndex(123),
1087 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1088 );
1089
1090 assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq)), Ok(()));
1091 }
1092
1093 #[test]
1094 fn test_invalid_ump_commitment() {
1095 let mut new_ccr = dummy_committed_candidate_receipt_v2();
1096 new_ccr.descriptor.core_index = 0;
1097 new_ccr.descriptor.para_id = ParaId::new(1000);
1098
1099 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1100
1101 let mut cq = BTreeMap::new();
1102 cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
1103
1104 assert!(new_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok());
1107
1108 new_ccr.commitments.upward_messages.force_push(vec![0, 13, 200].encode());
1110
1111 assert_eq!(
1113 new_ccr.commitments.core_selector(),
1114 Err(CommittedCandidateReceiptError::UmpSignalDecode)
1115 );
1116
1117 new_ccr.commitments.upward_messages.clear();
1120
1121 let mut cq = BTreeMap::new();
1122 cq.insert(
1123 CoreIndex(0),
1124 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1125 );
1126 cq.insert(
1127 CoreIndex(100),
1128 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1129 );
1130 assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq.clone())), Ok(()));
1131
1132 new_ccr.descriptor.set_core_index(CoreIndex(1));
1133 assert_eq!(
1134 new_ccr.check_core_index(&transpose_claim_queue(cq.clone())),
1135 Err(CommittedCandidateReceiptError::InvalidCoreIndex)
1136 );
1137 new_ccr.descriptor.set_core_index(CoreIndex(0));
1138
1139 new_ccr.commitments.upward_messages.clear();
1140 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1141 new_ccr
1142 .commitments
1143 .upward_messages
1144 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1145
1146 assert_eq!(
1148 new_ccr.check_core_index(&transpose_claim_queue(Default::default())),
1149 Err(CommittedCandidateReceiptError::NoAssignment)
1150 );
1151
1152 new_ccr.descriptor.set_core_index(CoreIndex(1));
1154 assert_eq!(
1155 new_ccr.check_core_index(&transpose_claim_queue(cq.clone())),
1156 Err(CommittedCandidateReceiptError::CoreIndexMismatch)
1157 );
1158 new_ccr.descriptor.set_core_index(CoreIndex(0));
1159
1160 new_ccr
1162 .commitments
1163 .upward_messages
1164 .force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode());
1165
1166 assert_eq!(
1167 new_ccr.check_core_index(&transpose_claim_queue(cq)),
1168 Err(CommittedCandidateReceiptError::TooManyUMPSignals)
1169 );
1170 }
1171
1172 #[test]
1173 fn test_version2_receipts_decoded_as_v1() {
1174 let mut new_ccr = dummy_committed_candidate_receipt_v2();
1175 new_ccr.descriptor.core_index = 123;
1176 new_ccr.descriptor.para_id = ParaId::new(1000);
1177
1178 new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
1180 new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
1181
1182 new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1184
1185 new_ccr
1187 .commitments
1188 .upward_messages
1189 .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1190
1191 let encoded_ccr = new_ccr.encode();
1192 let decoded_ccr: CommittedCandidateReceipt =
1193 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1194
1195 assert_eq!(decoded_ccr.descriptor.relay_parent, new_ccr.descriptor.relay_parent());
1196 assert_eq!(decoded_ccr.descriptor.para_id, new_ccr.descriptor.para_id());
1197
1198 assert_eq!(new_ccr.hash(), decoded_ccr.hash());
1199
1200 let encoded_ccr = new_ccr.encode();
1202 let v2_ccr: CommittedCandidateReceiptV2 =
1203 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1204
1205 assert_eq!(v2_ccr.descriptor.core_index(), Some(CoreIndex(123)));
1206
1207 let mut cq = BTreeMap::new();
1208 cq.insert(
1209 CoreIndex(123),
1210 vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1211 );
1212
1213 assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq)), Ok(()));
1214
1215 assert_eq!(new_ccr.hash(), v2_ccr.hash());
1216 }
1217
1218 #[test]
1220 fn test_v1_descriptors_with_ump_signal() {
1221 let mut ccr = dummy_old_committed_candidate_receipt();
1222 ccr.descriptor.para_id = ParaId::new(1024);
1223 ccr.descriptor.signature = dummy_collator_signature();
1225 ccr.descriptor.collator = dummy_collator_id();
1226
1227 ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1228 ccr.commitments
1229 .upward_messages
1230 .force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode());
1231
1232 let encoded_ccr: Vec<u8> = ccr.encode();
1233
1234 let v1_ccr: CommittedCandidateReceiptV2 =
1235 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1236
1237 assert_eq!(v1_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
1238 assert!(v1_ccr.commitments.core_selector().unwrap().is_some());
1239
1240 let mut cq = BTreeMap::new();
1241 cq.insert(CoreIndex(0), vec![v1_ccr.descriptor.para_id()].into());
1242 cq.insert(CoreIndex(1), vec![v1_ccr.descriptor.para_id()].into());
1243
1244 assert_eq!(v1_ccr.descriptor.core_index(), None);
1245
1246 assert_eq!(
1247 v1_ccr.check_core_index(&transpose_claim_queue(cq)),
1248 Err(CommittedCandidateReceiptError::CoreSelectorWithV1Decriptor)
1249 );
1250 }
1251
1252 #[test]
1253 fn test_core_select_is_optional() {
1254 let mut old_ccr = dummy_old_committed_candidate_receipt();
1256 old_ccr.descriptor.para_id = ParaId::new(1000);
1257 let encoded_ccr: Vec<u8> = old_ccr.encode();
1258
1259 let new_ccr: CommittedCandidateReceiptV2 =
1260 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1261
1262 let mut cq = BTreeMap::new();
1263 cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
1264
1265 assert!(new_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok());
1268
1269 let mut cq = BTreeMap::new();
1270 cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
1271 cq.insert(CoreIndex(1), vec![new_ccr.descriptor.para_id()].into());
1272
1273 assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq)), Ok(()));
1276
1277 old_ccr.descriptor.signature = dummy_collator_signature();
1279 old_ccr.descriptor.collator = dummy_collator_id();
1280
1281 let old_ccr_hash = old_ccr.hash();
1282
1283 let encoded_ccr: Vec<u8> = old_ccr.encode();
1284
1285 let new_ccr: CommittedCandidateReceiptV2 =
1286 Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
1287
1288 assert_eq!(new_ccr.descriptor.signature(), Some(old_ccr.descriptor.signature));
1289 assert_eq!(new_ccr.descriptor.collator(), Some(old_ccr.descriptor.collator));
1290
1291 assert_eq!(new_ccr.descriptor.core_index(), None);
1292 assert_eq!(new_ccr.descriptor.para_id(), ParaId::new(1000));
1293
1294 assert_eq!(old_ccr_hash, new_ccr.hash());
1295 }
1296}