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 if used {
516 gum::trace!(
517 target: LOG_TARGET,
518 ?candidate_hash,
519 ?core,
520 ?validator_index,
521 tranche,
522 "RelayVRFDelay Assignment",
523 );
524 }
525 }
526}
527
528pub(crate) fn check_assignment_cert(
540 claimed_core_indices: CoreBitfield,
541 validator_index: ValidatorIndex,
542 config: &Config,
543 relay_vrf_story: RelayVRFStory,
544 assignment: &AssignmentCertV2,
545 backing_groups: Vec<GroupIndex>,
546) -> Result<DelayTranche, InvalidAssignment> {
547 use InvalidAssignmentReason as Reason;
548
549 let validator_public = config
550 .assignment_keys
551 .get(validator_index.0 as usize)
552 .ok_or(InvalidAssignment(Reason::ValidatorIndexOutOfBounds))?;
553
554 let public = schnorrkel::PublicKey::from_bytes(validator_public.as_slice())
555 .map_err(|_| InvalidAssignment(Reason::InvalidAssignmentKey))?;
556
557 if claimed_core_indices.count_ones() == 0 ||
559 claimed_core_indices.count_ones() != backing_groups.len()
560 {
561 return Err(InvalidAssignment(Reason::InvalidArguments))
562 }
563
564 for (claimed_core, backing_group) in claimed_core_indices.iter_ones().zip(backing_groups.iter())
567 {
568 if claimed_core >= config.n_cores as usize {
569 return Err(InvalidAssignment(Reason::CoreIndexOutOfBounds))
570 }
571
572 let is_in_backing =
573 is_in_backing_group(&config.validator_groups, validator_index, *backing_group);
574
575 if is_in_backing {
576 return Err(InvalidAssignment(Reason::IsInBackingGroup))
577 }
578 }
579
580 let vrf_pre_output = &assignment.vrf.pre_output;
581 let vrf_proof = &assignment.vrf.proof;
582 let first_claimed_core_index =
583 claimed_core_indices.first_one().expect("Checked above; qed") as u32;
584
585 match &assignment.kind {
586 AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => {
587 if &claimed_core_indices != core_bitfield {
589 return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch))
590 }
591
592 let (vrf_in_out, _) = public
593 .vrf_verify_extra(
594 relay_vrf_modulo_transcript_v2(relay_vrf_story),
595 &vrf_pre_output.0,
596 &vrf_proof.0,
597 assigned_cores_transcript(core_bitfield),
598 )
599 .map_err(|_| InvalidAssignment(Reason::VRFModuloOutputMismatch))?;
600
601 let resulting_cores = relay_vrf_modulo_cores(
602 &vrf_in_out,
603 config.relay_vrf_modulo_samples,
604 config.n_cores,
605 );
606
607 for claimed_core_index in claimed_core_indices.iter_ones() {
612 if !resulting_cores.contains(&CoreIndex(claimed_core_index as u32)) {
613 gum::debug!(
614 target: LOG_TARGET,
615 ?resulting_cores,
616 ?claimed_core_indices,
617 vrf_modulo_cores = ?resulting_cores,
618 "Assignment claimed cores mismatch",
619 );
620 return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch))
621 }
622 }
623
624 Ok(0)
625 },
626 AssignmentCertKindV2::RelayVRFModulo { sample } => {
627 if *sample >= config.relay_vrf_modulo_samples {
628 return Err(InvalidAssignment(Reason::SampleOutOfBounds))
629 }
630
631 if claimed_core_indices.count_ones() != 1 {
633 gum::warn!(
634 target: LOG_TARGET,
635 ?claimed_core_indices,
636 "`RelayVRFModulo` assignment must always claim 1 core",
637 );
638 return Err(InvalidAssignment(Reason::InvalidArguments))
639 }
640
641 let (vrf_in_out, _) = public
642 .vrf_verify_extra(
643 relay_vrf_modulo_transcript_v1(relay_vrf_story, *sample),
644 &vrf_pre_output.0,
645 &vrf_proof.0,
646 assigned_core_transcript(CoreIndex(first_claimed_core_index)),
647 )
648 .map_err(|_| InvalidAssignment(Reason::VRFModuloOutputMismatch))?;
649
650 let core = relay_vrf_modulo_core(&vrf_in_out, config.n_cores);
651 if core.0 == first_claimed_core_index {
653 Ok(0)
654 } else {
655 gum::debug!(
656 target: LOG_TARGET,
657 ?core,
658 ?claimed_core_indices,
659 "Assignment claimed cores mismatch",
660 );
661 Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch))
662 }
663 },
664 AssignmentCertKindV2::RelayVRFDelay { core_index } => {
665 if claimed_core_indices.count_ones() != 1 {
667 gum::debug!(
668 target: LOG_TARGET,
669 ?claimed_core_indices,
670 "`RelayVRFDelay` assignment must always claim 1 core",
671 );
672 return Err(InvalidAssignment(Reason::InvalidArguments))
673 }
674
675 if core_index.0 != first_claimed_core_index {
676 return Err(InvalidAssignment(Reason::VRFDelayCoreIndexMismatch))
677 }
678
679 let (vrf_in_out, _) = public
680 .vrf_verify(
681 relay_vrf_delay_transcript(relay_vrf_story, *core_index),
682 &vrf_pre_output.0,
683 &vrf_proof.0,
684 )
685 .map_err(|_| InvalidAssignment(Reason::VRFDelayOutputMismatch))?;
686
687 Ok(relay_vrf_delay_tranche(
688 &vrf_in_out,
689 config.n_delay_tranches,
690 config.zeroth_delay_tranche_width,
691 ))
692 },
693 }
694}
695
696fn is_in_backing_group(
697 validator_groups: &IndexedVec<GroupIndex, Vec<ValidatorIndex>>,
698 validator: ValidatorIndex,
699 group: GroupIndex,
700) -> bool {
701 validator_groups.get(group).map_or(false, |g| g.contains(&validator))
702}
703
704impl From<crate::approval_db::v1::OurAssignment> for OurAssignment {
706 fn from(value: crate::approval_db::v1::OurAssignment) -> Self {
707 Self::new(
708 value.cert.into(),
709 value.tranche,
710 value.validator_index,
711 value.triggered,
713 )
714 }
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720 use crate::import::tests::garbage_vrf_signature;
721 use polkadot_primitives::{AssignmentId, Hash, ASSIGNMENT_KEY_TYPE_ID};
722 use sp_application_crypto::sr25519;
723 use sp_core::crypto::Pair as PairT;
724 use sp_keyring::sr25519::Keyring as Sr25519Keyring;
725 use sp_keystore::Keystore;
726
727 fn make_keystore(accounts: &[Sr25519Keyring]) -> LocalKeystore {
729 let store = LocalKeystore::in_memory();
730
731 for s in accounts.iter().copied().map(|k| k.to_seed()) {
732 store.sr25519_generate_new(ASSIGNMENT_KEY_TYPE_ID, Some(s.as_str())).unwrap();
733 }
734
735 store
736 }
737
738 fn assignment_keys(accounts: &[Sr25519Keyring]) -> Vec<AssignmentId> {
739 assignment_keys_plus_random(accounts, 0)
740 }
741
742 fn assignment_keys_plus_random(
743 accounts: &[Sr25519Keyring],
744 random: usize,
745 ) -> Vec<AssignmentId> {
746 let gen_random =
747 (0..random).map(|_| AssignmentId::from(sr25519::Pair::generate().0.public()));
748
749 accounts
750 .iter()
751 .map(|k| AssignmentId::from(k.public()))
752 .chain(gen_random)
753 .collect()
754 }
755
756 fn basic_groups(
757 n_validators: usize,
758 n_groups: usize,
759 ) -> IndexedVec<GroupIndex, Vec<ValidatorIndex>> {
760 let size = n_validators / n_groups;
761 let big_groups = n_validators % n_groups;
762 let scraps = n_groups * size;
763
764 (0..n_groups)
765 .map(|i| {
766 (i * size..(i + 1) * size)
767 .chain(if i < big_groups { Some(scraps + i) } else { None })
768 .map(|j| ValidatorIndex(j as _))
769 .collect::<Vec<_>>()
770 })
771 .collect()
772 }
773
774 #[test]
775 fn assignments_produced_for_non_backing() {
776 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
777
778 let c_a = CandidateHash(Hash::repeat_byte(0));
779 let c_b = CandidateHash(Hash::repeat_byte(1));
780
781 let relay_vrf_story = RelayVRFStory([42u8; 32]);
782 let assignments = compute_assignments(
783 &keystore,
784 relay_vrf_story,
785 &Config {
786 assignment_keys: assignment_keys(&[
787 Sr25519Keyring::Alice,
788 Sr25519Keyring::Bob,
789 Sr25519Keyring::Charlie,
790 ]),
791 validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
792 vec![ValidatorIndex(0)],
793 vec![ValidatorIndex(1), ValidatorIndex(2)],
794 ]),
795 n_cores: 2,
796 zeroth_delay_tranche_width: 10,
797 relay_vrf_modulo_samples: 10,
798 n_delay_tranches: 40,
799 },
800 vec![(c_a, CoreIndex(0), GroupIndex(1)), (c_b, CoreIndex(1), GroupIndex(0))],
801 false,
802 );
803
804 assert_eq!(assignments.len(), 1);
807 assert!(assignments.get(&CoreIndex(0)).is_some());
808 }
809
810 #[test]
811 fn assign_to_nonzero_core() {
812 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
813
814 let c_a = CandidateHash(Hash::repeat_byte(0));
815 let c_b = CandidateHash(Hash::repeat_byte(1));
816
817 let relay_vrf_story = RelayVRFStory([42u8; 32]);
818 let assignments = compute_assignments(
819 &keystore,
820 relay_vrf_story,
821 &Config {
822 assignment_keys: assignment_keys(&[
823 Sr25519Keyring::Alice,
824 Sr25519Keyring::Bob,
825 Sr25519Keyring::Charlie,
826 ]),
827 validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
828 vec![ValidatorIndex(0)],
829 vec![ValidatorIndex(1), ValidatorIndex(2)],
830 ]),
831 n_cores: 2,
832 zeroth_delay_tranche_width: 10,
833 relay_vrf_modulo_samples: 10,
834 n_delay_tranches: 40,
835 },
836 vec![(c_a, CoreIndex(0), GroupIndex(0)), (c_b, CoreIndex(1), GroupIndex(1))],
837 false,
838 );
839
840 assert_eq!(assignments.len(), 1);
841 assert!(assignments.get(&CoreIndex(1)).is_some());
842 }
843
844 #[test]
845 fn succeeds_empty_for_0_cores() {
846 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
847
848 let relay_vrf_story = RelayVRFStory([42u8; 32]);
849 let assignments = compute_assignments(
850 &keystore,
851 relay_vrf_story,
852 &Config {
853 assignment_keys: assignment_keys(&[
854 Sr25519Keyring::Alice,
855 Sr25519Keyring::Bob,
856 Sr25519Keyring::Charlie,
857 ]),
858 validator_groups: Default::default(),
859 n_cores: 0,
860 zeroth_delay_tranche_width: 10,
861 relay_vrf_modulo_samples: 10,
862 n_delay_tranches: 40,
863 },
864 vec![],
865 false,
866 );
867
868 assert!(assignments.is_empty());
869 }
870
871 #[derive(Debug)]
872 struct MutatedAssignment {
873 cores: CoreBitfield,
874 cert: AssignmentCertV2,
875 groups: Vec<GroupIndex>,
876 own_group: GroupIndex,
877 val_index: ValidatorIndex,
878 config: Config,
879 }
880
881 fn check_mutated_assignments(
883 n_validators: usize,
884 n_cores: usize,
885 rotation_offset: usize,
886 f: impl Fn(&mut MutatedAssignment) -> Option<bool>, ) {
888 let keystore = make_keystore(&[Sr25519Keyring::Alice]);
889
890 let group_for_core = |i| GroupIndex(((i + rotation_offset) % n_cores) as _);
891
892 let config = Config {
893 assignment_keys: assignment_keys_plus_random(
894 &[Sr25519Keyring::Alice],
895 n_validators - 1,
896 ),
897 validator_groups: basic_groups(n_validators, n_cores),
898 n_cores: n_cores as u32,
899 zeroth_delay_tranche_width: 10,
900 relay_vrf_modulo_samples: 15,
901 n_delay_tranches: 40,
902 };
903
904 let relay_vrf_story = RelayVRFStory([42u8; 32]);
905 let mut assignments = compute_assignments(
906 &keystore,
907 relay_vrf_story.clone(),
908 &config,
909 (0..n_cores)
910 .map(|i| {
911 (
912 CandidateHash(Hash::repeat_byte(i as u8)),
913 CoreIndex(i as u32),
914 group_for_core(i),
915 )
916 })
917 .collect::<Vec<_>>(),
918 false,
919 );
920
921 assignments.extend(compute_assignments(
923 &keystore,
924 relay_vrf_story.clone(),
925 &config,
926 (0..n_cores)
927 .map(|i| {
928 (
929 CandidateHash(Hash::repeat_byte(i as u8)),
930 CoreIndex(i as u32),
931 group_for_core(i),
932 )
933 })
934 .collect::<Vec<_>>(),
935 true,
936 ));
937
938 let mut counted = 0;
939 for (core, assignment) in assignments {
940 let cores = match assignment.cert().kind.clone() {
941 AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => core_bitfield,
942 AssignmentCertKindV2::RelayVRFModulo { sample: _ } => core.into(),
943 AssignmentCertKindV2::RelayVRFDelay { core_index } => core_index.into(),
944 };
945
946 let mut mutated = MutatedAssignment {
947 cores: cores.clone(),
948 groups: cores.iter_ones().map(|core| group_for_core(core)).collect(),
949 cert: assignment.into_cert(),
950 own_group: GroupIndex(0),
951 val_index: ValidatorIndex(0),
952 config: config.clone(),
953 };
954 let expected = match f(&mut mutated) {
955 None => continue,
956 Some(e) => e,
957 };
958
959 counted += 1;
960
961 let is_good = check_assignment_cert(
962 mutated.cores,
963 mutated.val_index,
964 &mutated.config,
965 relay_vrf_story.clone(),
966 &mutated.cert,
967 mutated.groups,
968 )
969 .is_ok();
970
971 assert_eq!(expected, is_good);
972 }
973
974 assert!(counted > 0);
975 }
976
977 #[test]
978 fn computed_assignments_pass_checks() {
979 check_mutated_assignments(200, 100, 25, |_| Some(true));
980 }
981
982 #[test]
983 fn check_rejects_claimed_core_out_of_bounds() {
984 check_mutated_assignments(200, 100, 25, |m| {
985 m.cores = CoreIndex(100).into();
986 Some(false)
987 });
988 }
989
990 #[test]
991 fn check_rejects_in_backing_group() {
992 check_mutated_assignments(200, 100, 25, |m| {
993 m.groups[0] = m.own_group;
994 Some(false)
995 });
996 }
997
998 #[test]
999 fn check_rejects_nonexistent_key() {
1000 check_mutated_assignments(200, 100, 25, |m| {
1001 m.val_index.0 += 200;
1002 Some(false)
1003 });
1004 }
1005
1006 #[test]
1007 fn check_rejects_delay_bad_vrf() {
1008 check_mutated_assignments(40, 100, 8, |m| {
1009 let vrf_signature = garbage_vrf_signature();
1010 match m.cert.kind.clone() {
1011 AssignmentCertKindV2::RelayVRFDelay { .. } => {
1012 m.cert.vrf = vrf_signature;
1013 Some(false)
1014 },
1015 _ => None, }
1017 });
1018 }
1019
1020 #[test]
1021 fn check_rejects_modulo_bad_vrf() {
1022 check_mutated_assignments(200, 100, 25, |m| {
1023 let vrf_signature = garbage_vrf_signature();
1024 match m.cert.kind.clone() {
1025 AssignmentCertKindV2::RelayVRFModulo { .. } => {
1026 m.cert.vrf = vrf_signature;
1027 Some(false)
1028 },
1029 AssignmentCertKindV2::RelayVRFModuloCompact { .. } => {
1030 m.cert.vrf = vrf_signature;
1031 Some(false)
1032 },
1033 _ => None, }
1035 });
1036 }
1037
1038 #[test]
1039 fn check_rejects_modulo_sample_out_of_bounds() {
1040 check_mutated_assignments(200, 100, 25, |m| {
1041 match m.cert.kind.clone() {
1042 AssignmentCertKindV2::RelayVRFModulo { sample } => {
1043 m.config.relay_vrf_modulo_samples = sample;
1044 Some(false)
1045 },
1046 AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield: _ } => Some(true),
1047 _ => None, }
1049 });
1050 }
1051
1052 #[test]
1053 fn check_rejects_delay_claimed_core_wrong() {
1054 check_mutated_assignments(200, 100, 25, |m| {
1055 match m.cert.kind.clone() {
1056 AssignmentCertKindV2::RelayVRFDelay { .. } => {
1057 m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into();
1061 Some(false)
1062 },
1063 _ => None, }
1065 });
1066 }
1067
1068 #[test]
1069 fn check_rejects_modulo_core_wrong() {
1070 check_mutated_assignments(200, 100, 25, |m| {
1071 match m.cert.kind.clone() {
1072 AssignmentCertKindV2::RelayVRFModulo { .. } |
1073 AssignmentCertKindV2::RelayVRFModuloCompact { .. } => {
1074 m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into();
1075
1076 Some(false)
1077 },
1078 _ => None, }
1080 });
1081 }
1082
1083 #[test]
1084 fn generate_samples_invariant() {
1085 let seed = [
1086 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,
1087 0, 0, 0, 2, 92,
1088 ];
1089 let rand_chacha = ChaCha20Rng::from_seed(seed);
1090
1091 let samples = generate_samples(rand_chacha.clone(), 6, 100);
1092 let expected = vec![19, 79, 17, 75, 66, 30].into_iter().map(Into::into).collect_vec();
1093 assert_eq!(samples, expected);
1094
1095 let samples = generate_samples(rand_chacha.clone(), 6, 7);
1096 let expected = vec![0, 3, 6, 5, 4, 2].into_iter().map(Into::into).collect_vec();
1097 assert_eq!(samples, expected);
1098
1099 let samples = generate_samples(rand_chacha.clone(), 6, 12);
1100 let expected = vec![2, 4, 7, 5, 11, 3].into_iter().map(Into::into).collect_vec();
1101 assert_eq!(samples, expected);
1102
1103 let samples = generate_samples(rand_chacha.clone(), 1, 100);
1104 let expected = vec![30].into_iter().map(Into::into).collect_vec();
1105 assert_eq!(samples, expected);
1106
1107 let samples = generate_samples(rand_chacha.clone(), 0, 100);
1108 let expected = vec![];
1109 assert_eq!(samples, expected);
1110
1111 let samples = generate_samples(rand_chacha, MAX_MODULO_SAMPLES + 1, 100);
1112 let expected = vec![
1113 42, 54, 55, 93, 64, 27, 49, 15, 83, 71, 62, 1, 43, 77, 97, 41, 7, 69, 0, 88, 59, 14,
1114 23, 87, 47, 4, 51, 12, 74, 56, 50, 44, 9, 82, 19, 79, 17, 75, 66, 30,
1115 ]
1116 .into_iter()
1117 .map(Into::into)
1118 .collect_vec();
1119 assert_eq!(samples, expected);
1120 }
1121}