1use alloc::{
20 collections::{BTreeMap, BTreeSet, VecDeque},
21 vec,
22 vec::{IntoIter, Vec},
23};
24
25use bitvec::{field::BitField, prelude::*, slice::BitSlice};
26
27use codec::{Decode, DecodeWithMemTracking, Encode};
28use scale_info::TypeInfo;
29
30use core::{
31 marker::PhantomData,
32 slice::{Iter, IterMut},
33};
34
35#[cfg(feature = "test")]
36use sp_application_crypto::ByteArray;
37use sp_application_crypto::KeyTypeId;
38use sp_arithmetic::{
39 traits::{BaseArithmetic, Saturating},
40 Perbill,
41};
42
43use bounded_collections::BoundedVec;
44use serde::{Deserialize, Serialize};
45use sp_core::ConstU32;
46use sp_inherents::InherentIdentifier;
47
48pub use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
53pub use sp_consensus_slots::Slot;
54pub use sp_runtime::traits::{AppVerify, BlakeTwo256, Hash as HashT, Header as HeaderT};
55pub use sp_staking::SessionIndex;
56
57pub use polkadot_core_primitives::v2::{
59 AccountId, AccountIndex, AccountPublic, Balance, Block, BlockId, BlockNumber, CandidateHash,
60 ChainId, DownwardMessage, Hash, Header, InboundDownwardMessage, InboundHrmpMessage, Moment,
61 Nonce, OutboundHrmpMessage, Remark, Signature, UncheckedExtrinsic,
62};
63
64pub use polkadot_parachain_primitives::primitives::{
66 HeadData, HorizontalMessages, HrmpChannelId, Id, Id as ParaId, UpwardMessage, UpwardMessages,
67 ValidationCode, ValidationCodeHash, LOWEST_PUBLIC_ID,
68};
69
70mod signed;
72pub use signed::{EncodeAs, Signed, UncheckedSigned};
73
74pub mod async_backing;
75pub mod executor_params;
76pub mod slashing;
77
78pub use async_backing::AsyncBackingParams;
79pub use executor_params::{
80 ExecutorHostFunction, ExecutorParam, ExecutorParamError, ExecutorParams, ExecutorParamsHash,
81 ExecutorParamsPrepHash,
82};
83
84mod metrics;
85pub use metrics::{
86 metric_definitions, RuntimeMetricLabel, RuntimeMetricLabelValue, RuntimeMetricLabelValues,
87 RuntimeMetricLabels, RuntimeMetricOp, RuntimeMetricUpdate,
88};
89
90pub const COLLATOR_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"coll");
92const LOG_TARGET: &str = "runtime::primitives";
93
94mod collator_app {
95 use sp_application_crypto::{app_crypto, sr25519};
96 app_crypto!(sr25519, super::COLLATOR_KEY_TYPE_ID);
97}
98
99pub type CollatorId = collator_app::Public;
101
102#[cfg(feature = "std")]
104pub type CollatorPair = collator_app::Pair;
105
106pub type CollatorSignature = collator_app::Signature;
108
109pub const PARACHAIN_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"para");
111
112mod validator_app {
113 use sp_application_crypto::{app_crypto, sr25519};
114 app_crypto!(sr25519, super::PARACHAIN_KEY_TYPE_ID);
115}
116
117pub type ValidatorId = validator_app::Public;
122
123pub trait TypeIndex {
125 fn type_index(&self) -> usize;
127}
128
129#[derive(
132 Eq,
133 Ord,
134 PartialEq,
135 PartialOrd,
136 Copy,
137 Clone,
138 Encode,
139 Decode,
140 DecodeWithMemTracking,
141 TypeInfo,
142 Debug,
143)]
144#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash))]
145pub struct ValidatorIndex(pub u32);
146
147#[derive(Eq, Ord, PartialEq, PartialOrd, Copy, Clone, Encode, Decode, TypeInfo, Debug)]
154#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash))]
155pub struct ChunkIndex(pub u32);
156
157impl From<ChunkIndex> for ValidatorIndex {
158 fn from(c_index: ChunkIndex) -> Self {
159 ValidatorIndex(c_index.0)
160 }
161}
162
163impl From<ValidatorIndex> for ChunkIndex {
164 fn from(v_index: ValidatorIndex) -> Self {
165 ChunkIndex(v_index.0)
166 }
167}
168
169impl From<u32> for ChunkIndex {
170 fn from(n: u32) -> Self {
171 ChunkIndex(n)
172 }
173}
174
175impl From<u32> for ValidatorIndex {
177 fn from(n: u32) -> Self {
178 ValidatorIndex(n)
179 }
180}
181
182impl TypeIndex for ValidatorIndex {
183 fn type_index(&self) -> usize {
184 self.0 as usize
185 }
186}
187
188sp_application_crypto::with_pair! {
189 pub type ValidatorPair = validator_app::Pair;
191}
192
193pub type ValidatorSignature = validator_app::Signature;
198
199pub mod well_known_keys {
201 use super::{HrmpChannelId, Id, WellKnownKey};
202 use alloc::vec::Vec;
203 use codec::Encode as _;
204 use hex_literal::hex;
205 use sp_io::hashing::twox_64;
206
207 pub const EPOCH_INDEX: &[u8] =
223 &hex!["1cb6f36e027abb2091cfb5110ab5087f38316cbf8fa0da822a20ac1c55bf1be3"];
224
225 pub const CURRENT_BLOCK_RANDOMNESS: &[u8] =
229 &hex!["1cb6f36e027abb2091cfb5110ab5087fd077dfdb8adb10f78f10a5df8742c545"];
230
231 pub const ONE_EPOCH_AGO_RANDOMNESS: &[u8] =
235 &hex!["1cb6f36e027abb2091cfb5110ab5087f7ce678799d3eff024253b90e84927cc6"];
236
237 pub const TWO_EPOCHS_AGO_RANDOMNESS: &[u8] =
241 &hex!["1cb6f36e027abb2091cfb5110ab5087f7a414cb008e0e61e46722aa60abdd672"];
242
243 pub const CURRENT_SLOT: &[u8] =
247 &hex!["1cb6f36e027abb2091cfb5110ab5087f06155b3cd9a8c9e5e9a23fd5dc13a5ed"];
248
249 pub const ACTIVE_CONFIG: &[u8] =
253 &hex!["06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385"];
254
255 pub const AUTHORITIES: &[u8] =
260 &hex!["1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d"];
261
262 pub const NEXT_AUTHORITIES: &[u8] =
267 &hex!["1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c"];
268
269 pub fn para_head(para_id: Id) -> Vec<u8> {
273 let prefix = hex!["cd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c3"];
274
275 para_id.using_encoded(|para_id: &[u8]| {
276 prefix
277 .as_ref()
278 .iter()
279 .chain(twox_64(para_id).iter())
280 .chain(para_id.iter())
281 .cloned()
282 .collect()
283 })
284 }
285
286 #[deprecated = "Use `relay_dispatch_queue_remaining_capacity` instead"]
293 pub fn relay_dispatch_queue_size(para_id: Id) -> Vec<u8> {
294 let prefix = hex!["f5207f03cfdce586301014700e2c2593fad157e461d71fd4c1f936839a5f1f3e"];
295
296 para_id.using_encoded(|para_id: &[u8]| {
297 prefix
298 .as_ref()
299 .iter()
300 .chain(twox_64(para_id).iter())
301 .chain(para_id.iter())
302 .cloned()
303 .collect()
304 })
305 }
306
307 #[deprecated = "Use `relay_dispatch_queue_remaining_capacity` instead"]
309 pub fn relay_dispatch_queue_size_typed(para: Id) -> WellKnownKey<(u32, u32)> {
310 #[allow(deprecated)]
311 relay_dispatch_queue_size(para).into()
312 }
313
314 pub fn relay_dispatch_queue_remaining_capacity(para_id: Id) -> WellKnownKey<(u32, u32)> {
322 (b":relay_dispatch_queue_remaining_capacity", para_id).encode().into()
323 }
324
325 pub fn hrmp_channels(channel: HrmpChannelId) -> Vec<u8> {
329 let prefix = hex!["6a0da05ca59913bc38a8630590f2627cb6604cff828a6e3f579ca6c59ace013d"];
330
331 channel.using_encoded(|channel: &[u8]| {
332 prefix
333 .as_ref()
334 .iter()
335 .chain(twox_64(channel).iter())
336 .chain(channel.iter())
337 .cloned()
338 .collect()
339 })
340 }
341
342 pub fn hrmp_ingress_channel_index(para_id: Id) -> Vec<u8> {
346 let prefix = hex!["6a0da05ca59913bc38a8630590f2627c1d3719f5b0b12c7105c073c507445948"];
347
348 para_id.using_encoded(|para_id: &[u8]| {
349 prefix
350 .as_ref()
351 .iter()
352 .chain(twox_64(para_id).iter())
353 .chain(para_id.iter())
354 .cloned()
355 .collect()
356 })
357 }
358
359 pub fn hrmp_egress_channel_index(para_id: Id) -> Vec<u8> {
363 let prefix = hex!["6a0da05ca59913bc38a8630590f2627cf12b746dcf32e843354583c9702cc020"];
364
365 para_id.using_encoded(|para_id: &[u8]| {
366 prefix
367 .as_ref()
368 .iter()
369 .chain(twox_64(para_id).iter())
370 .chain(para_id.iter())
371 .cloned()
372 .collect()
373 })
374 }
375
376 pub fn dmq_mqc_head(para_id: Id) -> Vec<u8> {
381 let prefix = hex!["63f78c98723ddc9073523ef3beefda0c4d7fefc408aac59dbfe80a72ac8e3ce5"];
382
383 para_id.using_encoded(|para_id: &[u8]| {
384 prefix
385 .as_ref()
386 .iter()
387 .chain(twox_64(para_id).iter())
388 .chain(para_id.iter())
389 .cloned()
390 .collect()
391 })
392 }
393
394 pub fn upgrade_go_ahead_signal(para_id: Id) -> Vec<u8> {
399 let prefix = hex!["cd710b30bd2eab0352ddcc26417aa1949e94c040f5e73d9b7addd6cb603d15d3"];
400
401 para_id.using_encoded(|para_id: &[u8]| {
402 prefix
403 .as_ref()
404 .iter()
405 .chain(twox_64(para_id).iter())
406 .chain(para_id.iter())
407 .cloned()
408 .collect()
409 })
410 }
411
412 pub fn upgrade_restriction_signal(para_id: Id) -> Vec<u8> {
417 let prefix = hex!["cd710b30bd2eab0352ddcc26417aa194f27bbb460270642b5bcaf032ea04d56a"];
418
419 para_id.using_encoded(|para_id: &[u8]| {
420 prefix
421 .as_ref()
422 .iter()
423 .chain(twox_64(para_id).iter())
424 .chain(para_id.iter())
425 .cloned()
426 .collect()
427 })
428 }
429}
430
431pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u64 = 6000;
434
435pub const PARACHAINS_INHERENT_IDENTIFIER: InherentIdentifier = *b"parachn0";
437
438pub const ASSIGNMENT_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"asgn");
440
441pub const MIN_CODE_SIZE: u32 = 9;
443
444pub const MAX_CODE_SIZE: u32 = 5 * 1024 * 1024;
453
454pub const MAX_HEAD_DATA_SIZE: u32 = 1 * 1024 * 1024;
461
462pub const MAX_POV_SIZE: u32 = 10 * 1024 * 1024;
470
471pub const ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE: u32 = 10_000;
475
476pub const ON_DEMAND_MAX_QUEUE_MAX_SIZE: u32 = 10_000;
480
481pub const LEGACY_MIN_BACKING_VOTES: u32 = 2;
484
485pub const DEFAULT_SCHEDULING_LOOKAHEAD: u32 = 3;
487
488mod assignment_app {
491 use sp_application_crypto::{app_crypto, sr25519};
492 app_crypto!(sr25519, super::ASSIGNMENT_KEY_TYPE_ID);
493}
494
495pub type AssignmentId = assignment_app::Public;
498
499sp_application_crypto::with_pair! {
500 pub type AssignmentPair = assignment_app::Pair;
503}
504
505pub type CandidateIndex = u32;
507
508#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, Debug)]
530#[cfg_attr(feature = "std", derive(Default))]
531pub struct PersistedValidationData<H = Hash, N = BlockNumber> {
532 pub parent_head: HeadData,
534 pub relay_parent_number: N,
536 pub relay_parent_storage_root: H,
538 pub max_pov_size: u32,
540}
541
542impl<H: Encode, N: Encode> PersistedValidationData<H, N> {
543 pub fn hash(&self) -> Hash {
545 BlakeTwo256::hash_of(self)
546 }
547}
548
549#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, Debug)]
551#[cfg_attr(feature = "std", derive(Default, Hash))]
552pub struct CandidateCommitments<N = BlockNumber> {
553 pub upward_messages: UpwardMessages,
555 pub horizontal_messages: HorizontalMessages,
557 pub new_validation_code: Option<ValidationCode>,
559 pub head_data: HeadData,
561 pub processed_downward_messages: u32,
563 pub hrmp_watermark: N,
566}
567
568impl CandidateCommitments {
569 pub fn hash(&self) -> Hash {
571 BlakeTwo256::hash_of(self)
572 }
573}
574
575#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
579pub struct AvailabilityBitfield(pub BitVec<u8, bitvec::order::Lsb0>);
580
581impl From<BitVec<u8, bitvec::order::Lsb0>> for AvailabilityBitfield {
582 fn from(inner: BitVec<u8, bitvec::order::Lsb0>) -> Self {
583 AvailabilityBitfield(inner)
584 }
585}
586
587pub type SignedStatement = Signed<CompactStatement>;
589pub type UncheckedSignedStatement = UncheckedSigned<CompactStatement>;
591
592pub type SignedAvailabilityBitfield = Signed<AvailabilityBitfield>;
594pub type UncheckedSignedAvailabilityBitfield = UncheckedSigned<AvailabilityBitfield>;
596
597pub type SignedAvailabilityBitfields = Vec<SignedAvailabilityBitfield>;
599pub type UncheckedSignedAvailabilityBitfields = Vec<UncheckedSignedAvailabilityBitfield>;
602
603pub fn check_candidate_backing<H: AsRef<[u8]> + Clone + Encode + core::fmt::Debug>(
614 candidate_hash: CandidateHash,
615 validity_votes: &[ValidityAttestation],
616 validator_indices: &BitSlice<u8, bitvec::order::Lsb0>,
617 signing_context: &SigningContext<H>,
618 group_len: usize,
619 validator_lookup: impl Fn(usize) -> Option<ValidatorId>,
620) -> Result<usize, ()> {
621 if validator_indices.len() != group_len {
622 log::debug!(
623 target: LOG_TARGET,
624 "Check candidate backing: indices mismatch: group_len = {} , indices_len = {}",
625 group_len,
626 validator_indices.len(),
627 );
628 return Err(());
629 }
630
631 if validity_votes.len() > group_len {
632 log::debug!(
633 target: LOG_TARGET,
634 "Check candidate backing: Too many votes, expected: {}, found: {}",
635 group_len,
636 validity_votes.len(),
637 );
638 return Err(());
639 }
640
641 let mut signed = 0;
642 for ((val_in_group_idx, _), attestation) in validator_indices
643 .iter()
644 .enumerate()
645 .filter(|(_, signed)| **signed)
646 .zip(validity_votes.iter())
647 {
648 let validator_id = validator_lookup(val_in_group_idx).ok_or(())?;
649 let payload = attestation.signed_payload(candidate_hash, signing_context);
650 let sig = attestation.signature();
651
652 if sig.verify(&payload[..], &validator_id) {
653 signed += 1;
654 } else {
655 log::debug!(
656 target: LOG_TARGET,
657 "Check candidate backing: Invalid signature. validator_id = {:?}, validator_index = {} ",
658 validator_id,
659 val_in_group_idx,
660 );
661 return Err(());
662 }
663 }
664
665 if signed != validity_votes.len() {
666 log::error!(
667 target: LOG_TARGET,
668 "Check candidate backing: Too many signatures, expected = {}, found = {}",
669 validity_votes.len(),
670 signed,
671 );
672 return Err(());
673 }
674
675 Ok(signed)
676}
677
678#[derive(
680 Encode,
681 Decode,
682 DecodeWithMemTracking,
683 Default,
684 PartialOrd,
685 Ord,
686 Eq,
687 PartialEq,
688 Clone,
689 Copy,
690 TypeInfo,
691 Debug,
692)]
693#[cfg_attr(feature = "std", derive(Hash))]
694pub struct CoreIndex(pub u32);
695
696impl From<u32> for CoreIndex {
697 fn from(i: u32) -> CoreIndex {
698 CoreIndex(i)
699 }
700}
701
702impl TypeIndex for CoreIndex {
703 fn type_index(&self) -> usize {
704 self.0 as usize
705 }
706}
707
708#[derive(
710 Encode,
711 Decode,
712 DecodeWithMemTracking,
713 Default,
714 Clone,
715 Copy,
716 Debug,
717 PartialEq,
718 Eq,
719 TypeInfo,
720 PartialOrd,
721 Ord,
722)]
723#[cfg_attr(feature = "std", derive(Hash))]
724pub struct GroupIndex(pub u32);
725
726impl From<u32> for GroupIndex {
727 fn from(i: u32) -> GroupIndex {
728 GroupIndex(i)
729 }
730}
731
732impl TypeIndex for GroupIndex {
733 fn type_index(&self) -> usize {
734 self.0 as usize
735 }
736}
737
738#[derive(Clone, Encode, Decode, TypeInfo, PartialEq, Debug)]
740pub struct ParathreadClaim(pub Id, pub Option<CollatorId>);
741
742#[derive(Clone, Encode, Decode, TypeInfo, PartialEq, Debug)]
744pub struct ParathreadEntry {
745 pub claim: ParathreadClaim,
747 pub retries: u32,
749}
750
751#[derive(Clone, Encode, Decode, TypeInfo, Debug)]
753#[cfg_attr(feature = "std", derive(PartialEq))]
754pub struct GroupRotationInfo<N = BlockNumber> {
755 pub session_start_block: N,
757 pub group_rotation_frequency: N,
759 pub now: N,
761}
762
763impl GroupRotationInfo {
764 pub fn group_for_core(&self, core_index: CoreIndex, cores: usize) -> GroupIndex {
769 if self.group_rotation_frequency == 0 {
770 return GroupIndex(core_index.0);
771 }
772 if cores == 0 {
773 return GroupIndex(0);
774 }
775
776 let cores = core::cmp::min(cores, u32::MAX as usize);
777 let blocks_since_start = self.now.saturating_sub(self.session_start_block);
778 let rotations = blocks_since_start / self.group_rotation_frequency;
779
780 let idx = (core_index.0 as usize + rotations as usize) % cores;
783 GroupIndex(idx as u32)
784 }
785
786 pub fn core_for_group(&self, group_index: GroupIndex, cores: usize) -> CoreIndex {
791 if self.group_rotation_frequency == 0 {
792 return CoreIndex(group_index.0);
793 }
794 if cores == 0 {
795 return CoreIndex(0);
796 }
797
798 let cores = core::cmp::min(cores, u32::MAX as usize);
799 let blocks_since_start = self.now.saturating_sub(self.session_start_block);
800 let rotations = blocks_since_start / self.group_rotation_frequency;
801 let rotations = rotations % cores as u32;
802
803 let idx = (group_index.0 as usize + cores - rotations as usize) % cores;
809 CoreIndex(idx as u32)
810 }
811
812 pub fn bump_rotation(&self) -> Self {
814 GroupRotationInfo {
815 session_start_block: self.session_start_block,
816 group_rotation_frequency: self.group_rotation_frequency,
817 now: self.next_rotation_at(),
818 }
819 }
820}
821
822impl<N: Saturating + BaseArithmetic + Copy> GroupRotationInfo<N> {
823 pub fn next_rotation_at(&self) -> N {
826 let cycle_once = self.now + self.group_rotation_frequency;
827 cycle_once -
828 (cycle_once.saturating_sub(self.session_start_block) % self.group_rotation_frequency)
829 }
830
831 pub fn last_rotation_at(&self) -> N {
834 self.now -
835 (self.now.saturating_sub(self.session_start_block) % self.group_rotation_frequency)
836 }
837}
838
839#[derive(Clone, Encode, Decode, TypeInfo, Debug)]
841#[cfg_attr(feature = "std", derive(PartialEq))]
842pub struct ScheduledCore {
843 pub para_id: Id,
845 pub collator: Option<CollatorId>,
849}
850
851#[derive(Clone, Copy, Encode, Decode, TypeInfo, Debug)]
853#[cfg_attr(feature = "std", derive(PartialEq, Eq, Hash))]
854pub enum OccupiedCoreAssumption {
855 #[codec(index = 0)]
857 Included,
858 #[codec(index = 1)]
860 TimedOut,
861 #[codec(index = 2)]
863 Free,
864}
865
866#[derive(Clone, Debug)]
868pub struct ApprovalVote(pub CandidateHash);
869
870impl ApprovalVote {
871 pub fn signing_payload(&self, session_index: SessionIndex) -> Vec<u8> {
873 const MAGIC: [u8; 4] = *b"APPR";
874
875 (MAGIC, &self.0, session_index).encode()
876 }
877}
878
879#[derive(Clone, Debug)]
881pub struct ApprovalVoteMultipleCandidates<'a>(pub &'a [CandidateHash]);
882
883impl<'a> ApprovalVoteMultipleCandidates<'a> {
884 pub fn signing_payload(&self, session_index: SessionIndex) -> Vec<u8> {
886 const MAGIC: [u8; 4] = *b"APPR";
887 if self.0.len() == 1 {
892 (MAGIC, self.0.first().expect("QED: we just checked"), session_index).encode()
893 } else {
894 (MAGIC, &self.0, session_index).encode()
895 }
896 }
897}
898
899#[derive(
901 Debug,
902 Copy,
903 Clone,
904 PartialEq,
905 Encode,
906 Decode,
907 DecodeWithMemTracking,
908 TypeInfo,
909 serde::Serialize,
910 serde::Deserialize,
911)]
912pub struct ApprovalVotingParams {
913 pub max_approval_coalesce_count: u32,
918}
919
920impl Default for ApprovalVotingParams {
921 fn default() -> Self {
922 Self { max_approval_coalesce_count: 1 }
923 }
924}
925
926#[repr(u8)]
928pub enum ValidityError {
929 InvalidEthereumSignature = 0,
931 SignerHasNoClaim = 1,
933 NoPermission = 2,
935 InvalidStatement = 3,
937}
938
939impl From<ValidityError> for u8 {
940 fn from(err: ValidityError) -> Self {
941 err as u8
942 }
943}
944
945#[derive(Clone, Encode, Decode, Debug, TypeInfo)]
948#[cfg_attr(feature = "std", derive(PartialEq))]
949pub struct AbridgedHostConfiguration {
950 pub max_code_size: u32,
952 pub max_head_data_size: u32,
954 pub max_upward_queue_count: u32,
956 pub max_upward_queue_size: u32,
960 pub max_upward_message_size: u32,
964 pub max_upward_message_num_per_candidate: u32,
968 pub hrmp_max_message_num_per_candidate: u32,
972 pub validation_upgrade_cooldown: BlockNumber,
974 pub validation_upgrade_delay: BlockNumber,
976 pub async_backing_params: AsyncBackingParams,
978}
979
980#[derive(Clone, Encode, Decode, Debug, TypeInfo)]
983#[cfg_attr(feature = "std", derive(Default, PartialEq))]
984pub struct AbridgedHrmpChannel {
985 pub max_capacity: u32,
987 pub max_total_size: u32,
989 pub max_message_size: u32,
991 pub msg_count: u32,
994 pub total_size: u32,
997 pub mqc_head: Option<Hash>,
1005}
1006
1007#[derive(Copy, Clone, Encode, Decode, PartialEq, Debug, TypeInfo)]
1009pub enum UpgradeRestriction {
1010 #[codec(index = 0)]
1013 Present,
1014}
1015
1016#[derive(Copy, Clone, Encode, Decode, PartialEq, Debug, TypeInfo)]
1022pub enum UpgradeGoAhead {
1023 #[codec(index = 0)]
1032 Abort,
1033 #[codec(index = 1)]
1037 GoAhead,
1038}
1039
1040pub const POLKADOT_ENGINE_ID: sp_runtime::ConsensusEngineId = *b"POL1";
1042
1043#[derive(Decode, Encode, Clone, PartialEq, Eq)]
1045pub enum ConsensusLog {
1046 #[codec(index = 1)]
1048 ParaUpgradeCode(Id, ValidationCodeHash),
1049 #[codec(index = 2)]
1051 ParaScheduleUpgradeCode(Id, ValidationCodeHash, BlockNumber),
1052 #[codec(index = 3)]
1055 ForceApprove(BlockNumber),
1056 #[codec(index = 4)]
1065 Revert(BlockNumber),
1066}
1067
1068impl ConsensusLog {
1069 pub fn from_digest_item(
1071 digest_item: &sp_runtime::DigestItem,
1072 ) -> Result<Option<Self>, codec::Error> {
1073 match digest_item {
1074 sp_runtime::DigestItem::Consensus(id, encoded) if id == &POLKADOT_ENGINE_ID => {
1075 Ok(Some(Self::decode(&mut &encoded[..])?))
1076 },
1077 _ => Ok(None),
1078 }
1079 }
1080}
1081
1082impl From<ConsensusLog> for sp_runtime::DigestItem {
1083 fn from(c: ConsensusLog) -> sp_runtime::DigestItem {
1084 Self::Consensus(POLKADOT_ENGINE_ID, c.encode())
1085 }
1086}
1087
1088#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Debug, TypeInfo)]
1092pub enum DisputeStatement {
1093 #[codec(index = 0)]
1095 Valid(ValidDisputeStatementKind),
1096 #[codec(index = 1)]
1098 Invalid(InvalidDisputeStatementKind),
1099}
1100
1101impl DisputeStatement {
1102 pub fn payload_data(
1107 &self,
1108 candidate_hash: CandidateHash,
1109 session: SessionIndex,
1110 ) -> Result<Vec<u8>, ()> {
1111 match self {
1112 DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) => {
1113 Ok(ExplicitDisputeStatement { valid: true, candidate_hash, session }
1114 .signing_payload())
1115 },
1116 DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded(
1117 inclusion_parent,
1118 )) => Ok(CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext {
1119 session_index: session,
1120 parent_hash: *inclusion_parent,
1121 })),
1122 DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent)) => {
1123 Ok(CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext {
1124 session_index: session,
1125 parent_hash: *inclusion_parent,
1126 }))
1127 },
1128 DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) => {
1129 Ok(ApprovalVote(candidate_hash).signing_payload(session))
1130 },
1131 DisputeStatement::Valid(
1132 ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes),
1133 ) => {
1134 if candidate_hashes.contains(&candidate_hash) {
1135 Ok(ApprovalVoteMultipleCandidates(candidate_hashes).signing_payload(session))
1136 } else {
1137 Err(())
1138 }
1139 },
1140 DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) => {
1141 Ok(ExplicitDisputeStatement { valid: false, candidate_hash, session }
1142 .signing_payload())
1143 },
1144 }
1145 }
1146
1147 pub fn check_signature(
1149 &self,
1150 validator_public: &ValidatorId,
1151 candidate_hash: CandidateHash,
1152 session: SessionIndex,
1153 validator_signature: &ValidatorSignature,
1154 ) -> Result<(), ()> {
1155 let payload = self.payload_data(candidate_hash, session)?;
1156
1157 if validator_signature.verify(&payload[..], &validator_public) {
1158 Ok(())
1159 } else {
1160 Err(())
1161 }
1162 }
1163
1164 pub fn indicates_validity(&self) -> bool {
1166 match *self {
1167 DisputeStatement::Valid(_) => true,
1168 DisputeStatement::Invalid(_) => false,
1169 }
1170 }
1171
1172 pub fn indicates_invalidity(&self) -> bool {
1174 match *self {
1175 DisputeStatement::Valid(_) => false,
1176 DisputeStatement::Invalid(_) => true,
1177 }
1178 }
1179
1180 pub fn is_backing(&self) -> bool {
1182 match self {
1183 Self::Valid(s) => s.is_backing(),
1184 Self::Invalid(_) => false,
1185 }
1186 }
1187}
1188
1189pub const MAX_COALESCE_APPROVALS: u32 = 16;
1194
1195pub type CoalescedApprovalCandidateHashes =
1199 BoundedVec<CandidateHash, ConstU32<{ MAX_COALESCE_APPROVALS }>>;
1200
1201#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Debug, TypeInfo)]
1203pub enum ValidDisputeStatementKind {
1204 #[codec(index = 0)]
1206 Explicit,
1207 #[codec(index = 1)]
1209 BackingSeconded(Hash),
1210 #[codec(index = 2)]
1212 BackingValid(Hash),
1213 #[codec(index = 3)]
1215 ApprovalChecking,
1216 #[codec(index = 4)]
1226 ApprovalCheckingMultipleCandidates(
1227 BoundedVec<CandidateHash, ConstU32<{ MAX_COALESCE_APPROVALS }>>,
1228 ),
1229}
1230
1231impl ValidDisputeStatementKind {
1232 pub fn is_backing(&self) -> bool {
1234 match self {
1235 ValidDisputeStatementKind::BackingSeconded(_) |
1236 ValidDisputeStatementKind::BackingValid(_) => true,
1237 ValidDisputeStatementKind::Explicit |
1238 ValidDisputeStatementKind::ApprovalChecking |
1239 ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(_) => false,
1240 }
1241 }
1242}
1243
1244#[derive(Encode, Decode, DecodeWithMemTracking, Copy, Clone, PartialEq, Debug, TypeInfo)]
1246pub enum InvalidDisputeStatementKind {
1247 #[codec(index = 0)]
1249 Explicit,
1250}
1251
1252#[derive(Clone, PartialEq, Debug)]
1254pub struct ExplicitDisputeStatement {
1255 pub valid: bool,
1257 pub candidate_hash: CandidateHash,
1259 pub session: SessionIndex,
1261}
1262
1263impl ExplicitDisputeStatement {
1264 pub fn signing_payload(&self) -> Vec<u8> {
1266 const MAGIC: [u8; 4] = *b"DISP";
1267
1268 (MAGIC, self.valid, self.candidate_hash, self.session).encode()
1269 }
1270}
1271
1272#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Debug, TypeInfo)]
1274pub struct DisputeStatementSet {
1275 pub candidate_hash: CandidateHash,
1277 pub session: SessionIndex,
1279 pub statements: Vec<(DisputeStatement, ValidatorIndex, ValidatorSignature)>,
1281}
1282
1283impl From<CheckedDisputeStatementSet> for DisputeStatementSet {
1284 fn from(other: CheckedDisputeStatementSet) -> Self {
1285 other.0
1286 }
1287}
1288
1289impl AsRef<DisputeStatementSet> for DisputeStatementSet {
1290 fn as_ref(&self) -> &DisputeStatementSet {
1291 &self
1292 }
1293}
1294
1295pub type MultiDisputeStatementSet = Vec<DisputeStatementSet>;
1297
1298#[derive(Clone, PartialEq, Debug, Encode)]
1300pub struct CheckedDisputeStatementSet(DisputeStatementSet);
1301
1302impl AsRef<DisputeStatementSet> for CheckedDisputeStatementSet {
1303 fn as_ref(&self) -> &DisputeStatementSet {
1304 &self.0
1305 }
1306}
1307
1308impl core::cmp::PartialEq<DisputeStatementSet> for CheckedDisputeStatementSet {
1309 fn eq(&self, other: &DisputeStatementSet) -> bool {
1310 self.0.eq(other)
1311 }
1312}
1313
1314impl CheckedDisputeStatementSet {
1315 pub fn unchecked_from_unchecked(unchecked: DisputeStatementSet) -> Self {
1318 Self(unchecked)
1319 }
1320}
1321
1322pub type CheckedMultiDisputeStatementSet = Vec<CheckedDisputeStatementSet>;
1324
1325#[derive(Encode, Decode, Clone, Debug, PartialEq, TypeInfo)]
1327pub struct DisputeState<N = BlockNumber> {
1328 pub validators_for: BitVec<u8, bitvec::order::Lsb0>, pub validators_against: BitVec<u8, bitvec::order::Lsb0>, pub start: N,
1334 pub concluded_at: Option<N>,
1336}
1337
1338#[derive(Clone, Eq, PartialEq, Decode, DecodeWithMemTracking, Encode, Debug, TypeInfo)]
1341pub enum ValidityAttestation {
1342 #[codec(index = 1)]
1345 Implicit(ValidatorSignature),
1346 #[codec(index = 2)]
1349 Explicit(ValidatorSignature),
1350}
1351
1352impl ValidityAttestation {
1353 pub fn to_compact_statement(&self, candidate_hash: CandidateHash) -> CompactStatement {
1356 match *self {
1360 ValidityAttestation::Implicit(_) => CompactStatement::Seconded(candidate_hash),
1361 ValidityAttestation::Explicit(_) => CompactStatement::Valid(candidate_hash),
1362 }
1363 }
1364
1365 pub fn signature(&self) -> &ValidatorSignature {
1367 match *self {
1368 ValidityAttestation::Implicit(ref sig) => sig,
1369 ValidityAttestation::Explicit(ref sig) => sig,
1370 }
1371 }
1372
1373 pub fn signed_payload<H: Encode>(
1376 &self,
1377 candidate_hash: CandidateHash,
1378 signing_context: &SigningContext<H>,
1379 ) -> Vec<u8> {
1380 match *self {
1381 ValidityAttestation::Implicit(_) => {
1382 (CompactStatement::Seconded(candidate_hash), signing_context).encode()
1383 },
1384 ValidityAttestation::Explicit(_) => {
1385 (CompactStatement::Valid(candidate_hash), signing_context).encode()
1386 },
1387 }
1388 }
1389}
1390
1391#[derive(Clone, Eq, PartialEq, Default, Decode, Encode, Debug)]
1393pub struct SigningContext<H = Hash> {
1394 pub session_index: sp_staking::SessionIndex,
1396 pub parent_hash: H,
1398}
1399
1400const BACKING_STATEMENT_MAGIC: [u8; 4] = *b"BKNG";
1401
1402#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
1405#[cfg_attr(feature = "std", derive(Hash))]
1406pub enum CompactStatement {
1407 Seconded(CandidateHash),
1409 Valid(CandidateHash),
1411}
1412
1413impl CompactStatement {
1414 pub fn signing_payload(&self, context: &SigningContext) -> Vec<u8> {
1417 (self, context).encode()
1418 }
1419
1420 pub fn candidate_hash(&self) -> &CandidateHash {
1422 match *self {
1423 CompactStatement::Seconded(ref h) | CompactStatement::Valid(ref h) => h,
1424 }
1425 }
1426}
1427
1428#[derive(Encode, Decode, TypeInfo)]
1430enum CompactStatementInner {
1431 #[codec(index = 1)]
1432 Seconded(CandidateHash),
1433 #[codec(index = 2)]
1434 Valid(CandidateHash),
1435}
1436
1437impl From<CompactStatement> for CompactStatementInner {
1438 fn from(s: CompactStatement) -> Self {
1439 match s {
1440 CompactStatement::Seconded(h) => CompactStatementInner::Seconded(h),
1441 CompactStatement::Valid(h) => CompactStatementInner::Valid(h),
1442 }
1443 }
1444}
1445
1446impl codec::Encode for CompactStatement {
1447 fn size_hint(&self) -> usize {
1448 4 + 1 + 32
1450 }
1451
1452 fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
1453 dest.write(&BACKING_STATEMENT_MAGIC);
1454 CompactStatementInner::from(self.clone()).encode_to(dest)
1455 }
1456}
1457
1458impl codec::Decode for CompactStatement {
1459 fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
1460 let maybe_magic = <[u8; 4]>::decode(input)?;
1461 if maybe_magic != BACKING_STATEMENT_MAGIC {
1462 return Err(codec::Error::from("invalid magic string"));
1463 }
1464
1465 Ok(match CompactStatementInner::decode(input)? {
1466 CompactStatementInner::Seconded(h) => CompactStatement::Seconded(h),
1467 CompactStatementInner::Valid(h) => CompactStatement::Valid(h),
1468 })
1469 }
1470}
1471
1472#[derive(Clone, Encode, Decode, Debug, TypeInfo)]
1474#[cfg_attr(feature = "std", derive(PartialEq))]
1475pub struct IndexedVec<K, V>(Vec<V>, PhantomData<fn(K) -> K>);
1476
1477impl<K, V> Default for IndexedVec<K, V> {
1478 fn default() -> Self {
1479 Self(vec![], PhantomData)
1480 }
1481}
1482
1483impl<K, V> From<Vec<V>> for IndexedVec<K, V> {
1484 fn from(validators: Vec<V>) -> Self {
1485 Self(validators, PhantomData)
1486 }
1487}
1488
1489impl<K, V> FromIterator<V> for IndexedVec<K, V> {
1490 fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
1491 Self(Vec::from_iter(iter), PhantomData)
1492 }
1493}
1494
1495impl<K, V> IndexedVec<K, V>
1496where
1497 V: Clone,
1498{
1499 pub fn get(&self, index: K) -> Option<&V>
1501 where
1502 K: TypeIndex,
1503 {
1504 self.0.get(index.type_index())
1505 }
1506
1507 pub fn get_mut(&mut self, index: K) -> Option<&mut V>
1509 where
1510 K: TypeIndex,
1511 {
1512 self.0.get_mut(index.type_index())
1513 }
1514
1515 pub fn len(&self) -> usize {
1517 self.0.len()
1518 }
1519
1520 pub fn to_vec(&self) -> Vec<V> {
1522 self.0.clone()
1523 }
1524
1525 pub fn iter(&self) -> Iter<'_, V> {
1527 self.0.iter()
1528 }
1529
1530 pub fn iter_mut(&mut self) -> IterMut<'_, V> {
1532 self.0.iter_mut()
1533 }
1534
1535 pub fn into_iter(self) -> IntoIter<V> {
1537 self.0.into_iter()
1538 }
1539
1540 pub fn is_empty(&self) -> bool {
1542 self.0.is_empty()
1543 }
1544}
1545
1546pub const fn byzantine_threshold(n: usize) -> usize {
1550 n.saturating_sub(1) / 3
1551}
1552
1553pub const fn supermajority_threshold(n: usize) -> usize {
1556 n - byzantine_threshold(n)
1557}
1558
1559pub fn effective_minimum_backing_votes(
1561 group_len: usize,
1562 configured_minimum_backing_votes: u32,
1563) -> usize {
1564 core::cmp::min(group_len, configured_minimum_backing_votes as usize)
1565}
1566
1567#[derive(Clone, Encode, Decode, Debug, TypeInfo)]
1572#[cfg_attr(feature = "std", derive(PartialEq))]
1573pub struct SessionInfo {
1574 pub active_validator_indices: Vec<ValidatorIndex>,
1578 pub random_seed: [u8; 32],
1580 pub dispute_period: SessionIndex,
1582
1583 pub validators: IndexedVec<ValidatorIndex, ValidatorId>,
1592 pub discovery_keys: Vec<AuthorityDiscoveryId>,
1599 pub assignment_keys: Vec<AssignmentId>,
1610 pub validator_groups: IndexedVec<GroupIndex, Vec<ValidatorIndex>>,
1614 pub n_cores: u32,
1616 pub zeroth_delay_tranche_width: u32,
1618 pub relay_vrf_modulo_samples: u32,
1620 pub n_delay_tranches: u32,
1622 pub no_show_slots: u32,
1625 pub needed_approvals: u32,
1627}
1628
1629#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Debug, TypeInfo)]
1632pub struct PvfCheckStatement {
1633 pub accept: bool,
1635 pub subject: ValidationCodeHash,
1637 pub session_index: SessionIndex,
1639 pub validator_index: ValidatorIndex,
1641}
1642
1643impl PvfCheckStatement {
1644 pub fn signing_payload(&self) -> Vec<u8> {
1649 const MAGIC: [u8; 4] = *b"VCPC"; (MAGIC, self.accept, self.subject, self.session_index, self.validator_index).encode()
1651 }
1652}
1653
1654pub struct WellKnownKey<T> {
1658 pub key: Vec<u8>,
1660 _p: core::marker::PhantomData<T>,
1661}
1662
1663impl<T> From<Vec<u8>> for WellKnownKey<T> {
1664 fn from(key: Vec<u8>) -> Self {
1665 Self { key, _p: Default::default() }
1666 }
1667}
1668
1669impl<T> AsRef<[u8]> for WellKnownKey<T> {
1670 fn as_ref(&self) -> &[u8] {
1671 self.key.as_ref()
1672 }
1673}
1674
1675impl<T: Decode> WellKnownKey<T> {
1676 pub fn get(&self) -> Option<T> {
1678 sp_io::storage::get(&self.key)
1679 .and_then(|raw| codec::DecodeAll::decode_all(&mut raw.as_ref()).ok())
1680 }
1681}
1682
1683impl<T: Encode> WellKnownKey<T> {
1684 pub fn set(&self, value: T) {
1686 sp_io::storage::set(&self.key, &value.encode());
1687 }
1688}
1689
1690#[derive(
1692 Encode,
1693 Decode,
1694 DecodeWithMemTracking,
1695 TypeInfo,
1696 Clone,
1697 Copy,
1698 Debug,
1699 PartialEq,
1700 Eq,
1701 Serialize,
1702 Deserialize,
1703)]
1704pub enum PvfPrepKind {
1705 Precheck,
1707
1708 Prepare,
1710}
1711
1712#[derive(
1714 Encode,
1715 Decode,
1716 DecodeWithMemTracking,
1717 TypeInfo,
1718 Clone,
1719 Copy,
1720 Debug,
1721 PartialEq,
1722 Eq,
1723 Serialize,
1724 Deserialize,
1725)]
1726pub enum PvfExecKind {
1727 Backing,
1729 Approval,
1731}
1732
1733pub type NodeFeatures = BitVec<u8, bitvec::order::Lsb0>;
1735
1736pub mod node_features {
1738 use crate::NodeFeatures;
1739
1740 #[repr(u8)]
1743 #[derive(Clone, Copy)]
1744 pub enum FeatureIndex {
1745 EnableAssignmentsV2 = 0,
1748 ElasticScalingMVP = 1,
1752 AvailabilityChunkMapping = 2,
1758 CandidateReceiptV2 = 3,
1762 CandidateReceiptV3 = 4,
1764 FirstUnassigned = 5,
1768 }
1769
1770 impl FeatureIndex {
1771 pub fn is_set(self, node_features: &NodeFeatures) -> bool {
1773 node_features.get(self as usize).map(|v| *v).unwrap_or(false)
1774 }
1775 }
1776}
1777
1778#[derive(
1780 Debug,
1781 Copy,
1782 Clone,
1783 PartialEq,
1784 Encode,
1785 Decode,
1786 DecodeWithMemTracking,
1787 TypeInfo,
1788 serde::Serialize,
1789 serde::Deserialize,
1790)]
1791pub struct SchedulerParams<BlockNumber> {
1792 pub group_rotation_frequency: BlockNumber,
1796 pub paras_availability_period: BlockNumber,
1811 pub max_validators_per_core: Option<u32>,
1815 pub lookahead: u32,
1817 pub num_cores: u32,
1819 #[deprecated]
1822 pub max_availability_timeouts: u32,
1823 pub on_demand_queue_max_size: u32,
1825 pub on_demand_target_queue_utilization: Perbill,
1827 pub on_demand_fee_variability: Perbill,
1830 pub on_demand_base_fee: Balance,
1832 #[deprecated]
1835 pub ttl: BlockNumber,
1836}
1837
1838impl<BlockNumber: Default + From<u32>> Default for SchedulerParams<BlockNumber> {
1839 #[allow(deprecated)]
1840 fn default() -> Self {
1841 Self {
1842 group_rotation_frequency: 1u32.into(),
1843 paras_availability_period: 1u32.into(),
1844 max_validators_per_core: Default::default(),
1845 lookahead: 1,
1846 num_cores: Default::default(),
1847 max_availability_timeouts: Default::default(),
1848 on_demand_queue_max_size: ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE,
1849 on_demand_target_queue_utilization: Perbill::from_percent(25),
1850 on_demand_fee_variability: Perbill::from_percent(3),
1851 on_demand_base_fee: 10_000_000u128,
1852 ttl: 5u32.into(),
1853 }
1854 }
1855}
1856
1857#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, TypeInfo, Debug, PartialOrd, Ord, Hash)]
1859pub enum CandidateDescriptorVersion {
1860 V1,
1862 V2,
1870 V3,
1872 Unknown,
1876}
1877
1878#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1880pub enum CandidateDescriptorVersionCheckError {
1881 Inconsistency,
1884 V3NotEnabled,
1886}
1887
1888impl core::fmt::Display for CandidateDescriptorVersionCheckError {
1891 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1892 match self {
1893 Self::Inconsistency => {
1894 write!(f, "Descriptor version detection inconsistency (old vs new rules disagree)")
1895 },
1896 Self::V3NotEnabled => write!(f, "V3 candidate descriptor but V3 feature not enabled"),
1897 }
1898 }
1899}
1900
1901#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
1903pub struct CandidateDescriptorV2<H = Hash> {
1904 pub(super) para_id: ParaId,
1906 relay_parent: H,
1908 pub(super) version: u8,
1913 pub(super) core_index: u16,
1915 session_index: SessionIndex,
1917 scheduling_session_offset: u8,
1922 reserved1: [u8; 24],
1924 persisted_validation_data_hash: Hash,
1928 pov_hash: Hash,
1930 erasure_root: Hash,
1932 scheduling_parent: H, reserved2: [u8; 32],
1936 para_head: Hash,
1938 validation_code_hash: ValidationCodeHash,
1940}
1941
1942impl<H: AsRef<[u8]>> CandidateDescriptorV2<H> {
1943 pub fn version(&self) -> CandidateDescriptorVersion {
2007 self.v3_version()
2008 }
2009
2010 pub fn version_old_rules(&self) -> CandidateDescriptorVersion {
2021 self.v2_version()
2022 }
2023
2024 pub fn check_version_consistency(&self) -> bool {
2031 self.version() == self.version_old_rules()
2032 }
2033
2034 pub fn check_version_acceptance(
2051 &self,
2052 v3_enabled: bool,
2053 ) -> Result<(), CandidateDescriptorVersionCheckError> {
2054 let version = self.version();
2055
2056 let is_expected_v3_disagreement = version == CandidateDescriptorVersion::V3 && v3_enabled;
2059 if !self.check_version_consistency() && !is_expected_v3_disagreement {
2060 return Err(CandidateDescriptorVersionCheckError::Inconsistency);
2061 }
2062
2063 if version == CandidateDescriptorVersion::V3 && !v3_enabled {
2065 return Err(CandidateDescriptorVersionCheckError::V3NotEnabled);
2066 }
2067
2068 Ok(())
2069 }
2070
2071 fn v2_version(&self) -> CandidateDescriptorVersion {
2072 let old_v1_detected = self.reserved2 != [0u8; 32] ||
2076 self.reserved1 != [0u8; 24] ||
2077 self.scheduling_session_offset != 0 ||
2078 self.scheduling_parent.as_ref() != &[0u8; 32];
2079
2080 if old_v1_detected {
2081 return CandidateDescriptorVersion::V1;
2082 }
2083
2084 match self.version {
2085 0 => CandidateDescriptorVersion::V2,
2086 _ => CandidateDescriptorVersion::Unknown,
2087 }
2088 }
2089}
2090
2091impl<H> CandidateDescriptorV2<H> {
2092 fn v3_version(&self) -> CandidateDescriptorVersion {
2093 let new_v1_detected = self.reserved1[0..16] != [0u8; 16];
2102
2103 if new_v1_detected {
2104 return CandidateDescriptorVersion::V1;
2105 }
2106 match self.version {
2107 0 => CandidateDescriptorVersion::V2,
2108 1 => CandidateDescriptorVersion::V3,
2109 _ => CandidateDescriptorVersion::Unknown,
2110 }
2111 }
2112}
2113
2114macro_rules! impl_getter {
2115 ($field:ident, $type:ident) => {
2116 pub fn $field(&self) -> $type {
2118 self.$field
2119 }
2120 };
2121}
2122
2123impl<H: Copy + AsRef<[u8]>> CandidateDescriptorV2<H> {
2124 impl_getter!(erasure_root, Hash);
2125 impl_getter!(para_head, Hash);
2126 impl_getter!(relay_parent, H);
2127 impl_getter!(para_id, ParaId);
2128 impl_getter!(persisted_validation_data_hash, Hash);
2129 impl_getter!(pov_hash, Hash);
2130 impl_getter!(validation_code_hash, ValidationCodeHash);
2131
2132 #[cfg(feature = "test")]
2133 fn rebuild_collator_field(&self) -> CollatorId {
2134 let mut collator_id = Vec::with_capacity(32);
2135 let core_index: [u8; 2] = self.core_index.to_ne_bytes();
2136 let session_index: [u8; 4] = self.session_index.to_ne_bytes();
2137
2138 collator_id.push(self.version);
2139 collator_id.extend_from_slice(core_index.as_slice());
2140 collator_id.extend_from_slice(session_index.as_slice());
2141 collator_id.push(self.scheduling_session_offset);
2142 collator_id.extend_from_slice(self.reserved1.as_slice());
2143
2144 CollatorId::from_slice(&collator_id.as_slice())
2145 .expect("Slice size is exactly 32 bytes; qed")
2146 }
2147
2148 #[cfg(feature = "test")]
2150 pub fn collator(&self) -> Option<CollatorId> {
2151 if self.version() == CandidateDescriptorVersion::V1 {
2152 Some(self.rebuild_collator_field())
2153 } else {
2154 None
2155 }
2156 }
2157
2158 #[cfg(feature = "test")]
2159 fn rebuild_signature_field(&self) -> CollatorSignature {
2160 let mut signature_bytes = Vec::with_capacity(64);
2161 signature_bytes.extend_from_slice(self.scheduling_parent.as_ref());
2162 signature_bytes.extend_from_slice(self.reserved2.as_slice());
2163
2164 CollatorSignature::from_slice(&signature_bytes)
2165 .expect("Slice size is exactly 64 bytes; qed")
2166 }
2167
2168 #[cfg(feature = "test")]
2169 #[doc(hidden)]
2170 pub fn rebuild_collator_field_for_tests(&self) -> CollatorId {
2171 self.rebuild_collator_field()
2172 }
2173
2174 #[cfg(feature = "test")]
2175 #[doc(hidden)]
2176 pub fn rebuild_signature_field_for_tests(&self) -> CollatorSignature {
2177 self.rebuild_signature_field()
2178 }
2179
2180 #[cfg(feature = "test")]
2182 pub fn signature(&self) -> Option<CollatorSignature> {
2183 if self.version() == CandidateDescriptorVersion::V1 {
2184 return Some(self.rebuild_signature_field());
2185 }
2186
2187 None
2188 }
2189
2190 pub fn core_index(&self) -> Option<CoreIndex> {
2192 if self.version() == CandidateDescriptorVersion::V1 {
2193 return None;
2194 }
2195
2196 Some(CoreIndex(self.core_index as u32))
2197 }
2198
2199 pub fn session_index(&self) -> Option<SessionIndex> {
2201 if self.version() == CandidateDescriptorVersion::V1 {
2202 return None;
2203 }
2204
2205 Some(self.session_index)
2206 }
2207
2208 pub fn scheduling_parent(&self) -> H {
2214 match self.version() {
2215 CandidateDescriptorVersion::V1 => self.relay_parent,
2216 CandidateDescriptorVersion::V2 => self.relay_parent,
2217 CandidateDescriptorVersion::V3 => self.scheduling_parent,
2218 CandidateDescriptorVersion::Unknown => self.relay_parent,
2219 }
2220 }
2221
2222 pub fn scheduling_session(&self) -> Option<SessionIndex> {
2229 match self.version() {
2230 CandidateDescriptorVersion::V1 => None,
2231 CandidateDescriptorVersion::V2 => Some(self.session_index),
2232 CandidateDescriptorVersion::V3 => {
2233 Some(self.session_index.saturating_add(self.scheduling_session_offset as _))
2234 },
2235 CandidateDescriptorVersion::Unknown => None,
2236 }
2237 }
2238
2239 pub fn version_for_candidate_validation(
2258 &self,
2259 v3_ever_seen: bool,
2260 ) -> CandidateDescriptorVersion {
2261 if v3_ever_seen {
2262 self.version()
2263 } else {
2264 self.version_old_rules()
2265 }
2266 }
2267
2268 pub fn scheduling_parent_for_candidate_validation(&self, v3_ever_seen: bool) -> H
2272 where
2273 H: Copy,
2274 {
2275 match self.version_for_candidate_validation(v3_ever_seen) {
2276 CandidateDescriptorVersion::V3 => self.scheduling_parent,
2277 _ => self.relay_parent,
2278 }
2279 }
2280
2281 pub fn scheduling_session_for_candidate_validation(
2285 &self,
2286 v3_ever_seen: bool,
2287 ) -> Option<SessionIndex> {
2288 match self.version_for_candidate_validation(v3_ever_seen) {
2289 CandidateDescriptorVersion::V1 => None,
2290 CandidateDescriptorVersion::V2 => Some(self.session_index),
2291 CandidateDescriptorVersion::V3 => {
2292 Some(self.session_index.saturating_add(self.scheduling_session_offset as _))
2293 },
2294 CandidateDescriptorVersion::Unknown => None,
2295 }
2296 }
2297
2298 pub fn session_index_for_candidate_validation(
2302 &self,
2303 v3_ever_seen: bool,
2304 ) -> Option<SessionIndex> {
2305 match self.version_for_candidate_validation(v3_ever_seen) {
2306 CandidateDescriptorVersion::V1 | CandidateDescriptorVersion::Unknown => None,
2307 CandidateDescriptorVersion::V2 | CandidateDescriptorVersion::V3 => {
2308 Some(self.session_index)
2309 },
2310 }
2311 }
2312}
2313
2314impl<H> core::fmt::Debug for CandidateDescriptorV2<H>
2315where
2316 H: core::fmt::Debug,
2317{
2318 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2319 match self.v3_version() {
2322 CandidateDescriptorVersion::V1 => f
2323 .debug_struct("CandidateDescriptorV1")
2324 .field("para_id", &self.para_id)
2325 .field("relay_parent", &self.relay_parent)
2326 .field("persisted_validation_hash", &self.persisted_validation_data_hash)
2327 .field("pov_hash", &self.pov_hash)
2328 .field("erasure_root", &self.erasure_root)
2329 .field("para_head", &self.para_head)
2330 .field("validation_code_hash", &self.validation_code_hash)
2331 .finish(),
2332 CandidateDescriptorVersion::V2 => f
2333 .debug_struct("CandidateDescriptorV2")
2334 .field("para_id", &self.para_id)
2335 .field("relay_parent", &self.relay_parent)
2336 .field("core_index", &self.core_index)
2337 .field("session_index", &self.session_index)
2338 .field("persisted_validation_data_hash", &self.persisted_validation_data_hash)
2339 .field("pov_hash", &self.pov_hash)
2340 .field("erasure_root", &self.erasure_root)
2341 .field("para_head", &self.para_head)
2342 .field("validation_code_hash", &self.validation_code_hash)
2343 .finish(),
2344 CandidateDescriptorVersion::V3 => f
2345 .debug_struct("CandidateDescriptorV3")
2346 .field("para_id", &self.para_id)
2347 .field("relay_parent", &self.relay_parent)
2348 .field("core_index", &self.core_index)
2349 .field("session_index", &self.session_index)
2350 .field("scheduling_session_offset", &self.scheduling_session_offset)
2351 .field("persisted_validation_data_hash", &self.persisted_validation_data_hash)
2352 .field("pov_hash", &self.pov_hash)
2353 .field("erasure_root", &self.erasure_root)
2354 .field("scheduling_parent", &self.scheduling_parent)
2355 .field("para_head", &self.para_head)
2356 .field("validation_code_hash", &self.validation_code_hash)
2357 .finish(),
2358 CandidateDescriptorVersion::Unknown => {
2359 write!(f, "CandidateDescriptorV2(unknown version={})", self.version)
2360 },
2361 }
2362 }
2363}
2364
2365impl<H: Copy + AsRef<[u8]>> CandidateDescriptorV2<H> {
2366 pub fn new(
2368 para_id: Id,
2369 relay_parent: H,
2370 core_index: CoreIndex,
2371 session_index: SessionIndex,
2372 persisted_validation_data_hash: Hash,
2373 pov_hash: Hash,
2374 erasure_root: Hash,
2375 para_head: Hash,
2376 validation_code_hash: ValidationCodeHash,
2377 ) -> Self
2378 where
2379 H: Default,
2380 {
2381 Self {
2382 para_id,
2383 relay_parent,
2384 version: 0,
2385 core_index: core_index.0 as u16,
2386 session_index,
2387 scheduling_session_offset: 0,
2388 reserved1: [0; 24],
2389 persisted_validation_data_hash,
2390 pov_hash,
2391 erasure_root,
2392 scheduling_parent: H::default(),
2393 reserved2: [0; 32],
2394 para_head,
2395 validation_code_hash,
2396 }
2397 }
2398
2399 pub fn new_v3(
2405 para_id: Id,
2406 relay_parent: H,
2407 core_index: CoreIndex,
2408 session_index: SessionIndex,
2409 scheduling_session_index: SessionIndex,
2410 persisted_validation_data_hash: Hash,
2411 pov_hash: Hash,
2412 erasure_root: Hash,
2413 para_head: Hash,
2414 validation_code_hash: ValidationCodeHash,
2415 scheduling_parent: H,
2416 ) -> Self {
2417 Self {
2418 para_id,
2419 relay_parent,
2420 version: 1,
2421 core_index: core_index.0 as u16,
2422 session_index,
2423 scheduling_session_offset: scheduling_session_index
2424 .saturating_sub(session_index)
2425 .try_into()
2426 .expect("scheduling session offset should fit in u8"),
2427 reserved1: [0; 24],
2428 persisted_validation_data_hash,
2429 pov_hash,
2430 erasure_root,
2431 scheduling_parent,
2432 reserved2: [0; 32],
2433 para_head,
2434 validation_code_hash,
2435 }
2436 }
2437
2438 pub fn new_v1(
2441 para_id: Id,
2442 relay_parent: H,
2443 persisted_validation_data_hash: Hash,
2444 pov_hash: Hash,
2445 erasure_root: Hash,
2446 para_head: Hash,
2447 validation_code_hash: ValidationCodeHash,
2448 ) -> Self
2449 where
2450 H: Default,
2451 {
2452 Self {
2453 para_id,
2454 relay_parent,
2455 version: 0,
2456 core_index: 0,
2457 session_index: 0,
2458 scheduling_session_offset: 0,
2459 reserved1: [1u8; 24],
2460 persisted_validation_data_hash,
2461 pov_hash,
2462 erasure_root,
2463 scheduling_parent: H::default(),
2464 reserved2: [1u8; 32],
2465 para_head,
2466 validation_code_hash,
2467 }
2468 }
2469
2470 #[cfg(feature = "test")]
2471 #[doc(hidden)]
2472 pub fn new_from_raw(
2473 para_id: Id,
2474 relay_parent: H,
2475 version: u8,
2476 core_index: u16,
2477 session_index: SessionIndex,
2478 scheduling_session_offset: u8,
2479 reserved1: [u8; 24],
2480 persisted_validation_data_hash: Hash,
2481 pov_hash: Hash,
2482 erasure_root: Hash,
2483 scheduling_parent: H,
2484 reserved2: [u8; 32],
2485 para_head: Hash,
2486 validation_code_hash: ValidationCodeHash,
2487 ) -> Self {
2488 Self {
2489 para_id,
2490 relay_parent,
2491 version,
2492 core_index,
2493 session_index,
2494 scheduling_session_offset,
2495 reserved1,
2496 persisted_validation_data_hash,
2497 pov_hash,
2498 erasure_root,
2499 scheduling_parent,
2500 reserved2,
2501 para_head,
2502 validation_code_hash,
2503 }
2504 }
2505}
2506
2507#[cfg(feature = "test")]
2509pub trait MutateDescriptorV2<H> {
2510 fn set_relay_parent(&mut self, relay_parent: H);
2512 fn set_para_id(&mut self, para_id: Id);
2514 fn set_pov_hash(&mut self, pov_hash: Hash);
2516 fn set_version(&mut self, version: u8);
2518 fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash);
2520 fn set_validation_code_hash(&mut self, validation_code_hash: ValidationCodeHash);
2522 fn set_erasure_root(&mut self, erasure_root: Hash);
2524 fn set_para_head(&mut self, para_head: Hash);
2526 fn set_core_index(&mut self, core_index: CoreIndex);
2528 fn set_session_index(&mut self, session_index: SessionIndex);
2530 fn set_reserved2(&mut self, reserved2: [u8; 32]);
2532 fn set_scheduling_parent(&mut self, scheduling_parent: H);
2534 fn set_scheduling_session_offset(&mut self, offset: u8);
2536}
2537
2538#[cfg(feature = "test")]
2539impl<H> MutateDescriptorV2<H> for CandidateDescriptorV2<H> {
2540 fn set_para_id(&mut self, para_id: Id) {
2541 self.para_id = para_id;
2542 }
2543
2544 fn set_relay_parent(&mut self, relay_parent: H) {
2545 self.relay_parent = relay_parent;
2546 }
2547
2548 fn set_pov_hash(&mut self, pov_hash: Hash) {
2549 self.pov_hash = pov_hash;
2550 }
2551
2552 fn set_version(&mut self, version: u8) {
2553 self.version = version;
2554 }
2555
2556 fn set_core_index(&mut self, core_index: CoreIndex) {
2557 self.core_index = core_index.0 as u16;
2558 }
2559
2560 fn set_session_index(&mut self, session_index: SessionIndex) {
2561 self.session_index = session_index;
2562 }
2563
2564 fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash) {
2565 self.persisted_validation_data_hash = persisted_validation_data_hash;
2566 }
2567
2568 fn set_validation_code_hash(&mut self, validation_code_hash: ValidationCodeHash) {
2569 self.validation_code_hash = validation_code_hash;
2570 }
2571
2572 fn set_erasure_root(&mut self, erasure_root: Hash) {
2573 self.erasure_root = erasure_root;
2574 }
2575
2576 fn set_para_head(&mut self, para_head: Hash) {
2577 self.para_head = para_head;
2578 }
2579
2580 fn set_reserved2(&mut self, reserved2: [u8; 32]) {
2581 self.reserved2 = reserved2;
2582 }
2583
2584 fn set_scheduling_parent(&mut self, scheduling_parent: H) {
2585 self.scheduling_parent = scheduling_parent;
2586 }
2587
2588 fn set_scheduling_session_offset(&mut self, offset: u8) {
2589 self.scheduling_session_offset = offset;
2590 }
2591}
2592
2593#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, Debug)]
2595pub struct CandidateReceiptV2<H = Hash> {
2596 pub descriptor: CandidateDescriptorV2<H>,
2598 pub commitments_hash: Hash,
2600}
2601
2602#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, TypeInfo, Debug)]
2604pub struct CommittedCandidateReceiptV2<H = Hash> {
2605 pub descriptor: CandidateDescriptorV2<H>,
2607 pub commitments: CandidateCommitments,
2609}
2610
2611#[derive(Clone, Encode, Decode, TypeInfo, Debug)]
2613#[cfg_attr(feature = "std", derive(PartialEq))]
2614pub enum CandidateEvent<H = Hash> {
2615 #[codec(index = 0)]
2618 CandidateBacked(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
2619 #[codec(index = 1)]
2623 CandidateIncluded(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
2624 #[codec(index = 2)]
2627 CandidateTimedOut(CandidateReceiptV2<H>, HeadData, CoreIndex),
2628}
2629
2630impl<H> CandidateReceiptV2<H> {
2631 pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
2633 &self.descriptor
2634 }
2635
2636 pub fn hash(&self) -> CandidateHash
2638 where
2639 H: Encode,
2640 {
2641 CandidateHash(BlakeTwo256::hash_of(self))
2642 }
2643}
2644
2645impl<H: Clone> CommittedCandidateReceiptV2<H> {
2646 pub fn to_plain(&self) -> CandidateReceiptV2<H> {
2648 CandidateReceiptV2 {
2649 descriptor: self.descriptor.clone(),
2650 commitments_hash: self.commitments.hash(),
2651 }
2652 }
2653
2654 pub fn hash(&self) -> CandidateHash
2659 where
2660 H: Encode,
2661 {
2662 self.to_plain().hash()
2663 }
2664
2665 pub fn corresponds_to(&self, receipt: &CandidateReceiptV2<H>) -> bool
2667 where
2668 H: PartialEq,
2669 {
2670 receipt.descriptor == self.descriptor && receipt.commitments_hash == self.commitments.hash()
2671 }
2672}
2673
2674impl PartialOrd for CommittedCandidateReceiptV2 {
2675 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2676 Some(self.cmp(other))
2677 }
2678}
2679
2680impl Ord for CommittedCandidateReceiptV2 {
2681 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2682 self.descriptor
2683 .para_id
2684 .cmp(&other.descriptor.para_id)
2685 .then_with(|| self.commitments.head_data.cmp(&other.commitments.head_data))
2686 }
2687}
2688
2689#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug, Copy)]
2692pub struct CoreSelector(pub u8);
2693
2694impl From<u8> for CoreSelector {
2695 fn from(value: u8) -> Self {
2696 Self(value)
2697 }
2698}
2699
2700#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug, Copy)]
2702pub struct ClaimQueueOffset(pub u8);
2703
2704impl From<u8> for ClaimQueueOffset {
2705 fn from(value: u8) -> Self {
2706 Self(value)
2707 }
2708}
2709
2710#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug)]
2712pub enum UMPSignal {
2713 SelectCore(CoreSelector, ClaimQueueOffset),
2717 ApprovedPeer(ApprovedPeerId),
2719}
2720
2721pub const DEFAULT_CLAIM_QUEUE_OFFSET: u8 = 0;
2724
2725pub type ApprovedPeerId = BoundedVec<u8, ConstU32<64>>;
2729
2730#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug, Default)]
2731pub struct CandidateUMPSignals {
2733 pub(super) select_core: Option<(CoreSelector, ClaimQueueOffset)>,
2734 pub(super) approved_peer: Option<ApprovedPeerId>,
2735}
2736
2737impl CandidateUMPSignals {
2738 pub fn core_selector(&self) -> Option<(CoreSelector, ClaimQueueOffset)> {
2740 self.select_core
2741 }
2742
2743 pub fn approved_peer(&self) -> Option<&ApprovedPeerId> {
2745 self.approved_peer.as_ref()
2746 }
2747
2748 pub fn is_empty(&self) -> bool {
2750 self.select_core.is_none() && self.approved_peer.is_none()
2751 }
2752
2753 fn try_decode_signal(
2754 &mut self,
2755 buffer: &mut impl codec::Input,
2756 ) -> Result<(), CommittedCandidateReceiptError> {
2757 match UMPSignal::decode(buffer)
2758 .map_err(|_| CommittedCandidateReceiptError::UmpSignalDecode)?
2759 {
2760 UMPSignal::ApprovedPeer(approved_peer_id) if self.approved_peer.is_none() => {
2761 self.approved_peer = Some(approved_peer_id);
2762 },
2763 UMPSignal::SelectCore(core_selector, cq_offset) if self.select_core.is_none() => {
2764 self.select_core = Some((core_selector, cq_offset));
2765 },
2766 _ => {
2767 return Err(CommittedCandidateReceiptError::DuplicateUMPSignal);
2769 },
2770 };
2771
2772 Ok(())
2773 }
2774
2775 #[cfg(feature = "test")]
2776 #[doc(hidden)]
2777 pub fn dummy(
2778 select_core: Option<(CoreSelector, ClaimQueueOffset)>,
2779 approved_peer: Option<ApprovedPeerId>,
2780 ) -> Self {
2781 Self { select_core, approved_peer }
2782 }
2783}
2784
2785pub const UMP_SEPARATOR: Vec<u8> = vec![];
2787
2788pub fn skip_ump_signals<'a>(
2790 upward_messages: impl Iterator<Item = &'a Vec<u8>>,
2791) -> impl Iterator<Item = &'a Vec<u8>> {
2792 upward_messages.take_while(|message| *message != &UMP_SEPARATOR)
2793}
2794
2795impl CandidateCommitments {
2796 pub fn ump_signals(&self) -> Result<CandidateUMPSignals, CommittedCandidateReceiptError> {
2799 let mut res = CandidateUMPSignals::default();
2800
2801 let mut signals_iter =
2802 self.upward_messages.iter().skip_while(|message| *message != &UMP_SEPARATOR);
2803
2804 if signals_iter.next().is_none() {
2805 return Ok(res);
2807 }
2808
2809 let Some(first_signal) = signals_iter.next() else { return Ok(res) };
2811 res.try_decode_signal(&mut first_signal.as_slice())?;
2812
2813 let Some(second_signal) = signals_iter.next() else { return Ok(res) };
2815 res.try_decode_signal(&mut second_signal.as_slice())?;
2816
2817 if signals_iter.next().is_some() {
2819 return Err(CommittedCandidateReceiptError::TooManyUMPSignals);
2820 }
2821
2822 Ok(res)
2823 }
2824}
2825
2826#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug)]
2828#[cfg_attr(feature = "std", derive(thiserror::Error))]
2829pub enum CommittedCandidateReceiptError {
2830 #[cfg_attr(feature = "std", error("The specified core index is invalid"))]
2832 InvalidCoreIndex,
2833 #[cfg_attr(
2835 feature = "std",
2836 error("The core index in commitments ({commitments:?}) doesn't match the one in descriptor ({descriptor:?})")
2837 )]
2838 CoreIndexMismatch {
2839 descriptor: CoreIndex,
2841 commitments: CoreIndex,
2843 },
2844 #[cfg_attr(feature = "std", error("The core selector or claim queue offset is invalid"))]
2846 InvalidSelectedCore,
2847 #[cfg_attr(feature = "std", error("Could not decode UMP signal"))]
2848 UmpSignalDecode,
2850 #[cfg_attr(
2852 feature = "std",
2853 error("The parachain is not assigned to any core at specified claim queue offset")
2854 )]
2855 NoAssignment,
2856 #[cfg_attr(feature = "std", error("Unknown internal version"))]
2858 UnknownVersion(u8),
2859 #[cfg_attr(feature = "std", error("Too many UMP signals"))]
2861 TooManyUMPSignals,
2862 #[cfg_attr(feature = "std", error("Duplicate UMP signal"))]
2864 DuplicateUMPSignal,
2865 #[cfg_attr(feature = "std", error("Version 1 receipt does not support ump signals"))]
2868 UMPSignalWithV1Descriptor,
2869 #[cfg_attr(feature = "std", error("Version 3 receipt requires ump signals"))]
2882 NoUMPSignalWithV3Descriptor,
2883}
2884
2885impl<H: Copy + AsRef<[u8]>> CommittedCandidateReceiptV2<H> {
2886 pub fn parse_ump_signals(
2899 &self,
2900 cores_per_para: &TransposedClaimQueue,
2901 ) -> Result<CandidateUMPSignals, CommittedCandidateReceiptError> {
2902 let signals = self.commitments.ump_signals()?;
2903
2904 match self.descriptor.version() {
2905 CandidateDescriptorVersion::V1 => {
2906 if !signals.is_empty() {
2909 return Err(CommittedCandidateReceiptError::UMPSignalWithV1Descriptor);
2910 } else {
2911 return Ok(CandidateUMPSignals::default());
2913 }
2914 },
2915 CandidateDescriptorVersion::V2 => {},
2916 CandidateDescriptorVersion::Unknown => {
2917 return Err(CommittedCandidateReceiptError::UnknownVersion(self.descriptor.version))
2918 },
2919 _ if signals.is_empty() => {
2920 return Err(CommittedCandidateReceiptError::NoUMPSignalWithV3Descriptor);
2922 },
2923 _ => {},
2924 }
2925
2926 let (maybe_core_index_selector, cq_offset) = signals
2928 .core_selector()
2929 .map(|(selector, offset)| (Some(selector), offset))
2930 .unwrap_or_else(|| (None, ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET)));
2931
2932 self.check_core_index(cores_per_para, maybe_core_index_selector, cq_offset)?;
2933
2934 Ok(signals)
2937 }
2938
2939 fn check_core_index(
2943 &self,
2944 cores_per_para: &TransposedClaimQueue,
2945 maybe_core_index_selector: Option<CoreSelector>,
2946 cq_offset: ClaimQueueOffset,
2947 ) -> Result<(), CommittedCandidateReceiptError> {
2948 let assigned_cores = cores_per_para
2949 .get(&self.descriptor.para_id())
2950 .ok_or(CommittedCandidateReceiptError::NoAssignment)?
2951 .get(&cq_offset.0)
2952 .ok_or(CommittedCandidateReceiptError::NoAssignment)?;
2953
2954 if assigned_cores.is_empty() {
2955 return Err(CommittedCandidateReceiptError::NoAssignment);
2956 }
2957
2958 let descriptor_core_index = CoreIndex(self.descriptor.core_index as u32);
2959
2960 let core_index_selector = if let Some(core_index_selector) = maybe_core_index_selector {
2961 core_index_selector
2963 } else if assigned_cores.len() > 1 {
2964 if !assigned_cores.contains(&descriptor_core_index) {
2966 return Err(CommittedCandidateReceiptError::InvalidCoreIndex);
2968 } else {
2969 return Ok(());
2972 }
2973 } else {
2974 CoreSelector(0)
2976 };
2977
2978 let core_index = assigned_cores
2979 .iter()
2980 .nth(core_index_selector.0 as usize % assigned_cores.len())
2981 .ok_or(CommittedCandidateReceiptError::InvalidSelectedCore)
2982 .copied()?;
2983
2984 if core_index != descriptor_core_index {
2985 return Err(CommittedCandidateReceiptError::CoreIndexMismatch {
2986 descriptor: descriptor_core_index,
2987 commitments: core_index,
2988 });
2989 }
2990
2991 Ok(())
2992 }
2993}
2994
2995#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug, TypeInfo)]
2997pub struct BackedCandidate<H = Hash> {
2998 candidate: CommittedCandidateReceiptV2<H>,
3000 validity_votes: Vec<ValidityAttestation>,
3002 validator_indices: BitVec<u8, bitvec::order::Lsb0>,
3006}
3007
3008#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Debug, TypeInfo)]
3010pub struct InherentData<HDR: HeaderT = Header> {
3011 pub bitfields: UncheckedSignedAvailabilityBitfields,
3013 pub backed_candidates: Vec<BackedCandidate<HDR::Hash>>,
3015 pub disputes: MultiDisputeStatementSet,
3017 pub parent_header: HDR,
3019}
3020
3021impl<H> BackedCandidate<H> {
3022 pub fn new(
3024 candidate: CommittedCandidateReceiptV2<H>,
3025 validity_votes: Vec<ValidityAttestation>,
3026 validator_indices: BitVec<u8, bitvec::order::Lsb0>,
3027 core_index: CoreIndex,
3028 ) -> Self {
3029 let mut instance = Self { candidate, validity_votes, validator_indices };
3030 instance.inject_core_index(core_index);
3031 instance
3032 }
3033
3034 pub fn candidate(&self) -> &CommittedCandidateReceiptV2<H> {
3036 &self.candidate
3037 }
3038
3039 #[cfg(feature = "test")]
3042 pub fn candidate_mut(&mut self) -> &mut CommittedCandidateReceiptV2<H> {
3043 &mut self.candidate
3044 }
3045 pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
3047 &self.candidate.descriptor
3048 }
3049
3050 #[cfg(feature = "test")]
3052 pub fn descriptor_mut(&mut self) -> &mut CandidateDescriptorV2<H> {
3053 &mut self.candidate.descriptor
3054 }
3055
3056 pub fn validity_votes(&self) -> &[ValidityAttestation] {
3058 &self.validity_votes
3059 }
3060
3061 pub fn validity_votes_mut(&mut self) -> &mut Vec<ValidityAttestation> {
3063 &mut self.validity_votes
3064 }
3065
3066 pub fn hash(&self) -> CandidateHash
3068 where
3069 H: Clone + Encode,
3070 {
3071 self.candidate.to_plain().hash()
3072 }
3073
3074 pub fn receipt(&self) -> CandidateReceiptV2<H>
3076 where
3077 H: Clone,
3078 {
3079 self.candidate.to_plain()
3080 }
3081
3082 #[cfg(feature = "test")]
3084 pub fn raw_validator_indices(&self) -> BitVec<u8, bitvec::order::Lsb0> {
3085 self.validator_indices.clone()
3086 }
3087
3088 pub fn validator_indices_and_core_index(
3090 &self,
3091 ) -> (&BitSlice<u8, bitvec::order::Lsb0>, Option<CoreIndex>) {
3092 let core_idx_offset = self.validator_indices.len().saturating_sub(8);
3094 if core_idx_offset > 0 {
3095 let (validator_indices_slice, core_idx_slice) =
3096 self.validator_indices.split_at(core_idx_offset);
3097 return (validator_indices_slice, Some(CoreIndex(core_idx_slice.load::<u8>() as u32)));
3098 }
3099
3100 (&self.validator_indices, None)
3101 }
3102
3103 fn inject_core_index(&mut self, core_index: CoreIndex) {
3105 let core_index_to_inject: BitVec<u8, bitvec::order::Lsb0> =
3106 BitVec::from_vec(vec![core_index.0 as u8]);
3107 self.validator_indices.extend(core_index_to_inject);
3108 }
3109
3110 pub fn set_validator_indices_and_core_index(
3112 &mut self,
3113 new_indices: BitVec<u8, bitvec::order::Lsb0>,
3114 maybe_core_index: Option<CoreIndex>,
3115 ) {
3116 self.validator_indices = new_indices;
3117
3118 if let Some(core_index) = maybe_core_index {
3119 self.inject_core_index(core_index);
3120 }
3121 }
3122}
3123
3124#[derive(Clone, Encode, Decode, Debug, TypeInfo)]
3126#[cfg_attr(feature = "std", derive(PartialEq))]
3127pub struct ScrapedOnChainVotes<H: Encode + Decode = Hash> {
3128 pub session: SessionIndex,
3130 pub backing_validators_per_candidate:
3133 Vec<(CandidateReceiptV2<H>, Vec<(ValidatorIndex, ValidityAttestation)>)>,
3134 pub disputes: MultiDisputeStatementSet,
3138}
3139
3140#[derive(Clone, Encode, Decode, TypeInfo, Debug)]
3142#[cfg_attr(feature = "std", derive(PartialEq))]
3143pub struct OccupiedCore<H = Hash, N = BlockNumber> {
3144 pub next_up_on_available: Option<ScheduledCore>,
3148 pub occupied_since: N,
3150 pub time_out_at: N,
3152 pub next_up_on_time_out: Option<ScheduledCore>,
3156 pub availability: BitVec<u8, bitvec::order::Lsb0>,
3160 pub group_responsible: GroupIndex,
3162 pub candidate_hash: CandidateHash,
3164 pub candidate_descriptor: CandidateDescriptorV2<H>,
3166}
3167
3168impl<H, N> OccupiedCore<H, N> {
3169 pub fn para_id(&self) -> Id {
3171 self.candidate_descriptor.para_id
3172 }
3173}
3174
3175#[derive(Clone, Encode, Decode, TypeInfo, Debug)]
3177#[cfg_attr(feature = "std", derive(PartialEq))]
3178pub enum CoreState<H = Hash, N = BlockNumber> {
3179 #[codec(index = 0)]
3181 Occupied(OccupiedCore<H, N>),
3182 #[codec(index = 1)]
3188 Scheduled(ScheduledCore),
3189 #[codec(index = 2)]
3193 Free,
3194}
3195
3196impl<N> CoreState<N> {
3197 #[deprecated(
3202 note = "`para_id` will be removed. Use `ClaimQueue` to query the scheduled `para_id` instead."
3203 )]
3204 pub fn para_id(&self) -> Option<Id> {
3205 match self {
3206 Self::Occupied(ref core) => core.next_up_on_available.as_ref().map(|n| n.para_id),
3207 Self::Scheduled(core) => Some(core.para_id),
3208 Self::Free => None,
3209 }
3210 }
3211
3212 pub fn is_occupied(&self) -> bool {
3214 matches!(self, Self::Occupied(_))
3215 }
3216}
3217
3218pub type TransposedClaimQueue = BTreeMap<ParaId, BTreeMap<u8, BTreeSet<CoreIndex>>>;
3220
3221pub fn transpose_claim_queue(
3224 claim_queue: BTreeMap<CoreIndex, VecDeque<Id>>,
3225) -> TransposedClaimQueue {
3226 let mut per_para_claim_queue = BTreeMap::new();
3227
3228 for (core, paras) in claim_queue {
3229 for (depth, para) in paras.into_iter().enumerate() {
3231 let depths: &mut BTreeMap<u8, BTreeSet<CoreIndex>> =
3232 per_para_claim_queue.entry(para).or_insert_with(|| Default::default());
3233
3234 depths.entry(depth as u8).or_default().insert(core);
3235 }
3236 }
3237
3238 per_para_claim_queue
3239}
3240
3241#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, DecodeWithMemTracking, TypeInfo, Debug)]
3244pub enum DisputeOffenceKind {
3245 #[codec(index = 0)]
3248 ForInvalidBacked,
3249 #[codec(index = 1)]
3252 AgainstValid,
3253 #[codec(index = 2)]
3256 ForInvalidApproved,
3257}
3258
3259impl From<super::v9::slashing::SlashingOffenceKind> for DisputeOffenceKind {
3263 fn from(value: super::v9::slashing::SlashingOffenceKind) -> Self {
3264 match value {
3265 super::v9::slashing::SlashingOffenceKind::ForInvalid => Self::ForInvalidBacked,
3266 super::v9::slashing::SlashingOffenceKind::AgainstValid => Self::AgainstValid,
3267 }
3268 }
3269}
3270
3271impl TryFrom<DisputeOffenceKind> for super::v9::slashing::SlashingOffenceKind {
3273 type Error = ();
3274
3275 fn try_from(value: DisputeOffenceKind) -> Result<Self, Self::Error> {
3276 match value {
3277 DisputeOffenceKind::ForInvalidBacked => Ok(Self::ForInvalid),
3278 DisputeOffenceKind::AgainstValid => Ok(Self::AgainstValid),
3279 DisputeOffenceKind::ForInvalidApproved => Err(()),
3280 }
3281 }
3282}
3283
3284#[cfg(test)]
3285pub mod tests {
3287 use super::*;
3288
3289 #[test]
3290 fn group_rotation_info_calculations() {
3291 let info =
3292 GroupRotationInfo { session_start_block: 10u32, now: 15, group_rotation_frequency: 5 };
3293
3294 assert_eq!(info.next_rotation_at(), 20);
3295 assert_eq!(info.last_rotation_at(), 15);
3296 }
3297
3298 #[test]
3299 fn group_for_core_is_core_for_group() {
3300 for cores in 1..=256 {
3301 for rotations in 0..(cores * 2) {
3302 let info = GroupRotationInfo {
3303 session_start_block: 0u32,
3304 now: rotations,
3305 group_rotation_frequency: 1,
3306 };
3307
3308 for core in 0..cores {
3309 let group = info.group_for_core(CoreIndex(core), cores as usize);
3310 assert_eq!(info.core_for_group(group, cores as usize).0, core);
3311 }
3312 }
3313 }
3314 }
3315
3316 #[test]
3317 fn test_byzantine_threshold() {
3318 assert_eq!(byzantine_threshold(0), 0);
3319 assert_eq!(byzantine_threshold(1), 0);
3320 assert_eq!(byzantine_threshold(2), 0);
3321 assert_eq!(byzantine_threshold(3), 0);
3322 assert_eq!(byzantine_threshold(4), 1);
3323 assert_eq!(byzantine_threshold(5), 1);
3324 assert_eq!(byzantine_threshold(6), 1);
3325 assert_eq!(byzantine_threshold(7), 2);
3326 }
3327
3328 #[test]
3329 fn test_supermajority_threshold() {
3330 assert_eq!(supermajority_threshold(0), 0);
3331 assert_eq!(supermajority_threshold(1), 1);
3332 assert_eq!(supermajority_threshold(2), 2);
3333 assert_eq!(supermajority_threshold(3), 3);
3334 assert_eq!(supermajority_threshold(4), 3);
3335 assert_eq!(supermajority_threshold(5), 4);
3336 assert_eq!(supermajority_threshold(6), 5);
3337 assert_eq!(supermajority_threshold(7), 5);
3338 }
3339
3340 #[test]
3341 fn balance_bigger_than_usize() {
3342 let zero_b: Balance = 0;
3343 let zero_u: usize = 0;
3344
3345 assert!(zero_b.leading_zeros() >= zero_u.leading_zeros());
3346 }
3347
3348 fn make_v2_descriptor() -> CandidateDescriptorV2 {
3349 CandidateDescriptorV2::new(
3350 Id::from(1u32),
3351 Hash::repeat_byte(1),
3352 CoreIndex(0),
3353 1,
3354 Hash::repeat_byte(2),
3355 Hash::repeat_byte(3),
3356 Hash::repeat_byte(4),
3357 Hash::repeat_byte(5),
3358 ValidationCodeHash::from(Hash::repeat_byte(6)),
3359 )
3360 }
3361
3362 fn make_v3_descriptor() -> CandidateDescriptorV2 {
3363 CandidateDescriptorV2::new_v3(
3364 Id::from(1u32),
3365 Hash::repeat_byte(1),
3366 CoreIndex(0),
3367 1, 1, Hash::repeat_byte(2),
3370 Hash::repeat_byte(3),
3371 Hash::repeat_byte(4),
3372 Hash::repeat_byte(5),
3373 ValidationCodeHash::from(Hash::repeat_byte(6)),
3374 Hash::repeat_byte(7), )
3376 }
3377
3378 #[test]
3379 fn check_version_acceptance_v1_consistent() {
3380 let mut desc = make_v2_descriptor();
3383 desc.reserved1[0] = 0xFF;
3386
3387 assert_eq!(desc.version(), CandidateDescriptorVersion::V1);
3388 assert_eq!(desc.version_old_rules(), CandidateDescriptorVersion::V1);
3389 assert!(desc.check_version_consistency());
3390
3391 assert!(desc.check_version_acceptance(false).is_ok());
3392 assert!(desc.check_version_acceptance(true).is_ok());
3393 }
3394
3395 #[test]
3396 fn check_version_acceptance_v2_consistent() {
3397 let desc = make_v2_descriptor();
3399
3400 assert_eq!(desc.version(), CandidateDescriptorVersion::V2);
3401 assert_eq!(desc.version_old_rules(), CandidateDescriptorVersion::V2);
3402 assert!(desc.check_version_consistency());
3403
3404 assert!(desc.check_version_acceptance(false).is_ok());
3405 assert!(desc.check_version_acceptance(true).is_ok());
3406 }
3407
3408 #[test]
3409 fn check_version_acceptance_v3_when_enabled() {
3410 let desc = make_v3_descriptor();
3412
3413 assert_eq!(desc.version(), CandidateDescriptorVersion::V3);
3414 assert_eq!(desc.version_old_rules(), CandidateDescriptorVersion::V1);
3415 assert!(!desc.check_version_consistency());
3416
3417 assert!(desc.check_version_acceptance(true).is_ok());
3418 }
3419
3420 #[test]
3421 fn check_version_acceptance_v3_when_disabled() {
3422 let desc = make_v3_descriptor();
3426
3427 assert_eq!(desc.version(), CandidateDescriptorVersion::V3);
3428 assert_eq!(
3429 desc.check_version_acceptance(false),
3430 Err(CandidateDescriptorVersionCheckError::Inconsistency)
3431 );
3432 }
3433
3434 #[test]
3435 fn check_version_acceptance_ambiguous_rejected() {
3436 let mut desc = make_v2_descriptor();
3439 desc.reserved1[16] = 0xFF; assert_eq!(desc.version(), CandidateDescriptorVersion::V2);
3442 assert_eq!(desc.version_old_rules(), CandidateDescriptorVersion::V1);
3443 assert!(!desc.check_version_consistency());
3444
3445 assert_eq!(
3447 desc.check_version_acceptance(false),
3448 Err(CandidateDescriptorVersionCheckError::Inconsistency)
3449 );
3450 assert_eq!(
3451 desc.check_version_acceptance(true),
3452 Err(CandidateDescriptorVersionCheckError::Inconsistency)
3453 );
3454 }
3455
3456 #[test]
3457 fn check_version_consistency_v3_expected_disagreement() {
3458 let desc = make_v3_descriptor();
3461
3462 assert_eq!(desc.version(), CandidateDescriptorVersion::V3);
3463 assert_eq!(desc.version_old_rules(), CandidateDescriptorVersion::V1);
3464 assert!(!desc.check_version_consistency());
3465 assert!(desc.check_version_acceptance(true).is_ok());
3467 }
3468
3469 #[test]
3470 fn v3_feature_activation_changes_descriptor_interpretation() {
3471 let desc = make_v3_descriptor();
3472
3473 assert_eq!(desc.version(), CandidateDescriptorVersion::V3);
3475 assert_eq!(desc.version_old_rules(), CandidateDescriptorVersion::V1);
3476
3477 assert_eq!(desc.version_for_candidate_validation(false), CandidateDescriptorVersion::V1,);
3479 assert_eq!(
3480 desc.scheduling_parent_for_candidate_validation(false),
3481 Hash::repeat_byte(1), );
3483 assert_eq!(
3484 desc.scheduling_session_for_candidate_validation(false),
3485 None,
3486 "V1 has no embedded session — must be fetched from runtime",
3487 );
3488
3489 assert_eq!(desc.version_for_candidate_validation(true), CandidateDescriptorVersion::V3,);
3491 assert_eq!(
3492 desc.scheduling_parent_for_candidate_validation(true),
3493 Hash::repeat_byte(7), );
3495 assert_eq!(
3496 desc.scheduling_session_for_candidate_validation(true),
3497 Some(1), );
3499 }
3500
3501 #[test]
3502 fn check_version_acceptance_ambiguous_scheduling_parent_nonzero() {
3503 let mut desc = make_v2_descriptor();
3507 desc.scheduling_parent = Hash::repeat_byte(0xAB);
3508
3509 assert_eq!(desc.version(), CandidateDescriptorVersion::V2);
3510 assert_eq!(desc.version_old_rules(), CandidateDescriptorVersion::V1);
3511 assert!(!desc.check_version_consistency());
3512
3513 assert_eq!(
3514 desc.check_version_acceptance(false),
3515 Err(CandidateDescriptorVersionCheckError::Inconsistency)
3516 );
3517 assert_eq!(
3518 desc.check_version_acceptance(true),
3519 Err(CandidateDescriptorVersionCheckError::Inconsistency)
3520 );
3521 }
3522
3523 const APPROVAL_MULTIPLE_CANDIDATES_INDEX: u8 = 4;
3525
3526 fn coalesced_candidate_hashes(n: usize) -> Vec<CandidateHash> {
3527 (0..n).map(|i| CandidateHash(Hash::repeat_byte(i as u8))).collect()
3528 }
3529
3530 fn approval_multiple_candidates_vec_bytes(hashes: &[CandidateHash]) -> Vec<u8> {
3534 let mut bytes = vec![APPROVAL_MULTIPLE_CANDIDATES_INDEX];
3535 bytes.extend(hashes.encode());
3536 bytes
3537 }
3538
3539 #[test]
3540 fn approval_multiple_candidates_bounded_vec_encodes_like_vec() {
3541 let limit = MAX_COALESCE_APPROVALS as usize;
3542 for n in [0, 1, 2, 8, limit - 1, limit] {
3543 let hashes = coalesced_candidate_hashes(n);
3544 let bounded: BoundedVec<CandidateHash, ConstU32<{ MAX_COALESCE_APPROVALS }>> =
3545 hashes.clone().try_into().expect("n <= MAX_COALESCE_APPROVALS; qed");
3546
3547 assert_eq!(hashes.encode(), bounded.encode(), "inner encoding differs for n = {n}");
3549
3550 assert_eq!(
3552 ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(bounded).encode(),
3553 approval_multiple_candidates_vec_bytes(&hashes),
3554 "enum encoding differs from the plain Vec encoding for n = {n}",
3555 );
3556 }
3557 }
3558
3559 #[test]
3560 fn approval_multiple_candidates_decodes_vec_encoding() {
3561 let limit = MAX_COALESCE_APPROVALS as usize;
3562 for n in [0, 1, limit - 1, limit] {
3563 let hashes = coalesced_candidate_hashes(n);
3564 let vec_bytes = approval_multiple_candidates_vec_bytes(&hashes);
3565
3566 let decoded = ValidDisputeStatementKind::decode(&mut &vec_bytes[..])
3568 .expect("a Vec encoding within the limit must decode; qed");
3569 let expected = ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(
3570 hashes.clone().try_into().expect("n <= MAX_COALESCE_APPROVALS; qed"),
3571 );
3572 assert_eq!(decoded, expected, "decode mismatch for n = {n}");
3573
3574 assert_eq!(
3576 ValidDisputeStatementKind::decode(&mut &expected.encode()[..]).unwrap(),
3577 expected,
3578 );
3579 }
3580 }
3581
3582 #[test]
3583 fn approval_multiple_candidates_rejects_above_limit() {
3584 let limit = MAX_COALESCE_APPROVALS as usize;
3585
3586 let too_many = coalesced_candidate_hashes(limit + 1);
3588 assert!(
3589 BoundedVec::<CandidateHash, ConstU32<{ MAX_COALESCE_APPROVALS }>>::decode(
3590 &mut &too_many.encode()[..]
3591 )
3592 .is_err(),
3593 "BoundedVec must reject more than MAX_COALESCE_APPROVALS elements",
3594 );
3595
3596 let vec_bytes = approval_multiple_candidates_vec_bytes(&too_many);
3598 assert!(
3599 ValidDisputeStatementKind::decode(&mut &vec_bytes[..]).is_err(),
3600 "enum must reject more than MAX_COALESCE_APPROVALS coalesced candidates",
3601 );
3602
3603 let at_limit = approval_multiple_candidates_vec_bytes(&coalesced_candidate_hashes(limit));
3605 assert!(ValidDisputeStatementKind::decode(&mut &at_limit[..]).is_ok());
3606 }
3607
3608 #[test]
3609 fn approval_multiple_candidates_signing_payload_matches_vec() {
3610 let session = 7;
3611 for n in [1, 2, MAX_COALESCE_APPROVALS as usize] {
3612 let hashes = coalesced_candidate_hashes(n);
3613 let bounded: BoundedVec<CandidateHash, ConstU32<{ MAX_COALESCE_APPROVALS }>> =
3614 hashes.clone().try_into().expect("n <= MAX_COALESCE_APPROVALS; qed");
3615
3616 assert_eq!(
3619 ApprovalVoteMultipleCandidates(&hashes).signing_payload(session),
3620 ApprovalVoteMultipleCandidates(&bounded).signing_payload(session),
3621 "signing payload differs for n = {n}",
3622 );
3623 }
3624 }
3625}