1use codec::Encode;
20use itertools::Itertools;
21pub use polkadot_node_primitives::approval::criteria::{
22 AssignmentCriteria, Config, InvalidAssignment, InvalidAssignmentReason, OurAssignment,
23};
24use polkadot_node_primitives::approval::{
25 self as approval_types,
26 v1::{DelayTranche, RelayVRFStory},
27 v2::{
28 AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, VrfPreOutput, VrfProof, VrfSignature,
29 },
30};
31
32use polkadot_primitives::{
33 AssignmentPair, CandidateHash, CoreIndex, GroupIndex, IndexedVec, ValidatorIndex,
34};
35use rand::{seq::SliceRandom, SeedableRng};
36use rand_chacha::ChaCha20Rng;
37use sc_keystore::LocalKeystore;
38use sp_application_crypto::ByteArray;
39
40use merlin::Transcript;
41use schnorrkel::vrf::VRFInOut;
42
43use std::{
44 cmp::min,
45 collections::{hash_map::Entry, HashMap},
46};
47
48use super::LOG_TARGET;
49
50impl From<crate::approval_db::v2::OurAssignment> for OurAssignment {
51 fn from(entry: crate::approval_db::v2::OurAssignment) -> Self {
52 OurAssignment::new(entry.cert, entry.tranche, entry.validator_index, entry.triggered)
53 }
54}
55
56impl From<OurAssignment> for crate::approval_db::v2::OurAssignment {
57 fn from(entry: OurAssignment) -> Self {
58 Self {
59 tranche: entry.tranche(),
60 validator_index: entry.validator_index(),
61 triggered: entry.triggered(),
62 cert: entry.into_cert(),
63 }
64 }
65}
66
67fn relay_vrf_modulo_transcript_inner(
69 mut transcript: Transcript,
70 relay_vrf_story: RelayVRFStory,
71 sample: Option<u32>,
72) -> Transcript {
73 transcript.append_message(b"RC-VRF", &relay_vrf_story.0);
74
75 if let Some(sample) = sample {
76 sample.using_encoded(|s| transcript.append_message(b"sample", s));
77 }
78
79 transcript
80}
81
82fn relay_vrf_modulo_transcript_v2(relay_vrf_story: RelayVRFStory) -> Transcript {
83 relay_vrf_modulo_transcript_inner(
84 Transcript::new(approval_types::v2::RELAY_VRF_MODULO_CONTEXT),
85 relay_vrf_story,
86 None,
87 )
88}
89
90const MAX_MODULO_SAMPLES: usize = 40;
92
93fn relay_vrf_modulo_cores(
96 vrf_in_out: &VRFInOut,
97 num_samples: u32,
99 max_cores: u32,
101) -> Vec<CoreIndex> {
102 let rand_chacha =
103 ChaCha20Rng::from_seed(vrf_in_out.make_bytes::<<ChaCha20Rng as SeedableRng>::Seed>(
104 approval_types::v2::CORE_RANDOMNESS_CONTEXT,
105 ));
106 generate_samples(rand_chacha, num_samples as usize, max_cores as usize)
107}
108
109fn generate_samples(
117 mut rand_chacha: ChaCha20Rng,
118 num_samples: usize,
119 max_cores: usize,
120) -> Vec<CoreIndex> {
121 if num_samples as usize > MAX_MODULO_SAMPLES {
122 gum::warn!(
123 target: LOG_TARGET,
124 n_cores = max_cores,
125 num_samples,
126 max_modulo_samples = MAX_MODULO_SAMPLES,
127 "`num_samples` is greater than `MAX_MODULO_SAMPLES`",
128 );
129 }
130
131 if 2 * num_samples > max_cores {
132 gum::debug!(
133 target: LOG_TARGET,
134 n_cores = max_cores,
135 num_samples,
136 max_modulo_samples = MAX_MODULO_SAMPLES,
137 "Suboptimal configuration `num_samples` should be less than `n_cores` / 2",
138 );
139 }
140
141 let num_samples = min(MAX_MODULO_SAMPLES, min(num_samples, max_cores));
142
143 let mut random_cores = (0..max_cores as u32).map(|val| val.into()).collect::<Vec<CoreIndex>>();
144 let (samples, _) = random_cores.partial_shuffle(&mut rand_chacha, num_samples as usize);
145 samples.into_iter().map(|val| *val).collect_vec()
146}
147
148fn relay_vrf_delay_transcript(relay_vrf_story: RelayVRFStory, core_index: CoreIndex) -> Transcript {
149 let mut t = Transcript::new(approval_types::v1::RELAY_VRF_DELAY_CONTEXT);
150 t.append_message(b"RC-VRF", &relay_vrf_story.0);
151 core_index.0.using_encoded(|s| t.append_message(b"core", s));
152 t
153}
154
155fn relay_vrf_delay_tranche(
156 vrf_in_out: &VRFInOut,
157 num_delay_tranches: u32,
158 zeroth_delay_tranche_width: u32,
159) -> DelayTranche {
160 let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::v1::TRANCHE_RANDOMNESS_CONTEXT);
161
162 let wide_tranche =
164 u32::from_le_bytes(bytes) % (num_delay_tranches + zeroth_delay_tranche_width);
165
166 wide_tranche.saturating_sub(zeroth_delay_tranche_width)
168}
169
170pub struct RealAssignmentCriteria;
171
172impl AssignmentCriteria for RealAssignmentCriteria {
173 fn compute_assignments(
174 &self,
175 keystore: &LocalKeystore,
176 relay_vrf_story: RelayVRFStory,
177 config: &Config,
178 leaving_cores: Vec<(CandidateHash, CoreIndex, GroupIndex)>,
179 ) -> HashMap<CoreIndex, OurAssignment> {
180 compute_assignments(keystore, relay_vrf_story, config, leaving_cores)
181 }
182
183 fn check_assignment_cert(
184 &self,
185 claimed_core_bitfield: CoreBitfield,
186 validator_index: ValidatorIndex,
187 config: &Config,
188 relay_vrf_story: RelayVRFStory,
189 assignment: &AssignmentCertV2,
190 backing_groups: Vec<GroupIndex>,
191 ) -> Result<DelayTranche, InvalidAssignment> {
192 check_assignment_cert(
193 claimed_core_bitfield,
194 validator_index,
195 config,
196 relay_vrf_story,
197 assignment,
198 backing_groups,
199 )
200 }
201}
202
203pub fn compute_assignments(
215 keystore: &LocalKeystore,
216 relay_vrf_story: RelayVRFStory,
217 config: &Config,
218 leaving_cores: impl IntoIterator<Item = (CandidateHash, CoreIndex, GroupIndex)> + Clone,
219) -> HashMap<CoreIndex, OurAssignment> {
220 if config.n_cores == 0 ||
221 config.assignment_keys.is_empty() ||
222 config.validator_groups.is_empty()
223 {
224 gum::trace!(
225 target: LOG_TARGET,
226 n_cores = config.n_cores,
227 has_assignment_keys = !config.assignment_keys.is_empty(),
228 has_validator_groups = !config.validator_groups.is_empty(),
229 "Not producing assignments because config is degenerate",
230 );
231
232 return HashMap::new();
233 }
234
235 let (index, assignments_key): (ValidatorIndex, AssignmentPair) = {
236 let key = config.assignment_keys.iter().enumerate().find_map(|(i, p)| {
237 match keystore.key_pair(p) {
238 Ok(Some(pair)) => Some((ValidatorIndex(i as _), pair)),
239 Ok(None) => None,
240 Err(sc_keystore::Error::Unavailable) => None,
241 Err(sc_keystore::Error::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => None,
242 Err(e) => {
243 gum::warn!(target: LOG_TARGET, "Encountered keystore error: {:?}", e);
244 None
245 },
246 }
247 });
248
249 match key {
250 None => {
251 gum::trace!(target: LOG_TARGET, "No assignment key");
252 return HashMap::new();
253 },
254 Some(k) => k,
255 }
256 };
257
258 let leaving_cores = leaving_cores
260 .into_iter()
261 .filter(|(_, _, g)| !is_in_backing_group(&config.validator_groups, index, *g))
262 .map(|(c_hash, core, _)| (c_hash, core))
263 .collect::<Vec<_>>();
264
265 gum::trace!(
266 target: LOG_TARGET,
267 assignable_cores = leaving_cores.len(),
268 "Assigning to candidates from different backing groups"
269 );
270
271 let assignments_key: &sp_application_crypto::sr25519::Pair = assignments_key.as_ref();
272 let assignments_key: &schnorrkel::Keypair = assignments_key.as_ref();
273
274 let mut assignments = HashMap::new();
275
276 compute_relay_vrf_modulo_assignments_v2(
278 &assignments_key,
279 index,
280 config,
281 relay_vrf_story.clone(),
282 leaving_cores.clone(),
283 &mut assignments,
284 );
285
286 compute_relay_vrf_delay_assignments(
288 &assignments_key,
289 index,
290 config,
291 relay_vrf_story,
292 leaving_cores,
293 &mut assignments,
294 );
295
296 assignments
297}
298
299fn assigned_cores_transcript(core_bitfield: &CoreBitfield) -> Transcript {
300 let mut t = Transcript::new(approval_types::v2::ASSIGNED_CORE_CONTEXT);
301 core_bitfield.using_encoded(|s| t.append_message(b"cores", s));
302 t
303}
304
305fn compute_relay_vrf_modulo_assignments_v2(
306 assignments_key: &schnorrkel::Keypair,
307 validator_index: ValidatorIndex,
308 config: &Config,
309 relay_vrf_story: RelayVRFStory,
310 leaving_cores: Vec<(CandidateHash, CoreIndex)>,
311 assignments: &mut HashMap<CoreIndex, OurAssignment>,
312) {
313 let mut assigned_cores = Vec::new();
314 let leaving_cores = leaving_cores.iter().map(|(_, core)| core).collect::<Vec<_>>();
315
316 let maybe_assignment = {
317 let assigned_cores = &mut assigned_cores;
318 assignments_key.vrf_sign_extra_after_check(
319 relay_vrf_modulo_transcript_v2(relay_vrf_story.clone()),
320 |vrf_in_out| {
321 *assigned_cores = relay_vrf_modulo_cores(
322 &vrf_in_out,
323 config.relay_vrf_modulo_samples,
324 config.n_cores,
325 )
326 .into_iter()
327 .filter(|core| leaving_cores.contains(&core))
328 .collect::<Vec<CoreIndex>>();
329
330 if !assigned_cores.is_empty() {
331 gum::trace!(
332 target: LOG_TARGET,
333 ?assigned_cores,
334 ?validator_index,
335 tranche = 0,
336 "RelayVRFModuloCompact Assignment."
337 );
338
339 let assignment_bitfield: CoreBitfield = assigned_cores
340 .clone()
341 .try_into()
342 .expect("Just checked `!assigned_cores.is_empty()`; qed");
343
344 Some(assigned_cores_transcript(&assignment_bitfield))
345 } else {
346 None
347 }
348 },
349 )
350 };
351
352 if let Some(assignment) = maybe_assignment.map(|(vrf_in_out, vrf_proof, _)| {
353 let assignment_bitfield: CoreBitfield = assigned_cores
354 .clone()
355 .try_into()
356 .expect("Just checked `!assigned_cores.is_empty()`; qed");
357
358 let cert = AssignmentCertV2 {
359 kind: AssignmentCertKindV2::RelayVRFModuloCompact {
360 core_bitfield: assignment_bitfield.clone(),
361 },
362 vrf: VrfSignature {
363 pre_output: VrfPreOutput(vrf_in_out.to_preout()),
364 proof: VrfProof(vrf_proof),
365 },
366 };
367
368 OurAssignment::new(cert, 0, validator_index, false)
370 }) {
371 for core_index in assigned_cores {
372 assignments.insert(core_index, assignment.clone());
373 }
374 }
375}
376
377fn compute_relay_vrf_delay_assignments(
378 assignments_key: &schnorrkel::Keypair,
379 validator_index: ValidatorIndex,
380 config: &Config,
381 relay_vrf_story: RelayVRFStory,
382 leaving_cores: impl IntoIterator<Item = (CandidateHash, CoreIndex)>,
383 assignments: &mut HashMap<CoreIndex, OurAssignment>,
384) {
385 for (candidate_hash, core) in leaving_cores {
386 let (vrf_in_out, vrf_proof, _) =
387 assignments_key.vrf_sign(relay_vrf_delay_transcript(relay_vrf_story.clone(), core));
388
389 let tranche = relay_vrf_delay_tranche(
390 &vrf_in_out,
391 config.n_delay_tranches,
392 config.zeroth_delay_tranche_width,
393 );
394
395 let cert = AssignmentCertV2 {
396 kind: AssignmentCertKindV2::RelayVRFDelay { core_index: core },
397 vrf: VrfSignature {
398 pre_output: VrfPreOutput(vrf_in_out.to_preout()),
399 proof: VrfProof(vrf_proof),
400 },
401 };
402
403 let our_assignment = OurAssignment::new(cert, tranche, validator_index, false);
404
405 let used = match assignments.entry(core) {
406 Entry::Vacant(e) => {
407 let _ = e.insert(our_assignment);
408 true
409 },
410 Entry::Occupied(mut e) => {
411 if e.get().tranche() > our_assignment.tranche() {
412 e.insert(our_assignment);
413 true
414 } else {
415 false
416 }
417 },
418 };
419
420 if used {
421 gum::trace!(
422 target: LOG_TARGET,
423 ?candidate_hash,
424 ?core,
425 ?validator_index,
426 tranche,
427 "RelayVRFDelay Assignment",
428 );
429 }
430 }
431}
432
433pub(crate) fn check_assignment_cert(
445 claimed_core_indices: CoreBitfield,
446 validator_index: ValidatorIndex,
447 config: &Config,
448 relay_vrf_story: RelayVRFStory,
449 assignment: &AssignmentCertV2,
450 backing_groups: Vec<GroupIndex>,
451) -> Result<DelayTranche, InvalidAssignment> {
452 use InvalidAssignmentReason as Reason;
453
454 let validator_public = config
455 .assignment_keys
456 .get(validator_index.0 as usize)
457 .ok_or(InvalidAssignment(Reason::ValidatorIndexOutOfBounds))?;
458
459 let public = schnorrkel::PublicKey::from_bytes(validator_public.as_slice())
460 .map_err(|_| InvalidAssignment(Reason::InvalidAssignmentKey))?;
461
462 if claimed_core_indices.count_ones() == 0 ||
464 claimed_core_indices.count_ones() != backing_groups.len()
465 {
466 return Err(InvalidAssignment(Reason::InvalidArguments));
467 }
468
469 for (claimed_core, backing_group) in claimed_core_indices.iter_ones().zip(backing_groups.iter())
472 {
473 if claimed_core >= config.n_cores as usize {
474 return Err(InvalidAssignment(Reason::CoreIndexOutOfBounds));
475 }
476
477 let is_in_backing =
478 is_in_backing_group(&config.validator_groups, validator_index, *backing_group);
479
480 if is_in_backing {
481 return Err(InvalidAssignment(Reason::IsInBackingGroup));
482 }
483 }
484
485 let vrf_pre_output = &assignment.vrf.pre_output;
486 let vrf_proof = &assignment.vrf.proof;
487 let first_claimed_core_index =
488 claimed_core_indices.first_one().expect("Checked above; qed") as u32;
489
490 match &assignment.kind {
491 AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => {
492 if &claimed_core_indices != core_bitfield {
494 return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch));
495 }
496
497 let (vrf_in_out, _) = public
498 .vrf_verify_extra(
499 relay_vrf_modulo_transcript_v2(relay_vrf_story),
500 &vrf_pre_output.0,
501 &vrf_proof.0,
502 assigned_cores_transcript(core_bitfield),
503 )
504 .map_err(|_| InvalidAssignment(Reason::VRFModuloOutputMismatch))?;
505
506 let resulting_cores = relay_vrf_modulo_cores(
507 &vrf_in_out,
508 config.relay_vrf_modulo_samples,
509 config.n_cores,
510 );
511
512 for claimed_core_index in claimed_core_indices.iter_ones() {
517 if !resulting_cores.contains(&CoreIndex(claimed_core_index as u32)) {
518 gum::debug!(
519 target: LOG_TARGET,
520 ?resulting_cores,
521 ?claimed_core_indices,
522 vrf_modulo_cores = ?resulting_cores,
523 "Assignment claimed cores mismatch",
524 );
525 return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch));
526 }
527 }
528
529 Ok(0)
530 },
531 AssignmentCertKindV2::RelayVRFDelay { core_index } => {
532 if claimed_core_indices.count_ones() != 1 {
534 gum::debug!(
535 target: LOG_TARGET,
536 ?claimed_core_indices,
537 "`RelayVRFDelay` assignment must always claim 1 core",
538 );
539 return Err(InvalidAssignment(Reason::InvalidArguments));
540 }
541
542 if core_index.0 != first_claimed_core_index {
543 return Err(InvalidAssignment(Reason::VRFDelayCoreIndexMismatch));
544 }
545
546 let (vrf_in_out, _) = public
547 .vrf_verify(
548 relay_vrf_delay_transcript(relay_vrf_story, *core_index),
549 &vrf_pre_output.0,
550 &vrf_proof.0,
551 )
552 .map_err(|_| InvalidAssignment(Reason::VRFDelayOutputMismatch))?;
553
554 Ok(relay_vrf_delay_tranche(
555 &vrf_in_out,
556 config.n_delay_tranches,
557 config.zeroth_delay_tranche_width,
558 ))
559 },
560 }
561}
562
563fn is_in_backing_group(
564 validator_groups: &IndexedVec<GroupIndex, Vec<ValidatorIndex>>,
565 validator: ValidatorIndex,
566 group: GroupIndex,
567) -> bool {
568 validator_groups.get(group).map_or(false, |g| g.contains(&validator))
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574 use crate::import::tests::garbage_vrf_signature;
575 use polkadot_primitives::{AssignmentId, Hash, ASSIGNMENT_KEY_TYPE_ID};
576 use sp_application_crypto::sr25519;
577 use sp_core::crypto::Pair as PairT;
578 use sp_keyring::sr25519::Keyring as Sr25519Keyring;
579 use sp_keystore::Keystore;
580
581 fn make_keystore(accounts: &[Sr25519Keyring]) -> LocalKeystore {
583 let store = LocalKeystore::in_memory();
584
585 for s in accounts.iter().copied().map(|k| k.to_seed()) {
586 store.sr25519_generate_new(ASSIGNMENT_KEY_TYPE_ID, Some(s.as_str())).unwrap();
587 }
588
589 store
590 }
591
592 fn assignment_keys(accounts: &[Sr25519Keyring]) -> Vec<AssignmentId> {
593 assignment_keys_plus_random(accounts, 0)
594 }
595
596 fn assignment_keys_plus_random(
597 accounts: &[Sr25519Keyring],
598 random: usize,
599 ) -> Vec<AssignmentId> {
600 let gen_random =
601 (0..random).map(|_| AssignmentId::from(sr25519::Pair::generate().0.public()));
602
603 accounts
604 .iter()
605 .map(|k| AssignmentId::from(k.public()))
606 .chain(gen_random)
607 .collect()
608 }
609
610 fn basic_groups(
611 n_validators: usize,
612 n_groups: usize,
613 ) -> IndexedVec<GroupIndex, Vec<ValidatorIndex>> {
614 let size = n_validators / n_groups;
615 let big_groups = n_validators % n_groups;
616 let scraps = n_groups * size;
617
618 (0..n_groups)
619 .map(|i| {
620 (i * size..(i + 1) * size)
621 .chain(if i < big_groups { Some(scraps + i) } else { None })
622 .map(|j| ValidatorIndex(j as _))
623 .collect::<Vec<_>>()
624 })
625 .collect()
626 }
627
628 #[test]
629 fn assignments_produced_for_non_backing() {
630 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
631
632 let c_a = CandidateHash(Hash::repeat_byte(0));
633 let c_b = CandidateHash(Hash::repeat_byte(1));
634
635 let relay_vrf_story = RelayVRFStory([42u8; 32]);
636 let assignments = compute_assignments(
637 &keystore,
638 relay_vrf_story,
639 &Config {
640 assignment_keys: assignment_keys(&[
641 Sr25519Keyring::Alice,
642 Sr25519Keyring::Bob,
643 Sr25519Keyring::Charlie,
644 ]),
645 validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
646 vec![ValidatorIndex(0)],
647 vec![ValidatorIndex(1), ValidatorIndex(2)],
648 ]),
649 n_cores: 2,
650 zeroth_delay_tranche_width: 10,
651 relay_vrf_modulo_samples: 10,
652 n_delay_tranches: 40,
653 },
654 vec![(c_a, CoreIndex(0), GroupIndex(1)), (c_b, CoreIndex(1), GroupIndex(0))],
655 );
656
657 assert_eq!(assignments.len(), 1);
660 assert!(assignments.get(&CoreIndex(0)).is_some());
661 }
662
663 #[test]
664 fn assign_to_nonzero_core() {
665 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
666
667 let c_a = CandidateHash(Hash::repeat_byte(0));
668 let c_b = CandidateHash(Hash::repeat_byte(1));
669
670 let relay_vrf_story = RelayVRFStory([42u8; 32]);
671 let assignments = compute_assignments(
672 &keystore,
673 relay_vrf_story,
674 &Config {
675 assignment_keys: assignment_keys(&[
676 Sr25519Keyring::Alice,
677 Sr25519Keyring::Bob,
678 Sr25519Keyring::Charlie,
679 ]),
680 validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
681 vec![ValidatorIndex(0)],
682 vec![ValidatorIndex(1), ValidatorIndex(2)],
683 ]),
684 n_cores: 2,
685 zeroth_delay_tranche_width: 10,
686 relay_vrf_modulo_samples: 10,
687 n_delay_tranches: 40,
688 },
689 vec![(c_a, CoreIndex(0), GroupIndex(0)), (c_b, CoreIndex(1), GroupIndex(1))],
690 );
691
692 assert_eq!(assignments.len(), 1);
693 assert!(assignments.get(&CoreIndex(1)).is_some());
694 }
695
696 #[test]
697 fn succeeds_empty_for_0_cores() {
698 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
699
700 let relay_vrf_story = RelayVRFStory([42u8; 32]);
701 let assignments = compute_assignments(
702 &keystore,
703 relay_vrf_story,
704 &Config {
705 assignment_keys: assignment_keys(&[
706 Sr25519Keyring::Alice,
707 Sr25519Keyring::Bob,
708 Sr25519Keyring::Charlie,
709 ]),
710 validator_groups: Default::default(),
711 n_cores: 0,
712 zeroth_delay_tranche_width: 10,
713 relay_vrf_modulo_samples: 10,
714 n_delay_tranches: 40,
715 },
716 vec![],
717 );
718
719 assert!(assignments.is_empty());
720 }
721
722 #[derive(Debug)]
723 struct MutatedAssignment {
724 cores: CoreBitfield,
725 cert: AssignmentCertV2,
726 groups: Vec<GroupIndex>,
727 own_group: GroupIndex,
728 val_index: ValidatorIndex,
729 config: Config,
730 }
731
732 fn check_mutated_assignments(
734 n_validators: usize,
735 n_cores: usize,
736 rotation_offset: usize,
737 f: impl Fn(&mut MutatedAssignment) -> Option<bool>, ) {
739 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
740
741 let group_for_core = |i| GroupIndex(((i + rotation_offset) % n_cores) as _);
742
743 let config = Config {
744 assignment_keys: assignment_keys_plus_random(
745 &[Sr25519Keyring::Alice],
746 n_validators - 1,
747 ),
748 validator_groups: basic_groups(n_validators, n_cores),
749 n_cores: n_cores as u32,
750 zeroth_delay_tranche_width: 10,
751 relay_vrf_modulo_samples: 15,
752 n_delay_tranches: 40,
753 };
754
755 let relay_vrf_story = RelayVRFStory([42u8; 32]);
756 let assignments = compute_assignments(
757 &keystore,
758 relay_vrf_story.clone(),
759 &config,
760 (0..n_cores)
761 .map(|i| {
762 (
763 CandidateHash(Hash::repeat_byte(i as u8)),
764 CoreIndex(i as u32),
765 group_for_core(i),
766 )
767 })
768 .collect::<Vec<_>>(),
769 );
770
771 let mut counted = 0;
772 for (_core, assignment) in assignments {
773 let cores = match assignment.cert().kind.clone() {
774 AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => core_bitfield,
775 AssignmentCertKindV2::RelayVRFDelay { core_index } => core_index.into(),
776 };
777
778 let mut mutated = MutatedAssignment {
779 cores: cores.clone(),
780 groups: cores.iter_ones().map(|core| group_for_core(core)).collect(),
781 cert: assignment.into_cert(),
782 own_group: GroupIndex(0),
783 val_index: ValidatorIndex(0),
784 config: config.clone(),
785 };
786 let expected = match f(&mut mutated) {
787 None => continue,
788 Some(e) => e,
789 };
790
791 counted += 1;
792
793 let is_good = check_assignment_cert(
794 mutated.cores,
795 mutated.val_index,
796 &mutated.config,
797 relay_vrf_story.clone(),
798 &mutated.cert,
799 mutated.groups,
800 )
801 .is_ok();
802
803 assert_eq!(expected, is_good);
804 }
805
806 assert!(counted > 0);
807 }
808
809 #[test]
810 fn computed_assignments_pass_checks() {
811 check_mutated_assignments(200, 100, 25, |_| Some(true));
812 }
813
814 #[test]
815 fn check_rejects_claimed_core_out_of_bounds() {
816 check_mutated_assignments(200, 100, 25, |m| {
817 m.cores = CoreIndex(100).into();
818 Some(false)
819 });
820 }
821
822 #[test]
823 fn check_rejects_in_backing_group() {
824 check_mutated_assignments(200, 100, 25, |m| {
825 m.groups[0] = m.own_group;
826 Some(false)
827 });
828 }
829
830 #[test]
831 fn check_rejects_nonexistent_key() {
832 check_mutated_assignments(200, 100, 25, |m| {
833 m.val_index.0 += 200;
834 Some(false)
835 });
836 }
837
838 #[test]
839 fn check_rejects_delay_bad_vrf() {
840 check_mutated_assignments(40, 100, 8, |m| {
841 let vrf_signature = garbage_vrf_signature();
842 match m.cert.kind.clone() {
843 AssignmentCertKindV2::RelayVRFDelay { .. } => {
844 m.cert.vrf = vrf_signature;
845 Some(false)
846 },
847 _ => None, }
849 });
850 }
851
852 #[test]
853 fn check_rejects_modulo_bad_vrf() {
854 check_mutated_assignments(200, 100, 25, |m| {
855 let vrf_signature = garbage_vrf_signature();
856 match m.cert.kind.clone() {
857 AssignmentCertKindV2::RelayVRFModuloCompact { .. } => {
858 m.cert.vrf = vrf_signature;
859 Some(false)
860 },
861 _ => None, }
863 });
864 }
865
866 #[test]
867 fn check_rejects_delay_claimed_core_wrong() {
868 check_mutated_assignments(200, 100, 25, |m| {
869 match m.cert.kind.clone() {
870 AssignmentCertKindV2::RelayVRFDelay { .. } => {
871 m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into();
875 Some(false)
876 },
877 _ => None, }
879 });
880 }
881
882 #[test]
883 fn check_rejects_modulo_core_wrong() {
884 check_mutated_assignments(200, 100, 25, |m| {
885 match m.cert.kind.clone() {
886 AssignmentCertKindV2::RelayVRFModuloCompact { .. } => {
887 m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into();
888
889 Some(false)
890 },
891 _ => None, }
893 });
894 }
895
896 #[test]
897 fn generate_samples_invariant() {
898 let seed = [
899 1, 0, 52, 0, 0, 0, 0, 0, 1, 0, 10, 0, 22, 32, 0, 0, 2, 0, 55, 49, 0, 11, 0, 0, 3, 0, 0,
900 0, 0, 0, 2, 92,
901 ];
902 let rand_chacha = ChaCha20Rng::from_seed(seed);
903
904 let samples = generate_samples(rand_chacha.clone(), 6, 100);
905 let expected = vec![19, 79, 17, 75, 66, 30].into_iter().map(Into::into).collect_vec();
906 assert_eq!(samples, expected);
907
908 let samples = generate_samples(rand_chacha.clone(), 6, 7);
909 let expected = vec![0, 3, 6, 5, 4, 2].into_iter().map(Into::into).collect_vec();
910 assert_eq!(samples, expected);
911
912 let samples = generate_samples(rand_chacha.clone(), 6, 12);
913 let expected = vec![2, 4, 7, 5, 11, 3].into_iter().map(Into::into).collect_vec();
914 assert_eq!(samples, expected);
915
916 let samples = generate_samples(rand_chacha.clone(), 1, 100);
917 let expected = vec![30].into_iter().map(Into::into).collect_vec();
918 assert_eq!(samples, expected);
919
920 let samples = generate_samples(rand_chacha.clone(), 0, 100);
921 let expected = vec![];
922 assert_eq!(samples, expected);
923
924 let samples = generate_samples(rand_chacha, MAX_MODULO_SAMPLES + 1, 100);
925 let expected = vec![
926 42, 54, 55, 93, 64, 27, 49, 15, 83, 71, 62, 1, 43, 77, 97, 41, 7, 69, 0, 88, 59, 14,
927 23, 87, 47, 4, 51, 12, 74, 56, 50, 44, 9, 82, 19, 79, 17, 75, 66, 30,
928 ]
929 .into_iter()
930 .map(Into::into)
931 .collect_vec();
932 assert_eq!(samples, expected);
933 }
934}