referrerpolicy=no-referrer-when-downgrade

polkadot_primitives_test_helpers/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17#![forbid(unused_crate_dependencies)]
18#![forbid(unused_extern_crates)]
19
20//! A set of primitive constructors, to aid in crafting meaningful testcase while reducing
21//! repetition.
22//!
23//! Note that `dummy_` prefixed values are meant to be fillers, that should not matter, and will
24//! contain randomness based data.
25use codec::{Decode, Encode};
26use polkadot_primitives::{
27	AppVerify, CandidateCommitments, CandidateDescriptorV2, CandidateHash, CandidateReceiptV2,
28	CollatorId, CollatorSignature, CommittedCandidateReceiptV2, CoreIndex, Hash, HashT, HeadData,
29	Id, Id as ParaId, InternalVersion, MutateDescriptorV2, PersistedValidationData, SessionIndex,
30	ValidationCode, ValidationCodeHash, ValidatorId,
31};
32pub use rand;
33use scale_info::TypeInfo;
34use sp_application_crypto::{sr25519, ByteArray};
35use sp_keyring::Sr25519Keyring;
36use sp_runtime::{generic::Digest, traits::BlakeTwo256};
37
38const MAX_POV_SIZE: u32 = 1_000_000;
39
40/// The legacy descriptor of a legacy candidate receipt.
41#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
42pub struct CandidateDescriptor<H = Hash> {
43	/// The ID of the para this is a candidate for.
44	pub para_id: Id,
45	/// The hash of the relay-chain block this is executed in the context of.
46	pub relay_parent: H,
47	/// The collator's sr25519 public key.
48	pub collator: CollatorId,
49	/// The blake2-256 hash of the persisted validation data. This is extra data derived from
50	/// relay-chain state which may vary based on bitfields included before the candidate.
51	/// Thus it cannot be derived entirely from the relay-parent.
52	pub persisted_validation_data_hash: Hash,
53	/// The blake2-256 hash of the PoV.
54	pub pov_hash: Hash,
55	/// The root of a block's erasure encoding Merkle tree.
56	pub erasure_root: Hash,
57	/// Signature on blake2-256 of components of this receipt:
58	/// The parachain index, the relay parent, the validation data hash, and the `pov_hash`.
59	pub signature: CollatorSignature,
60	/// Hash of the para header that is being generated by this candidate.
61	pub para_head: Hash,
62	/// The blake2-256 hash of the validation code bytes.
63	pub validation_code_hash: ValidationCodeHash,
64}
65
66impl<H: AsRef<[u8]>> CandidateDescriptor<H> {
67	/// Check the signature of the collator within this descriptor.
68	pub fn check_collator_signature(&self) -> Result<(), ()> {
69		check_collator_signature(
70			&self.relay_parent,
71			&self.para_id,
72			&self.persisted_validation_data_hash,
73			&self.pov_hash,
74			&self.validation_code_hash,
75			&self.collator,
76			&self.signature,
77		)
78	}
79}
80
81/// A legacy candidate-receipt.
82#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
83pub struct CandidateReceipt<H = Hash> {
84	/// The descriptor of the candidate.
85	pub descriptor: CandidateDescriptor<H>,
86	/// The hash of the encoded commitments made as a result of candidate execution.
87	pub commitments_hash: Hash,
88}
89
90impl<H> CandidateReceipt<H> {
91	/// Get a reference to the candidate descriptor.
92	pub fn descriptor(&self) -> &CandidateDescriptor<H> {
93		&self.descriptor
94	}
95
96	/// Computes the blake2-256 hash of the receipt.
97	pub fn hash(&self) -> CandidateHash
98	where
99		H: Encode,
100	{
101		CandidateHash(BlakeTwo256::hash_of(self))
102	}
103}
104
105impl<H: Copy> From<CandidateReceiptV2<H>> for CandidateReceipt<H> {
106	fn from(value: CandidateReceiptV2<H>) -> Self {
107		Self { descriptor: value.descriptor.into(), commitments_hash: value.commitments_hash }
108	}
109}
110
111impl<H: Copy + AsRef<[u8]>> From<CandidateReceipt<H>> for CandidateReceiptV2<H> {
112	fn from(value: CandidateReceipt<H>) -> Self {
113		Self { descriptor: value.descriptor.into(), commitments_hash: value.commitments_hash }
114	}
115}
116
117/// A legacy candidate-receipt with commitments directly included.
118#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
119pub struct CommittedCandidateReceipt<H = Hash> {
120	/// The descriptor of the candidate.
121	pub descriptor: CandidateDescriptor<H>,
122	/// The commitments of the candidate receipt.
123	pub commitments: CandidateCommitments,
124}
125
126impl<H> CommittedCandidateReceipt<H> {
127	/// Get a reference to the candidate descriptor.
128	pub fn descriptor(&self) -> &CandidateDescriptor<H> {
129		&self.descriptor
130	}
131}
132
133impl<H: Clone> CommittedCandidateReceipt<H> {
134	/// Transforms this into a plain `CandidateReceipt`.
135	pub fn to_plain(&self) -> CandidateReceipt<H> {
136		CandidateReceipt {
137			descriptor: self.descriptor.clone(),
138			commitments_hash: self.commitments.hash(),
139		}
140	}
141
142	/// Computes the hash of the committed candidate receipt.
143	///
144	/// This computes the canonical hash, not the hash of the directly encoded data.
145	/// Thus this is a shortcut for `candidate.to_plain().hash()`.
146	pub fn hash(&self) -> CandidateHash
147	where
148		H: Encode,
149	{
150		self.to_plain().hash()
151	}
152
153	/// Does this committed candidate receipt corresponds to the given [`CandidateReceipt`]?
154	pub fn corresponds_to(&self, receipt: &CandidateReceipt<H>) -> bool
155	where
156		H: PartialEq,
157	{
158		receipt.descriptor == self.descriptor && receipt.commitments_hash == self.commitments.hash()
159	}
160}
161
162impl PartialOrd for CommittedCandidateReceipt {
163	fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
164		Some(self.cmp(other))
165	}
166}
167
168impl Ord for CommittedCandidateReceipt {
169	fn cmp(&self, other: &Self) -> core::cmp::Ordering {
170		// TODO: compare signatures or something more sane
171		// https://github.com/paritytech/polkadot/issues/222
172		self.descriptor()
173			.para_id
174			.cmp(&other.descriptor().para_id)
175			.then_with(|| self.commitments.head_data.cmp(&other.commitments.head_data))
176	}
177}
178
179impl<H: Copy> From<CommittedCandidateReceiptV2<H>> for CommittedCandidateReceipt<H> {
180	fn from(value: CommittedCandidateReceiptV2<H>) -> Self {
181		Self { descriptor: value.descriptor.into(), commitments: value.commitments }
182	}
183}
184
185impl<H: Copy> From<CandidateDescriptorV2<H>> for CandidateDescriptor<H> {
186	fn from(value: CandidateDescriptorV2<H>) -> Self {
187		Self {
188			para_id: value.para_id(),
189			relay_parent: value.relay_parent(),
190			collator: value.rebuild_collator_field_for_tests(),
191			persisted_validation_data_hash: value.persisted_validation_data_hash(),
192			pov_hash: value.pov_hash(),
193			erasure_root: value.erasure_root(),
194			signature: value.rebuild_signature_field_for_tests(),
195			para_head: value.para_head(),
196			validation_code_hash: value.validation_code_hash(),
197		}
198	}
199}
200
201fn clone_into_array<A, T>(slice: &[T]) -> A
202where
203	A: Default + AsMut<[T]>,
204	T: Clone,
205{
206	let mut a = A::default();
207	<A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
208	a
209}
210
211impl<H: Copy + AsRef<[u8]>> From<CandidateDescriptor<H>> for CandidateDescriptorV2<H> {
212	fn from(value: CandidateDescriptor<H>) -> Self {
213		let collator = value.collator.as_slice();
214
215		CandidateDescriptorV2::new_from_raw(
216			value.para_id,
217			value.relay_parent,
218			InternalVersion(collator[0]),
219			u16::from_ne_bytes(clone_into_array(&collator[1..=2])),
220			SessionIndex::from_ne_bytes(clone_into_array(&collator[3..=6])),
221			clone_into_array(&collator[7..]),
222			value.persisted_validation_data_hash,
223			value.pov_hash,
224			value.erasure_root,
225			value.signature.into_inner().0,
226			value.para_head,
227			value.validation_code_hash,
228		)
229	}
230}
231
232impl<H: Copy + AsRef<[u8]>> From<CommittedCandidateReceipt<H>> for CommittedCandidateReceiptV2<H> {
233	fn from(value: CommittedCandidateReceipt<H>) -> Self {
234		Self { descriptor: value.descriptor.into(), commitments: value.commitments }
235	}
236}
237
238/// Get a collator signature payload on a relay-parent, block-data combo.
239pub fn collator_signature_payload<H: AsRef<[u8]>>(
240	relay_parent: &H,
241	para_id: &Id,
242	persisted_validation_data_hash: &Hash,
243	pov_hash: &Hash,
244	validation_code_hash: &ValidationCodeHash,
245) -> [u8; 132] {
246	// 32-byte hash length is protected in a test below.
247	let mut payload = [0u8; 132];
248
249	payload[0..32].copy_from_slice(relay_parent.as_ref());
250	u32::from(*para_id).using_encoded(|s| payload[32..32 + s.len()].copy_from_slice(s));
251	payload[36..68].copy_from_slice(persisted_validation_data_hash.as_ref());
252	payload[68..100].copy_from_slice(pov_hash.as_ref());
253	payload[100..132].copy_from_slice(validation_code_hash.as_ref());
254
255	payload
256}
257
258pub(crate) fn check_collator_signature<H: AsRef<[u8]>>(
259	relay_parent: &H,
260	para_id: &Id,
261	persisted_validation_data_hash: &Hash,
262	pov_hash: &Hash,
263	validation_code_hash: &ValidationCodeHash,
264	collator: &CollatorId,
265	signature: &CollatorSignature,
266) -> Result<(), ()> {
267	let payload = collator_signature_payload(
268		relay_parent,
269		para_id,
270		persisted_validation_data_hash,
271		pov_hash,
272		validation_code_hash,
273	);
274
275	if signature.verify(&payload[..], collator) {
276		Ok(())
277	} else {
278		Err(())
279	}
280}
281
282/// Creates a candidate receipt with filler data.
283pub fn dummy_candidate_receipt<H: AsRef<[u8]>>(relay_parent: H) -> CandidateReceipt<H> {
284	CandidateReceipt::<H> {
285		commitments_hash: dummy_candidate_commitments(dummy_head_data()).hash(),
286		descriptor: dummy_candidate_descriptor(relay_parent),
287	}
288}
289
290/// Creates a v2 candidate receipt with filler data.
291pub fn dummy_candidate_receipt_v2<H: AsRef<[u8]> + Copy>(relay_parent: H) -> CandidateReceiptV2<H> {
292	CandidateReceiptV2::<H> {
293		commitments_hash: dummy_candidate_commitments(dummy_head_data()).hash(),
294		descriptor: dummy_candidate_descriptor_v2(relay_parent),
295	}
296}
297
298/// Creates a committed candidate receipt with filler data.
299pub fn dummy_committed_candidate_receipt<H: AsRef<[u8]>>(
300	relay_parent: H,
301) -> CommittedCandidateReceipt<H> {
302	CommittedCandidateReceipt::<H> {
303		descriptor: dummy_candidate_descriptor::<H>(relay_parent),
304		commitments: dummy_candidate_commitments(dummy_head_data()),
305	}
306}
307
308/// Creates a v2 committed candidate receipt with filler data.
309pub fn dummy_committed_candidate_receipt_v2<H: AsRef<[u8]> + Copy>(
310	relay_parent: H,
311) -> CommittedCandidateReceiptV2<H> {
312	CommittedCandidateReceiptV2 {
313		descriptor: dummy_candidate_descriptor_v2::<H>(relay_parent),
314		commitments: dummy_candidate_commitments(dummy_head_data()),
315	}
316}
317
318/// Create a candidate receipt with a bogus signature and filler data. Optionally set the commitment
319/// hash with the `commitments` arg.
320pub fn dummy_candidate_receipt_bad_sig(
321	relay_parent: Hash,
322	commitments: impl Into<Option<Hash>>,
323) -> CandidateReceipt<Hash> {
324	let commitments_hash = if let Some(commitments) = commitments.into() {
325		commitments
326	} else {
327		dummy_candidate_commitments(dummy_head_data()).hash()
328	};
329	CandidateReceipt::<Hash> {
330		commitments_hash,
331		descriptor: dummy_candidate_descriptor_bad_sig(relay_parent),
332	}
333}
334
335/// Create a candidate receipt with a bogus signature and filler data. Optionally set the commitment
336/// hash with the `commitments` arg.
337pub fn dummy_candidate_receipt_v2_bad_sig(
338	relay_parent: Hash,
339	commitments: impl Into<Option<Hash>>,
340) -> CandidateReceiptV2<Hash> {
341	let commitments_hash = if let Some(commitments) = commitments.into() {
342		commitments
343	} else {
344		dummy_candidate_commitments(dummy_head_data()).hash()
345	};
346	CandidateReceiptV2::<Hash> {
347		commitments_hash,
348		descriptor: dummy_candidate_descriptor_bad_sig(relay_parent).into(),
349	}
350}
351
352/// Create candidate commitments with filler data.
353pub fn dummy_candidate_commitments(head_data: impl Into<Option<HeadData>>) -> CandidateCommitments {
354	CandidateCommitments {
355		head_data: head_data.into().unwrap_or(dummy_head_data()),
356		upward_messages: vec![].try_into().expect("empty vec fits within bounds"),
357		new_validation_code: None,
358		horizontal_messages: vec![].try_into().expect("empty vec fits within bounds"),
359		processed_downward_messages: 0,
360		hrmp_watermark: 0_u32,
361	}
362}
363
364/// Create meaningless dummy hash.
365pub fn dummy_hash() -> Hash {
366	Hash::zero()
367}
368
369/// Create meaningless dummy digest.
370pub fn dummy_digest() -> Digest {
371	Digest::default()
372}
373
374/// Create a candidate descriptor with a bogus signature and filler data.
375pub fn dummy_candidate_descriptor_bad_sig(relay_parent: Hash) -> CandidateDescriptor<Hash> {
376	let zeros = Hash::zero();
377	CandidateDescriptor::<Hash> {
378		para_id: 0.into(),
379		relay_parent,
380		collator: dummy_collator(),
381		persisted_validation_data_hash: zeros,
382		pov_hash: zeros,
383		erasure_root: zeros,
384		signature: dummy_collator_signature(),
385		para_head: zeros,
386		validation_code_hash: dummy_validation_code().hash(),
387	}
388}
389
390/// Create a candidate descriptor with filler data.
391pub fn dummy_candidate_descriptor<H: AsRef<[u8]>>(relay_parent: H) -> CandidateDescriptor<H> {
392	let collator = sp_keyring::Sr25519Keyring::Ferdie;
393	let invalid = Hash::zero();
394	let descriptor = make_valid_candidate_descriptor(
395		1.into(),
396		relay_parent,
397		invalid,
398		invalid,
399		invalid,
400		invalid,
401		invalid,
402		collator,
403	);
404	descriptor
405}
406
407/// Create a v2 candidate descriptor with filler data.
408pub fn dummy_candidate_descriptor_v2<H: AsRef<[u8]> + Copy>(
409	relay_parent: H,
410) -> CandidateDescriptorV2<H> {
411	let invalid = Hash::zero();
412	let descriptor = make_valid_candidate_descriptor_v2(
413		1.into(),
414		relay_parent,
415		CoreIndex(1),
416		1,
417		invalid,
418		invalid,
419		invalid,
420		invalid,
421		invalid,
422	);
423	descriptor
424}
425
426/// Create meaningless validation code.
427pub fn dummy_validation_code() -> ValidationCode {
428	ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])
429}
430
431/// Create meaningless head data.
432pub fn dummy_head_data() -> HeadData {
433	HeadData(vec![])
434}
435
436/// Create a meaningless validator id.
437pub fn dummy_validator() -> ValidatorId {
438	ValidatorId::from(sr25519::Public::default())
439}
440
441/// Create a meaningless collator id.
442pub fn dummy_collator() -> CollatorId {
443	CollatorId::from(sr25519::Public::default())
444}
445
446/// Create a meaningless collator signature. It is important to not be 0, as we'd confuse
447/// v1 and v2 descriptors.
448pub fn dummy_collator_signature() -> CollatorSignature {
449	CollatorSignature::from_slice(&mut (0..64).into_iter().collect::<Vec<_>>().as_slice())
450		.expect("64 bytes; qed")
451}
452
453/// Create a zeroed collator signature.
454pub fn zero_collator_signature() -> CollatorSignature {
455	CollatorSignature::from(sr25519::Signature::default())
456}
457
458/// Create a meaningless persisted validation data.
459pub fn dummy_pvd(parent_head: HeadData, relay_parent_number: u32) -> PersistedValidationData {
460	PersistedValidationData {
461		parent_head,
462		relay_parent_number,
463		max_pov_size: MAX_POV_SIZE,
464		relay_parent_storage_root: dummy_hash(),
465	}
466}
467
468/// Creates a meaningless signature
469pub fn dummy_signature() -> polkadot_primitives::ValidatorSignature {
470	sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64])
471}
472
473/// Create a meaningless candidate, returning its receipt and PVD.
474pub fn make_candidate(
475	relay_parent_hash: Hash,
476	relay_parent_number: u32,
477	para_id: ParaId,
478	parent_head: HeadData,
479	head_data: HeadData,
480	validation_code_hash: ValidationCodeHash,
481) -> (CommittedCandidateReceiptV2, PersistedValidationData) {
482	let pvd = dummy_pvd(parent_head, relay_parent_number);
483	let commitments = CandidateCommitments {
484		head_data,
485		horizontal_messages: Default::default(),
486		upward_messages: Default::default(),
487		new_validation_code: None,
488		processed_downward_messages: 0,
489		hrmp_watermark: relay_parent_number,
490	};
491
492	let mut candidate =
493		dummy_candidate_receipt_bad_sig(relay_parent_hash, Some(Default::default()));
494	candidate.commitments_hash = commitments.hash();
495	candidate.descriptor.para_id = para_id;
496	candidate.descriptor.persisted_validation_data_hash = pvd.hash();
497	candidate.descriptor.validation_code_hash = validation_code_hash;
498	let candidate =
499		CommittedCandidateReceiptV2 { descriptor: candidate.descriptor.into(), commitments };
500
501	(candidate, pvd)
502}
503
504/// Create a meaningless v2 candidate, returning its receipt and PVD.
505pub fn make_candidate_v2(
506	relay_parent_hash: Hash,
507	relay_parent_number: u32,
508	para_id: ParaId,
509	parent_head: HeadData,
510	head_data: HeadData,
511	validation_code_hash: ValidationCodeHash,
512) -> (CommittedCandidateReceiptV2, PersistedValidationData) {
513	let pvd = dummy_pvd(parent_head, relay_parent_number);
514	let commitments = CandidateCommitments {
515		head_data,
516		horizontal_messages: Default::default(),
517		upward_messages: Default::default(),
518		new_validation_code: None,
519		processed_downward_messages: 0,
520		hrmp_watermark: relay_parent_number,
521	};
522
523	let mut descriptor = dummy_candidate_descriptor_v2(relay_parent_hash);
524	descriptor.set_para_id(para_id);
525	descriptor.set_persisted_validation_data_hash(pvd.hash());
526	descriptor.set_validation_code_hash(validation_code_hash);
527	let candidate = CommittedCandidateReceiptV2 { descriptor, commitments };
528
529	(candidate, pvd)
530}
531
532/// Create a new candidate descriptor, and apply a valid signature
533/// using the provided `collator` key.
534pub fn make_valid_candidate_descriptor<H: AsRef<[u8]>>(
535	para_id: ParaId,
536	relay_parent: H,
537	persisted_validation_data_hash: Hash,
538	pov_hash: Hash,
539	validation_code_hash: impl Into<ValidationCodeHash>,
540	para_head: Hash,
541	erasure_root: Hash,
542	collator: Sr25519Keyring,
543) -> CandidateDescriptor<H> {
544	let validation_code_hash = validation_code_hash.into();
545	let payload = collator_signature_payload::<H>(
546		&relay_parent,
547		&para_id,
548		&persisted_validation_data_hash,
549		&pov_hash,
550		&validation_code_hash,
551	);
552
553	let signature = collator.sign(&payload).into();
554	let descriptor = CandidateDescriptor {
555		para_id,
556		relay_parent,
557		collator: collator.public().into(),
558		persisted_validation_data_hash,
559		pov_hash,
560		erasure_root,
561		signature,
562		para_head,
563		validation_code_hash,
564	};
565
566	assert!(descriptor.check_collator_signature().is_ok());
567	descriptor
568}
569
570/// Create a v2 candidate descriptor.
571pub fn make_valid_candidate_descriptor_v2<H: AsRef<[u8]> + Copy>(
572	para_id: ParaId,
573	relay_parent: H,
574	core_index: CoreIndex,
575	session_index: SessionIndex,
576	persisted_validation_data_hash: Hash,
577	pov_hash: Hash,
578	validation_code_hash: impl Into<ValidationCodeHash>,
579	para_head: Hash,
580	erasure_root: Hash,
581) -> CandidateDescriptorV2<H> {
582	let validation_code_hash = validation_code_hash.into();
583
584	let descriptor = CandidateDescriptorV2::new(
585		para_id,
586		relay_parent,
587		core_index,
588		session_index,
589		persisted_validation_data_hash,
590		pov_hash,
591		erasure_root,
592		para_head,
593		validation_code_hash,
594	);
595
596	descriptor
597}
598/// After manually modifying the candidate descriptor, resign with a defined collator key.
599pub fn resign_candidate_descriptor_with_collator<H: AsRef<[u8]>>(
600	descriptor: &mut CandidateDescriptor<H>,
601	collator: Sr25519Keyring,
602) {
603	descriptor.collator = collator.public().into();
604	let payload = collator_signature_payload::<H>(
605		&descriptor.relay_parent,
606		&descriptor.para_id,
607		&descriptor.persisted_validation_data_hash,
608		&descriptor.pov_hash,
609		&descriptor.validation_code_hash,
610	);
611	let signature = collator.sign(&payload).into();
612	descriptor.signature = signature;
613}
614
615/// Extracts validators's public keys (`ValidatorId`) from `Sr25519Keyring`
616pub fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
617	val_ids.iter().map(|v| v.public().into()).collect()
618}
619
620/// Builder for `CandidateReceipt`.
621pub struct TestCandidateBuilder {
622	pub para_id: ParaId,
623	pub pov_hash: Hash,
624	pub relay_parent: Hash,
625	pub commitments_hash: Hash,
626	pub core_index: CoreIndex,
627}
628
629impl std::default::Default for TestCandidateBuilder {
630	fn default() -> Self {
631		let zeros = Hash::zero();
632		Self {
633			para_id: 0.into(),
634			pov_hash: zeros,
635			relay_parent: zeros,
636			commitments_hash: zeros,
637			core_index: CoreIndex(0),
638		}
639	}
640}
641
642impl TestCandidateBuilder {
643	/// Build a `CandidateReceipt`.
644	pub fn build(self) -> CandidateReceiptV2 {
645		let mut descriptor = dummy_candidate_descriptor_v2(self.relay_parent);
646		descriptor.set_para_id(self.para_id);
647		descriptor.set_pov_hash(self.pov_hash);
648		descriptor.set_core_index(self.core_index);
649		CandidateReceiptV2 { descriptor, commitments_hash: self.commitments_hash }
650	}
651}
652
653/// A special `Rng` that always returns zero for testing something that implied
654/// to be random but should not be random in the tests
655pub struct AlwaysZeroRng;
656
657impl Default for AlwaysZeroRng {
658	fn default() -> Self {
659		Self {}
660	}
661}
662impl rand::RngCore for AlwaysZeroRng {
663	fn next_u32(&mut self) -> u32 {
664		0_u32
665	}
666
667	fn next_u64(&mut self) -> u64 {
668		0_u64
669	}
670
671	fn fill_bytes(&mut self, dest: &mut [u8]) {
672		for element in dest.iter_mut() {
673			*element = 0_u8;
674		}
675	}
676
677	fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
678		self.fill_bytes(dest);
679		Ok(())
680	}
681}
682
683#[cfg(test)]
684mod candidate_receipt_tests {
685
686	use super::*;
687	use bitvec::prelude::*;
688	use polkadot_primitives::{
689		transpose_claim_queue, v9::CandidateUMPSignals, BackedCandidate,
690		CandidateDescriptorVersion, ClaimQueueOffset, CommittedCandidateReceiptError, CoreSelector,
691		InternalVersion, UMPSignal, UMP_SEPARATOR,
692	};
693	use std::collections::BTreeMap;
694
695	#[test]
696	fn collator_signature_payload_is_valid() {
697		// if this fails, collator signature verification code has to be updated.
698		let h = Hash::default();
699		assert_eq!(h.as_ref().len(), 32);
700
701		let _payload = collator_signature_payload(
702			&Hash::repeat_byte(1),
703			&5u32.into(),
704			&Hash::repeat_byte(2),
705			&Hash::repeat_byte(3),
706			&Hash::repeat_byte(4).into(),
707		);
708	}
709
710	#[test]
711	fn is_binary_compatibile() {
712		let old_ccr = dummy_committed_candidate_receipt(Hash::default());
713		let new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
714
715		assert_eq!(old_ccr.encoded_size(), new_ccr.encoded_size());
716
717		let encoded_old = old_ccr.encode();
718
719		// Deserialize from old candidate receipt.
720		let new_ccr: CommittedCandidateReceiptV2 =
721			Decode::decode(&mut encoded_old.as_slice()).unwrap();
722
723		// We get same candidate hash.
724		assert_eq!(old_ccr.hash(), new_ccr.hash());
725	}
726
727	#[test]
728	fn test_from_v1_descriptor() {
729		let mut old_ccr = dummy_committed_candidate_receipt(Hash::default()).to_plain();
730		old_ccr.descriptor.collator = dummy_collator();
731		old_ccr.descriptor.signature = dummy_collator_signature();
732
733		let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default()).to_plain();
734
735		// Override descriptor from old candidate receipt.
736		new_ccr.descriptor = old_ccr.descriptor.clone().into();
737
738		// We get same candidate hash.
739		assert_eq!(old_ccr.hash(), new_ccr.hash());
740
741		assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
742		assert_eq!(old_ccr.descriptor.collator, new_ccr.descriptor.collator().unwrap());
743		assert_eq!(old_ccr.descriptor.signature, new_ccr.descriptor.signature().unwrap());
744	}
745
746	#[test]
747	fn invalid_version_descriptor() {
748		let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
749		assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V2);
750		// Put some unknown version.
751		new_ccr.descriptor.set_version(InternalVersion(100));
752
753		// Deserialize as V1.
754		let new_ccr: CommittedCandidateReceiptV2 =
755			Decode::decode(&mut new_ccr.encode().as_slice()).unwrap();
756
757		assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::Unknown);
758		assert_eq!(
759			new_ccr.parse_ump_signals(&std::collections::BTreeMap::new()),
760			Err(CommittedCandidateReceiptError::UnknownVersion(InternalVersion(100)))
761		);
762	}
763
764	#[test]
765	fn test_version2_receipts_decoded_as_v1() {
766		let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
767		new_ccr.descriptor.set_core_index(CoreIndex(123));
768		new_ccr.descriptor.set_para_id(ParaId::new(1000));
769
770		// dummy XCM messages
771		new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
772		new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
773
774		// separator
775		new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
776
777		// CoreIndex commitment
778		new_ccr
779			.commitments
780			.upward_messages
781			.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
782
783		let encoded_ccr = new_ccr.encode();
784		let decoded_ccr: CommittedCandidateReceipt =
785			Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
786
787		assert_eq!(decoded_ccr.descriptor.relay_parent, new_ccr.descriptor.relay_parent());
788		assert_eq!(decoded_ccr.descriptor.para_id, new_ccr.descriptor.para_id());
789
790		assert_eq!(new_ccr.hash(), decoded_ccr.hash());
791
792		// Encode v1 and decode as V2
793		let encoded_ccr = new_ccr.encode();
794		let v2_ccr: CommittedCandidateReceiptV2 =
795			Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
796
797		assert_eq!(v2_ccr.descriptor.core_index(), Some(CoreIndex(123)));
798
799		let mut cq = BTreeMap::new();
800		cq.insert(
801			CoreIndex(123),
802			vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
803		);
804
805		assert!(new_ccr.parse_ump_signals(&transpose_claim_queue(cq)).is_ok());
806
807		assert_eq!(new_ccr.hash(), v2_ccr.hash());
808	}
809
810	// V1 descriptors are forbidden once the parachain runtime started sending UMP signals.
811	#[test]
812	fn test_v1_descriptors_with_ump_signal() {
813		let mut ccr = dummy_committed_candidate_receipt(Hash::default());
814		ccr.descriptor.para_id = ParaId::new(1024);
815		// Adding collator signature should make it decode as v1.
816		ccr.descriptor.signature = dummy_collator_signature();
817		ccr.descriptor.collator = dummy_collator();
818
819		ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
820		ccr.commitments
821			.upward_messages
822			.force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode());
823
824		ccr.commitments
825			.upward_messages
826			.force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
827
828		let encoded_ccr: Vec<u8> = ccr.encode();
829
830		let v1_ccr: CommittedCandidateReceiptV2 =
831			Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
832
833		assert_eq!(v1_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
834		assert!(!v1_ccr.commitments.ump_signals().unwrap().is_empty());
835
836		let mut cq = BTreeMap::new();
837		cq.insert(CoreIndex(0), vec![v1_ccr.descriptor.para_id()].into());
838		cq.insert(CoreIndex(1), vec![v1_ccr.descriptor.para_id()].into());
839
840		assert_eq!(v1_ccr.descriptor.core_index(), None);
841
842		assert_eq!(
843			v1_ccr.parse_ump_signals(&transpose_claim_queue(cq)),
844			Err(CommittedCandidateReceiptError::UMPSignalWithV1Decriptor)
845		);
846	}
847
848	#[test]
849	fn test_core_select_is_optional() {
850		// Testing edge case when collators provide zeroed signature and collator id.
851		let mut old_ccr = dummy_committed_candidate_receipt(Hash::default());
852		old_ccr.descriptor.para_id = ParaId::new(1000);
853		let encoded_ccr: Vec<u8> = old_ccr.encode();
854
855		let new_ccr: CommittedCandidateReceiptV2 =
856			Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
857
858		let mut cq = BTreeMap::new();
859		cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
860
861		// Since collator sig and id are zeroed, it means that the descriptor uses format
862		// version 2. Should still pass checks without core selector.
863		assert!(new_ccr.parse_ump_signals(&transpose_claim_queue(cq)).is_ok());
864
865		let mut cq = BTreeMap::new();
866		cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
867		cq.insert(CoreIndex(1), vec![new_ccr.descriptor.para_id()].into());
868
869		// Passes even if 2 cores are assigned, because elastic scaling MVP could still inject the
870		// core index in the `BackedCandidate`.
871		assert!(new_ccr.parse_ump_signals(&transpose_claim_queue(cq)).is_ok());
872
873		// Adding collator signature should make it decode as v1.
874		old_ccr.descriptor.signature = dummy_collator_signature();
875		old_ccr.descriptor.collator = dummy_collator();
876
877		let old_ccr_hash = old_ccr.hash();
878
879		let encoded_ccr: Vec<u8> = old_ccr.encode();
880
881		let new_ccr: CommittedCandidateReceiptV2 =
882			Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
883
884		assert_eq!(new_ccr.descriptor.signature(), Some(old_ccr.descriptor.signature));
885		assert_eq!(new_ccr.descriptor.collator(), Some(old_ccr.descriptor.collator));
886
887		assert_eq!(new_ccr.descriptor.core_index(), None);
888		assert_eq!(new_ccr.descriptor.para_id(), ParaId::new(1000));
889
890		assert_eq!(old_ccr_hash, new_ccr.hash());
891	}
892
893	#[test]
894	// Test valid scenarios for parse_ump_signals():
895	// - no signals
896	// - only selected core signal
897	// - only approved peer signal
898	// - both signals in any order
899	fn test_ump_commitments() {
900		let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
901		new_ccr.descriptor.set_core_index(CoreIndex(123));
902		new_ccr.descriptor.set_para_id(ParaId::new(1000));
903
904		let mut cq = BTreeMap::new();
905		cq.insert(
906			CoreIndex(123),
907			vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
908		);
909		let cq = transpose_claim_queue(cq);
910
911		// No commitments
912
913		// dummy XCM messages
914		new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
915		new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
916
917		assert_eq!(new_ccr.parse_ump_signals(&cq), Ok(CandidateUMPSignals::dummy(None, None)));
918
919		// separator
920		new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
921
922		assert_eq!(new_ccr.parse_ump_signals(&cq), Ok(CandidateUMPSignals::dummy(None, None)));
923
924		// CoreIndex commitment
925		{
926			let mut new_ccr = new_ccr.clone();
927			new_ccr
928				.commitments
929				.upward_messages
930				.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
931
932			assert_eq!(
933				new_ccr.parse_ump_signals(&cq),
934				Ok(CandidateUMPSignals::dummy(Some((CoreSelector(0), ClaimQueueOffset(1))), None))
935			);
936		}
937
938		{
939			let mut new_ccr = new_ccr.clone();
940
941			// Test having only an approved peer.
942			new_ccr
943				.commitments
944				.upward_messages
945				.force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
946
947			assert_eq!(
948				new_ccr.parse_ump_signals(&cq),
949				Ok(CandidateUMPSignals::dummy(None, Some(vec![1, 2, 3].try_into().unwrap())))
950			);
951
952			// Test having an approved peer and a core selector.
953
954			new_ccr
955				.commitments
956				.upward_messages
957				.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
958
959			assert_eq!(
960				new_ccr.parse_ump_signals(&cq),
961				Ok(CandidateUMPSignals::dummy(
962					Some((CoreSelector(0), ClaimQueueOffset(1))),
963					Some(vec![1, 2, 3].try_into().unwrap())
964				))
965			);
966		}
967
968		// Test having a core selector and an approved peer.
969		new_ccr
970			.commitments
971			.upward_messages
972			.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
973		new_ccr
974			.commitments
975			.upward_messages
976			.force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
977
978		assert_eq!(
979			new_ccr.parse_ump_signals(&cq),
980			Ok(CandidateUMPSignals::dummy(
981				Some((CoreSelector(0), ClaimQueueOffset(1))),
982				Some(vec![1, 2, 3].try_into().unwrap())
983			))
984		);
985	}
986
987	#[test]
988	fn test_invalid_ump_commitments() {
989		let mut new_ccr = dummy_committed_candidate_receipt_v2(Hash::default());
990		new_ccr.descriptor.set_core_index(CoreIndex(0));
991		new_ccr.descriptor.set_para_id(ParaId::new(1000));
992
993		new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
994
995		let mut cq = BTreeMap::new();
996		cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
997		let cq = transpose_claim_queue(cq);
998
999		// Add an approved peer message.
1000		new_ccr
1001			.commitments
1002			.upward_messages
1003			.force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1004
1005		// Garbage message.
1006		new_ccr.commitments.upward_messages.force_push(vec![0, 13, 200].encode());
1007
1008		// No signals can be decoded.
1009		assert_eq!(
1010			new_ccr.parse_ump_signals(&cq),
1011			Err(CommittedCandidateReceiptError::UmpSignalDecode)
1012		);
1013		assert_eq!(
1014			new_ccr.commitments.ump_signals(),
1015			Err(CommittedCandidateReceiptError::UmpSignalDecode)
1016		);
1017
1018		// Verify core index checks.
1019		{
1020			// Has two cores assigned but no core commitment. Will pass the check if the descriptor
1021			// core index is indeed assigned to the para.
1022			new_ccr.commitments.upward_messages.clear();
1023			new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1024			new_ccr
1025				.commitments
1026				.upward_messages
1027				.force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1028
1029			let mut cq = BTreeMap::new();
1030			cq.insert(
1031				CoreIndex(0),
1032				vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1033			);
1034			cq.insert(
1035				CoreIndex(100),
1036				vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
1037			);
1038			let cq = transpose_claim_queue(cq);
1039
1040			assert_eq!(
1041				new_ccr.parse_ump_signals(&cq),
1042				Ok(CandidateUMPSignals::dummy(None, Some(vec![1, 2, 3].try_into().unwrap())))
1043			);
1044
1045			new_ccr.descriptor.set_core_index(CoreIndex(1));
1046			assert_eq!(
1047				new_ccr.parse_ump_signals(&cq),
1048				Err(CommittedCandidateReceiptError::InvalidCoreIndex)
1049			);
1050			new_ccr.descriptor.set_core_index(CoreIndex(0));
1051
1052			new_ccr
1053				.commitments
1054				.upward_messages
1055				.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
1056
1057			// No assignments.
1058			assert_eq!(
1059				new_ccr.parse_ump_signals(&transpose_claim_queue(Default::default())),
1060				Err(CommittedCandidateReceiptError::NoAssignment)
1061			);
1062
1063			// Mismatch between descriptor index and commitment.
1064			new_ccr.descriptor.set_core_index(CoreIndex(1));
1065			assert_eq!(
1066				new_ccr.parse_ump_signals(&cq),
1067				Err(CommittedCandidateReceiptError::CoreIndexMismatch {
1068					descriptor: CoreIndex(1),
1069					commitments: CoreIndex(0),
1070				})
1071			);
1072		}
1073
1074		new_ccr.descriptor.set_core_index(CoreIndex(0));
1075
1076		// Add two ApprovedPeer messages
1077		new_ccr.commitments.upward_messages.clear();
1078		new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1079		new_ccr
1080			.commitments
1081			.upward_messages
1082			.force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1083		new_ccr
1084			.commitments
1085			.upward_messages
1086			.force_push(UMPSignal::ApprovedPeer(vec![4, 5].try_into().unwrap()).encode());
1087
1088		assert_eq!(
1089			new_ccr.parse_ump_signals(&cq),
1090			Err(CommittedCandidateReceiptError::DuplicateUMPSignal)
1091		);
1092
1093		// Too many
1094		new_ccr.commitments.upward_messages.clear();
1095		new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
1096		new_ccr
1097			.commitments
1098			.upward_messages
1099			.force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1100		new_ccr
1101			.commitments
1102			.upward_messages
1103			.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(0)).encode());
1104		new_ccr
1105			.commitments
1106			.upward_messages
1107			.force_push(UMPSignal::ApprovedPeer(vec![1, 2, 3].try_into().unwrap()).encode());
1108
1109		assert_eq!(
1110			new_ccr.parse_ump_signals(&cq),
1111			Err(CommittedCandidateReceiptError::TooManyUMPSignals)
1112		);
1113	}
1114
1115	#[test]
1116	fn test_backed_candidate_injected_core_index() {
1117		let initial_validator_indices = bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1];
1118		let mut candidate = BackedCandidate::new(
1119			dummy_committed_candidate_receipt_v2(Hash::default()),
1120			vec![],
1121			initial_validator_indices.clone(),
1122			CoreIndex(10),
1123		);
1124
1125		// No core index supplied.
1126		candidate
1127			.set_validator_indices_and_core_index(initial_validator_indices.clone().into(), None);
1128		let (validator_indices, core_index) = candidate.validator_indices_and_core_index();
1129		assert_eq!(validator_indices, initial_validator_indices.as_bitslice());
1130		assert!(core_index.is_none());
1131
1132		// No core index supplied. Decoding is corrupted if backing group
1133		// size larger than 8.
1134		candidate.set_validator_indices_and_core_index(
1135			bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1, 0, 1, 0, 1, 0].into(),
1136			None,
1137		);
1138
1139		let (validator_indices, core_index) = candidate.validator_indices_and_core_index();
1140		assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0].as_bitslice());
1141		assert!(core_index.is_some());
1142
1143		// Core index supplied.
1144		let mut candidate = BackedCandidate::new(
1145			dummy_committed_candidate_receipt_v2(Hash::default()),
1146			vec![],
1147			bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1],
1148			CoreIndex(10),
1149		);
1150		let (validator_indices, core_index) = candidate.validator_indices_and_core_index();
1151		assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1]);
1152		assert_eq!(core_index, Some(CoreIndex(10)));
1153
1154		let encoded_validator_indices = candidate.raw_validator_indices();
1155		candidate.set_validator_indices_and_core_index(validator_indices.into(), core_index);
1156		assert_eq!(candidate.raw_validator_indices(), encoded_validator_indices);
1157	}
1158}