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::{AssignmentCert, AssignmentCertKind, 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_v1(relay_vrf_story: RelayVRFStory, sample: u32) -> Transcript {
83 relay_vrf_modulo_transcript_inner(
84 Transcript::new(approval_types::v1::RELAY_VRF_MODULO_CONTEXT),
85 relay_vrf_story,
86 Some(sample),
87 )
88}
89
90fn relay_vrf_modulo_transcript_v2(relay_vrf_story: RelayVRFStory) -> Transcript {
91 relay_vrf_modulo_transcript_inner(
92 Transcript::new(approval_types::v2::RELAY_VRF_MODULO_CONTEXT),
93 relay_vrf_story,
94 None,
95 )
96}
97
98const MAX_MODULO_SAMPLES: usize = 40;
100
101fn relay_vrf_modulo_cores(
104 vrf_in_out: &VRFInOut,
105 num_samples: u32,
107 max_cores: u32,
109) -> Vec<CoreIndex> {
110 let rand_chacha =
111 ChaCha20Rng::from_seed(vrf_in_out.make_bytes::<<ChaCha20Rng as SeedableRng>::Seed>(
112 approval_types::v2::CORE_RANDOMNESS_CONTEXT,
113 ));
114 generate_samples(rand_chacha, num_samples as usize, max_cores as usize)
115}
116
117fn generate_samples(
125 mut rand_chacha: ChaCha20Rng,
126 num_samples: usize,
127 max_cores: usize,
128) -> Vec<CoreIndex> {
129 if num_samples as usize > MAX_MODULO_SAMPLES {
130 gum::warn!(
131 target: LOG_TARGET,
132 n_cores = max_cores,
133 num_samples,
134 max_modulo_samples = MAX_MODULO_SAMPLES,
135 "`num_samples` is greater than `MAX_MODULO_SAMPLES`",
136 );
137 }
138
139 if 2 * num_samples > max_cores {
140 gum::debug!(
141 target: LOG_TARGET,
142 n_cores = max_cores,
143 num_samples,
144 max_modulo_samples = MAX_MODULO_SAMPLES,
145 "Suboptimal configuration `num_samples` should be less than `n_cores` / 2",
146 );
147 }
148
149 let num_samples = min(MAX_MODULO_SAMPLES, min(num_samples, max_cores));
150
151 let mut random_cores = (0..max_cores as u32).map(|val| val.into()).collect::<Vec<CoreIndex>>();
152 let (samples, _) = random_cores.partial_shuffle(&mut rand_chacha, num_samples as usize);
153 samples.into_iter().map(|val| *val).collect_vec()
154}
155
156fn relay_vrf_modulo_core(vrf_in_out: &VRFInOut, n_cores: u32) -> CoreIndex {
157 let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::v1::CORE_RANDOMNESS_CONTEXT);
158
159 let random_core = u32::from_le_bytes(bytes) % n_cores;
161 CoreIndex(random_core)
162}
163
164fn relay_vrf_delay_transcript(relay_vrf_story: RelayVRFStory, core_index: CoreIndex) -> Transcript {
165 let mut t = Transcript::new(approval_types::v1::RELAY_VRF_DELAY_CONTEXT);
166 t.append_message(b"RC-VRF", &relay_vrf_story.0);
167 core_index.0.using_encoded(|s| t.append_message(b"core", s));
168 t
169}
170
171fn relay_vrf_delay_tranche(
172 vrf_in_out: &VRFInOut,
173 num_delay_tranches: u32,
174 zeroth_delay_tranche_width: u32,
175) -> DelayTranche {
176 let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::v1::TRANCHE_RANDOMNESS_CONTEXT);
177
178 let wide_tranche =
180 u32::from_le_bytes(bytes) % (num_delay_tranches + zeroth_delay_tranche_width);
181
182 wide_tranche.saturating_sub(zeroth_delay_tranche_width)
184}
185
186fn assigned_core_transcript(core_index: CoreIndex) -> Transcript {
187 let mut t = Transcript::new(approval_types::v1::ASSIGNED_CORE_CONTEXT);
188 core_index.0.using_encoded(|s| t.append_message(b"core", s));
189 t
190}
191
192pub struct RealAssignmentCriteria;
193
194impl AssignmentCriteria for RealAssignmentCriteria {
195 fn compute_assignments(
196 &self,
197 keystore: &LocalKeystore,
198 relay_vrf_story: RelayVRFStory,
199 config: &Config,
200 leaving_cores: Vec<(CandidateHash, CoreIndex, GroupIndex)>,
201 enable_v2_assignments: bool,
202 ) -> HashMap<CoreIndex, OurAssignment> {
203 compute_assignments(keystore, relay_vrf_story, config, leaving_cores, enable_v2_assignments)
204 }
205
206 fn check_assignment_cert(
207 &self,
208 claimed_core_bitfield: CoreBitfield,
209 validator_index: ValidatorIndex,
210 config: &Config,
211 relay_vrf_story: RelayVRFStory,
212 assignment: &AssignmentCertV2,
213 backing_groups: Vec<GroupIndex>,
214 ) -> Result<DelayTranche, InvalidAssignment> {
215 check_assignment_cert(
216 claimed_core_bitfield,
217 validator_index,
218 config,
219 relay_vrf_story,
220 assignment,
221 backing_groups,
222 )
223 }
224}
225
226pub fn compute_assignments(
238 keystore: &LocalKeystore,
239 relay_vrf_story: RelayVRFStory,
240 config: &Config,
241 leaving_cores: impl IntoIterator<Item = (CandidateHash, CoreIndex, GroupIndex)> + Clone,
242 enable_v2_assignments: bool,
243) -> HashMap<CoreIndex, OurAssignment> {
244 if config.n_cores == 0 ||
245 config.assignment_keys.is_empty() ||
246 config.validator_groups.is_empty()
247 {
248 gum::trace!(
249 target: LOG_TARGET,
250 n_cores = config.n_cores,
251 has_assignment_keys = !config.assignment_keys.is_empty(),
252 has_validator_groups = !config.validator_groups.is_empty(),
253 "Not producing assignments because config is degenerate",
254 );
255
256 return HashMap::new();
257 }
258
259 let (index, assignments_key): (ValidatorIndex, AssignmentPair) = {
260 let key = config.assignment_keys.iter().enumerate().find_map(|(i, p)| {
261 match keystore.key_pair(p) {
262 Ok(Some(pair)) => Some((ValidatorIndex(i as _), pair)),
263 Ok(None) => None,
264 Err(sc_keystore::Error::Unavailable) => None,
265 Err(sc_keystore::Error::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => None,
266 Err(e) => {
267 gum::warn!(target: LOG_TARGET, "Encountered keystore error: {:?}", e);
268 None
269 },
270 }
271 });
272
273 match key {
274 None => {
275 gum::trace!(target: LOG_TARGET, "No assignment key");
276 return HashMap::new();
277 },
278 Some(k) => k,
279 }
280 };
281
282 let leaving_cores = leaving_cores
284 .into_iter()
285 .filter(|(_, _, g)| !is_in_backing_group(&config.validator_groups, index, *g))
286 .map(|(c_hash, core, _)| (c_hash, core))
287 .collect::<Vec<_>>();
288
289 gum::trace!(
290 target: LOG_TARGET,
291 assignable_cores = leaving_cores.len(),
292 "Assigning to candidates from different backing groups"
293 );
294
295 let assignments_key: &sp_application_crypto::sr25519::Pair = assignments_key.as_ref();
296 let assignments_key: &schnorrkel::Keypair = assignments_key.as_ref();
297
298 let mut assignments = HashMap::new();
299
300 if enable_v2_assignments {
302 compute_relay_vrf_modulo_assignments_v2(
303 &assignments_key,
304 index,
305 config,
306 relay_vrf_story.clone(),
307 leaving_cores.clone(),
308 &mut assignments,
309 );
310 } else {
311 compute_relay_vrf_modulo_assignments_v1(
312 &assignments_key,
313 index,
314 config,
315 relay_vrf_story.clone(),
316 leaving_cores.clone(),
317 &mut assignments,
318 );
319 }
320
321 compute_relay_vrf_delay_assignments(
323 &assignments_key,
324 index,
325 config,
326 relay_vrf_story,
327 leaving_cores,
328 &mut assignments,
329 );
330
331 assignments
332}
333
334fn compute_relay_vrf_modulo_assignments_v1(
335 assignments_key: &schnorrkel::Keypair,
336 validator_index: ValidatorIndex,
337 config: &Config,
338 relay_vrf_story: RelayVRFStory,
339 leaving_cores: impl IntoIterator<Item = (CandidateHash, CoreIndex)> + Clone,
340 assignments: &mut HashMap<CoreIndex, OurAssignment>,
341) {
342 for rvm_sample in 0..config.relay_vrf_modulo_samples {
343 let mut core = CoreIndex::default();
344
345 let maybe_assignment = {
346 let core = &mut core;
349 assignments_key.vrf_sign_extra_after_check(
350 relay_vrf_modulo_transcript_v1(relay_vrf_story.clone(), rvm_sample),
351 |vrf_in_out| {
352 *core = relay_vrf_modulo_core(&vrf_in_out, config.n_cores);
353 if let Some((candidate_hash, _)) =
354 leaving_cores.clone().into_iter().find(|(_, c)| c == core)
355 {
356 gum::trace!(
357 target: LOG_TARGET,
358 ?candidate_hash,
359 ?core,
360 ?validator_index,
361 tranche = 0,
362 "RelayVRFModulo Assignment."
363 );
364
365 Some(assigned_core_transcript(*core))
366 } else {
367 None
368 }
369 },
370 )
371 };
372
373 if let Some((vrf_in_out, vrf_proof, _)) = maybe_assignment {
374 let cert = AssignmentCert {
377 kind: AssignmentCertKind::RelayVRFModulo { sample: rvm_sample },
378 vrf: VrfSignature {
379 pre_output: VrfPreOutput(vrf_in_out.to_preout()),
380 proof: VrfProof(vrf_proof),
381 },
382 };
383
384 assignments.entry(core).or_insert(OurAssignment::new(
386 cert.into(),
387 0,
388 validator_index,
389 false,
390 ));
391 }
392 }
393}
394
395fn assigned_cores_transcript(core_bitfield: &CoreBitfield) -> Transcript {
396 let mut t = Transcript::new(approval_types::v2::ASSIGNED_CORE_CONTEXT);
397 core_bitfield.using_encoded(|s| t.append_message(b"cores", s));
398 t
399}
400
401fn compute_relay_vrf_modulo_assignments_v2(
402 assignments_key: &schnorrkel::Keypair,
403 validator_index: ValidatorIndex,
404 config: &Config,
405 relay_vrf_story: RelayVRFStory,
406 leaving_cores: Vec<(CandidateHash, CoreIndex)>,
407 assignments: &mut HashMap<CoreIndex, OurAssignment>,
408) {
409 let mut assigned_cores = Vec::new();
410 let leaving_cores = leaving_cores.iter().map(|(_, core)| core).collect::<Vec<_>>();
411
412 let maybe_assignment = {
413 let assigned_cores = &mut assigned_cores;
414 assignments_key.vrf_sign_extra_after_check(
415 relay_vrf_modulo_transcript_v2(relay_vrf_story.clone()),
416 |vrf_in_out| {
417 *assigned_cores = relay_vrf_modulo_cores(
418 &vrf_in_out,
419 config.relay_vrf_modulo_samples,
420 config.n_cores,
421 )
422 .into_iter()
423 .filter(|core| leaving_cores.contains(&core))
424 .collect::<Vec<CoreIndex>>();
425
426 if !assigned_cores.is_empty() {
427 gum::trace!(
428 target: LOG_TARGET,
429 ?assigned_cores,
430 ?validator_index,
431 tranche = 0,
432 "RelayVRFModuloCompact Assignment."
433 );
434
435 let assignment_bitfield: CoreBitfield = assigned_cores
436 .clone()
437 .try_into()
438 .expect("Just checked `!assigned_cores.is_empty()`; qed");
439
440 Some(assigned_cores_transcript(&assignment_bitfield))
441 } else {
442 None
443 }
444 },
445 )
446 };
447
448 if let Some(assignment) = maybe_assignment.map(|(vrf_in_out, vrf_proof, _)| {
449 let assignment_bitfield: CoreBitfield = assigned_cores
450 .clone()
451 .try_into()
452 .expect("Just checked `!assigned_cores.is_empty()`; qed");
453
454 let cert = AssignmentCertV2 {
455 kind: AssignmentCertKindV2::RelayVRFModuloCompact {
456 core_bitfield: assignment_bitfield.clone(),
457 },
458 vrf: VrfSignature {
459 pre_output: VrfPreOutput(vrf_in_out.to_preout()),
460 proof: VrfProof(vrf_proof),
461 },
462 };
463
464 OurAssignment::new(cert, 0, validator_index, false)
466 }) {
467 for core_index in assigned_cores {
468 assignments.insert(core_index, assignment.clone());
469 }
470 }
471}
472
473fn compute_relay_vrf_delay_assignments(
474 assignments_key: &schnorrkel::Keypair,
475 validator_index: ValidatorIndex,
476 config: &Config,
477 relay_vrf_story: RelayVRFStory,
478 leaving_cores: impl IntoIterator<Item = (CandidateHash, CoreIndex)>,
479 assignments: &mut HashMap<CoreIndex, OurAssignment>,
480) {
481 for (candidate_hash, core) in leaving_cores {
482 let (vrf_in_out, vrf_proof, _) =
483 assignments_key.vrf_sign(relay_vrf_delay_transcript(relay_vrf_story.clone(), core));
484
485 let tranche = relay_vrf_delay_tranche(
486 &vrf_in_out,
487 config.n_delay_tranches,
488 config.zeroth_delay_tranche_width,
489 );
490
491 let cert = AssignmentCertV2 {
492 kind: AssignmentCertKindV2::RelayVRFDelay { core_index: core },
493 vrf: VrfSignature {
494 pre_output: VrfPreOutput(vrf_in_out.to_preout()),
495 proof: VrfProof(vrf_proof),
496 },
497 };
498
499 let our_assignment = OurAssignment::new(cert, tranche, validator_index, false);
500
501 let used = match assignments.entry(core) {
502 Entry::Vacant(e) => {
503 let _ = e.insert(our_assignment);
504 true
505 },
506 Entry::Occupied(mut e) => {
507 if e.get().tranche() > our_assignment.tranche() {
508 e.insert(our_assignment);
509 true
510 } else {
511 false
512 }
513 },
514 };
515
516 if used {
517 gum::trace!(
518 target: LOG_TARGET,
519 ?candidate_hash,
520 ?core,
521 ?validator_index,
522 tranche,
523 "RelayVRFDelay Assignment",
524 );
525 }
526 }
527}
528
529pub(crate) fn check_assignment_cert(
541 claimed_core_indices: CoreBitfield,
542 validator_index: ValidatorIndex,
543 config: &Config,
544 relay_vrf_story: RelayVRFStory,
545 assignment: &AssignmentCertV2,
546 backing_groups: Vec<GroupIndex>,
547) -> Result<DelayTranche, InvalidAssignment> {
548 use InvalidAssignmentReason as Reason;
549
550 let validator_public = config
551 .assignment_keys
552 .get(validator_index.0 as usize)
553 .ok_or(InvalidAssignment(Reason::ValidatorIndexOutOfBounds))?;
554
555 let public = schnorrkel::PublicKey::from_bytes(validator_public.as_slice())
556 .map_err(|_| InvalidAssignment(Reason::InvalidAssignmentKey))?;
557
558 if claimed_core_indices.count_ones() == 0 ||
560 claimed_core_indices.count_ones() != backing_groups.len()
561 {
562 return Err(InvalidAssignment(Reason::InvalidArguments));
563 }
564
565 for (claimed_core, backing_group) in claimed_core_indices.iter_ones().zip(backing_groups.iter())
568 {
569 if claimed_core >= config.n_cores as usize {
570 return Err(InvalidAssignment(Reason::CoreIndexOutOfBounds));
571 }
572
573 let is_in_backing =
574 is_in_backing_group(&config.validator_groups, validator_index, *backing_group);
575
576 if is_in_backing {
577 return Err(InvalidAssignment(Reason::IsInBackingGroup));
578 }
579 }
580
581 let vrf_pre_output = &assignment.vrf.pre_output;
582 let vrf_proof = &assignment.vrf.proof;
583 let first_claimed_core_index =
584 claimed_core_indices.first_one().expect("Checked above; qed") as u32;
585
586 match &assignment.kind {
587 AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => {
588 if &claimed_core_indices != core_bitfield {
590 return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch));
591 }
592
593 let (vrf_in_out, _) = public
594 .vrf_verify_extra(
595 relay_vrf_modulo_transcript_v2(relay_vrf_story),
596 &vrf_pre_output.0,
597 &vrf_proof.0,
598 assigned_cores_transcript(core_bitfield),
599 )
600 .map_err(|_| InvalidAssignment(Reason::VRFModuloOutputMismatch))?;
601
602 let resulting_cores = relay_vrf_modulo_cores(
603 &vrf_in_out,
604 config.relay_vrf_modulo_samples,
605 config.n_cores,
606 );
607
608 for claimed_core_index in claimed_core_indices.iter_ones() {
613 if !resulting_cores.contains(&CoreIndex(claimed_core_index as u32)) {
614 gum::debug!(
615 target: LOG_TARGET,
616 ?resulting_cores,
617 ?claimed_core_indices,
618 vrf_modulo_cores = ?resulting_cores,
619 "Assignment claimed cores mismatch",
620 );
621 return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch));
622 }
623 }
624
625 Ok(0)
626 },
627 AssignmentCertKindV2::RelayVRFModulo { sample } => {
628 if *sample >= config.relay_vrf_modulo_samples {
629 return Err(InvalidAssignment(Reason::SampleOutOfBounds));
630 }
631
632 if claimed_core_indices.count_ones() != 1 {
634 gum::warn!(
635 target: LOG_TARGET,
636 ?claimed_core_indices,
637 "`RelayVRFModulo` assignment must always claim 1 core",
638 );
639 return Err(InvalidAssignment(Reason::InvalidArguments));
640 }
641
642 let (vrf_in_out, _) = public
643 .vrf_verify_extra(
644 relay_vrf_modulo_transcript_v1(relay_vrf_story, *sample),
645 &vrf_pre_output.0,
646 &vrf_proof.0,
647 assigned_core_transcript(CoreIndex(first_claimed_core_index)),
648 )
649 .map_err(|_| InvalidAssignment(Reason::VRFModuloOutputMismatch))?;
650
651 let core = relay_vrf_modulo_core(&vrf_in_out, config.n_cores);
652 if core.0 == first_claimed_core_index {
654 Ok(0)
655 } else {
656 gum::debug!(
657 target: LOG_TARGET,
658 ?core,
659 ?claimed_core_indices,
660 "Assignment claimed cores mismatch",
661 );
662 Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch))
663 }
664 },
665 AssignmentCertKindV2::RelayVRFDelay { core_index } => {
666 if claimed_core_indices.count_ones() != 1 {
668 gum::debug!(
669 target: LOG_TARGET,
670 ?claimed_core_indices,
671 "`RelayVRFDelay` assignment must always claim 1 core",
672 );
673 return Err(InvalidAssignment(Reason::InvalidArguments));
674 }
675
676 if core_index.0 != first_claimed_core_index {
677 return Err(InvalidAssignment(Reason::VRFDelayCoreIndexMismatch));
678 }
679
680 let (vrf_in_out, _) = public
681 .vrf_verify(
682 relay_vrf_delay_transcript(relay_vrf_story, *core_index),
683 &vrf_pre_output.0,
684 &vrf_proof.0,
685 )
686 .map_err(|_| InvalidAssignment(Reason::VRFDelayOutputMismatch))?;
687
688 Ok(relay_vrf_delay_tranche(
689 &vrf_in_out,
690 config.n_delay_tranches,
691 config.zeroth_delay_tranche_width,
692 ))
693 },
694 }
695}
696
697fn is_in_backing_group(
698 validator_groups: &IndexedVec<GroupIndex, Vec<ValidatorIndex>>,
699 validator: ValidatorIndex,
700 group: GroupIndex,
701) -> bool {
702 validator_groups.get(group).map_or(false, |g| g.contains(&validator))
703}
704
705impl From<crate::approval_db::v1::OurAssignment> for OurAssignment {
707 fn from(value: crate::approval_db::v1::OurAssignment) -> Self {
708 Self::new(
709 value.cert.into(),
710 value.tranche,
711 value.validator_index,
712 value.triggered,
714 )
715 }
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721 use crate::import::tests::garbage_vrf_signature;
722 use polkadot_primitives::{AssignmentId, Hash, ASSIGNMENT_KEY_TYPE_ID};
723 use sp_application_crypto::sr25519;
724 use sp_core::crypto::Pair as PairT;
725 use sp_keyring::sr25519::Keyring as Sr25519Keyring;
726 use sp_keystore::Keystore;
727
728 fn make_keystore(accounts: &[Sr25519Keyring]) -> LocalKeystore {
730 let store = LocalKeystore::in_memory();
731
732 for s in accounts.iter().copied().map(|k| k.to_seed()) {
733 store.sr25519_generate_new(ASSIGNMENT_KEY_TYPE_ID, Some(s.as_str())).unwrap();
734 }
735
736 store
737 }
738
739 fn assignment_keys(accounts: &[Sr25519Keyring]) -> Vec<AssignmentId> {
740 assignment_keys_plus_random(accounts, 0)
741 }
742
743 fn assignment_keys_plus_random(
744 accounts: &[Sr25519Keyring],
745 random: usize,
746 ) -> Vec<AssignmentId> {
747 let gen_random =
748 (0..random).map(|_| AssignmentId::from(sr25519::Pair::generate().0.public()));
749
750 accounts
751 .iter()
752 .map(|k| AssignmentId::from(k.public()))
753 .chain(gen_random)
754 .collect()
755 }
756
757 fn basic_groups(
758 n_validators: usize,
759 n_groups: usize,
760 ) -> IndexedVec<GroupIndex, Vec<ValidatorIndex>> {
761 let size = n_validators / n_groups;
762 let big_groups = n_validators % n_groups;
763 let scraps = n_groups * size;
764
765 (0..n_groups)
766 .map(|i| {
767 (i * size..(i + 1) * size)
768 .chain(if i < big_groups { Some(scraps + i) } else { None })
769 .map(|j| ValidatorIndex(j as _))
770 .collect::<Vec<_>>()
771 })
772 .collect()
773 }
774
775 #[test]
776 fn assignments_produced_for_non_backing() {
777 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
778
779 let c_a = CandidateHash(Hash::repeat_byte(0));
780 let c_b = CandidateHash(Hash::repeat_byte(1));
781
782 let relay_vrf_story = RelayVRFStory([42u8; 32]);
783 let assignments = compute_assignments(
784 &keystore,
785 relay_vrf_story,
786 &Config {
787 assignment_keys: assignment_keys(&[
788 Sr25519Keyring::Alice,
789 Sr25519Keyring::Bob,
790 Sr25519Keyring::Charlie,
791 ]),
792 validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
793 vec![ValidatorIndex(0)],
794 vec![ValidatorIndex(1), ValidatorIndex(2)],
795 ]),
796 n_cores: 2,
797 zeroth_delay_tranche_width: 10,
798 relay_vrf_modulo_samples: 10,
799 n_delay_tranches: 40,
800 },
801 vec![(c_a, CoreIndex(0), GroupIndex(1)), (c_b, CoreIndex(1), GroupIndex(0))],
802 false,
803 );
804
805 assert_eq!(assignments.len(), 1);
808 assert!(assignments.get(&CoreIndex(0)).is_some());
809 }
810
811 #[test]
812 fn assign_to_nonzero_core() {
813 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
814
815 let c_a = CandidateHash(Hash::repeat_byte(0));
816 let c_b = CandidateHash(Hash::repeat_byte(1));
817
818 let relay_vrf_story = RelayVRFStory([42u8; 32]);
819 let assignments = compute_assignments(
820 &keystore,
821 relay_vrf_story,
822 &Config {
823 assignment_keys: assignment_keys(&[
824 Sr25519Keyring::Alice,
825 Sr25519Keyring::Bob,
826 Sr25519Keyring::Charlie,
827 ]),
828 validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
829 vec![ValidatorIndex(0)],
830 vec![ValidatorIndex(1), ValidatorIndex(2)],
831 ]),
832 n_cores: 2,
833 zeroth_delay_tranche_width: 10,
834 relay_vrf_modulo_samples: 10,
835 n_delay_tranches: 40,
836 },
837 vec![(c_a, CoreIndex(0), GroupIndex(0)), (c_b, CoreIndex(1), GroupIndex(1))],
838 false,
839 );
840
841 assert_eq!(assignments.len(), 1);
842 assert!(assignments.get(&CoreIndex(1)).is_some());
843 }
844
845 #[test]
846 fn succeeds_empty_for_0_cores() {
847 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
848
849 let relay_vrf_story = RelayVRFStory([42u8; 32]);
850 let assignments = compute_assignments(
851 &keystore,
852 relay_vrf_story,
853 &Config {
854 assignment_keys: assignment_keys(&[
855 Sr25519Keyring::Alice,
856 Sr25519Keyring::Bob,
857 Sr25519Keyring::Charlie,
858 ]),
859 validator_groups: Default::default(),
860 n_cores: 0,
861 zeroth_delay_tranche_width: 10,
862 relay_vrf_modulo_samples: 10,
863 n_delay_tranches: 40,
864 },
865 vec![],
866 false,
867 );
868
869 assert!(assignments.is_empty());
870 }
871
872 #[derive(Debug)]
873 struct MutatedAssignment {
874 cores: CoreBitfield,
875 cert: AssignmentCertV2,
876 groups: Vec<GroupIndex>,
877 own_group: GroupIndex,
878 val_index: ValidatorIndex,
879 config: Config,
880 }
881
882 fn check_mutated_assignments(
884 n_validators: usize,
885 n_cores: usize,
886 rotation_offset: usize,
887 f: impl Fn(&mut MutatedAssignment) -> Option<bool>, ) {
889 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
890
891 let group_for_core = |i| GroupIndex(((i + rotation_offset) % n_cores) as _);
892
893 let config = Config {
894 assignment_keys: assignment_keys_plus_random(
895 &[Sr25519Keyring::Alice],
896 n_validators - 1,
897 ),
898 validator_groups: basic_groups(n_validators, n_cores),
899 n_cores: n_cores as u32,
900 zeroth_delay_tranche_width: 10,
901 relay_vrf_modulo_samples: 15,
902 n_delay_tranches: 40,
903 };
904
905 let relay_vrf_story = RelayVRFStory([42u8; 32]);
906 let mut assignments = compute_assignments(
907 &keystore,
908 relay_vrf_story.clone(),
909 &config,
910 (0..n_cores)
911 .map(|i| {
912 (
913 CandidateHash(Hash::repeat_byte(i as u8)),
914 CoreIndex(i as u32),
915 group_for_core(i),
916 )
917 })
918 .collect::<Vec<_>>(),
919 false,
920 );
921
922 assignments.extend(compute_assignments(
924 &keystore,
925 relay_vrf_story.clone(),
926 &config,
927 (0..n_cores)
928 .map(|i| {
929 (
930 CandidateHash(Hash::repeat_byte(i as u8)),
931 CoreIndex(i as u32),
932 group_for_core(i),
933 )
934 })
935 .collect::<Vec<_>>(),
936 true,
937 ));
938
939 let mut counted = 0;
940 for (core, assignment) in assignments {
941 let cores = match assignment.cert().kind.clone() {
942 AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => core_bitfield,
943 AssignmentCertKindV2::RelayVRFModulo { sample: _ } => core.into(),
944 AssignmentCertKindV2::RelayVRFDelay { core_index } => core_index.into(),
945 };
946
947 let mut mutated = MutatedAssignment {
948 cores: cores.clone(),
949 groups: cores.iter_ones().map(|core| group_for_core(core)).collect(),
950 cert: assignment.into_cert(),
951 own_group: GroupIndex(0),
952 val_index: ValidatorIndex(0),
953 config: config.clone(),
954 };
955 let expected = match f(&mut mutated) {
956 None => continue,
957 Some(e) => e,
958 };
959
960 counted += 1;
961
962 let is_good = check_assignment_cert(
963 mutated.cores,
964 mutated.val_index,
965 &mutated.config,
966 relay_vrf_story.clone(),
967 &mutated.cert,
968 mutated.groups,
969 )
970 .is_ok();
971
972 assert_eq!(expected, is_good);
973 }
974
975 assert!(counted > 0);
976 }
977
978 #[test]
979 fn computed_assignments_pass_checks() {
980 check_mutated_assignments(200, 100, 25, |_| Some(true));
981 }
982
983 #[test]
984 fn check_rejects_claimed_core_out_of_bounds() {
985 check_mutated_assignments(200, 100, 25, |m| {
986 m.cores = CoreIndex(100).into();
987 Some(false)
988 });
989 }
990
991 #[test]
992 fn check_rejects_in_backing_group() {
993 check_mutated_assignments(200, 100, 25, |m| {
994 m.groups[0] = m.own_group;
995 Some(false)
996 });
997 }
998
999 #[test]
1000 fn check_rejects_nonexistent_key() {
1001 check_mutated_assignments(200, 100, 25, |m| {
1002 m.val_index.0 += 200;
1003 Some(false)
1004 });
1005 }
1006
1007 #[test]
1008 fn check_rejects_delay_bad_vrf() {
1009 check_mutated_assignments(40, 100, 8, |m| {
1010 let vrf_signature = garbage_vrf_signature();
1011 match m.cert.kind.clone() {
1012 AssignmentCertKindV2::RelayVRFDelay { .. } => {
1013 m.cert.vrf = vrf_signature;
1014 Some(false)
1015 },
1016 _ => None, }
1018 });
1019 }
1020
1021 #[test]
1022 fn check_rejects_modulo_bad_vrf() {
1023 check_mutated_assignments(200, 100, 25, |m| {
1024 let vrf_signature = garbage_vrf_signature();
1025 match m.cert.kind.clone() {
1026 AssignmentCertKindV2::RelayVRFModulo { .. } => {
1027 m.cert.vrf = vrf_signature;
1028 Some(false)
1029 },
1030 AssignmentCertKindV2::RelayVRFModuloCompact { .. } => {
1031 m.cert.vrf = vrf_signature;
1032 Some(false)
1033 },
1034 _ => None, }
1036 });
1037 }
1038
1039 #[test]
1040 fn check_rejects_modulo_sample_out_of_bounds() {
1041 check_mutated_assignments(200, 100, 25, |m| {
1042 match m.cert.kind.clone() {
1043 AssignmentCertKindV2::RelayVRFModulo { sample } => {
1044 m.config.relay_vrf_modulo_samples = sample;
1045 Some(false)
1046 },
1047 AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield: _ } => Some(true),
1048 _ => None, }
1050 });
1051 }
1052
1053 #[test]
1054 fn check_rejects_delay_claimed_core_wrong() {
1055 check_mutated_assignments(200, 100, 25, |m| {
1056 match m.cert.kind.clone() {
1057 AssignmentCertKindV2::RelayVRFDelay { .. } => {
1058 m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into();
1062 Some(false)
1063 },
1064 _ => None, }
1066 });
1067 }
1068
1069 #[test]
1070 fn check_rejects_modulo_core_wrong() {
1071 check_mutated_assignments(200, 100, 25, |m| {
1072 match m.cert.kind.clone() {
1073 AssignmentCertKindV2::RelayVRFModulo { .. } |
1074 AssignmentCertKindV2::RelayVRFModuloCompact { .. } => {
1075 m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into();
1076
1077 Some(false)
1078 },
1079 _ => None, }
1081 });
1082 }
1083
1084 #[test]
1085 fn generate_samples_invariant() {
1086 let seed = [
1087 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,
1088 0, 0, 0, 2, 92,
1089 ];
1090 let rand_chacha = ChaCha20Rng::from_seed(seed);
1091
1092 let samples = generate_samples(rand_chacha.clone(), 6, 100);
1093 let expected = vec![19, 79, 17, 75, 66, 30].into_iter().map(Into::into).collect_vec();
1094 assert_eq!(samples, expected);
1095
1096 let samples = generate_samples(rand_chacha.clone(), 6, 7);
1097 let expected = vec![0, 3, 6, 5, 4, 2].into_iter().map(Into::into).collect_vec();
1098 assert_eq!(samples, expected);
1099
1100 let samples = generate_samples(rand_chacha.clone(), 6, 12);
1101 let expected = vec![2, 4, 7, 5, 11, 3].into_iter().map(Into::into).collect_vec();
1102 assert_eq!(samples, expected);
1103
1104 let samples = generate_samples(rand_chacha.clone(), 1, 100);
1105 let expected = vec![30].into_iter().map(Into::into).collect_vec();
1106 assert_eq!(samples, expected);
1107
1108 let samples = generate_samples(rand_chacha.clone(), 0, 100);
1109 let expected = vec![];
1110 assert_eq!(samples, expected);
1111
1112 let samples = generate_samples(rand_chacha, MAX_MODULO_SAMPLES + 1, 100);
1113 let expected = vec![
1114 42, 54, 55, 93, 64, 27, 49, 15, 83, 71, 62, 1, 43, 77, 97, 41, 7, 69, 0, 88, 59, 14,
1115 23, 87, 47, 4, 51, 12, 74, 56, 50, 44, 9, 82, 19, 79, 17, 75, 66, 30,
1116 ]
1117 .into_iter()
1118 .map(Into::into)
1119 .collect_vec();
1120 assert_eq!(samples, expected);
1121 }
1122}