mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-22 04:31:02 +00:00
002d9260f9
**Update:** Pushed additional changes based on the review comments. **This pull request fixes various spelling mistakes in this repository.** Most of the changes are contained in the first **3** commits: - `Fix spelling mistakes in comments and docs` - `Fix spelling mistakes in test names` - `Fix spelling mistakes in error messages, panic messages, logs and tracing` Other source code spelling mistakes are separated into individual commits for easier reviewing: - `Fix the spelling of 'authority'` - `Fix the spelling of 'REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY'` - `Fix the spelling of 'prev_enqueud_messages'` - `Fix the spelling of 'endpoint'` - `Fix the spelling of 'children'` - `Fix the spelling of 'PenpalSiblingSovereignAccount'` - `Fix the spelling of 'PenpalSudoAccount'` - `Fix the spelling of 'insufficient'` - `Fix the spelling of 'PalletXcmExtrinsicsBenchmark'` - `Fix the spelling of 'subtracted'` - `Fix the spelling of 'CandidatePendingAvailability'` - `Fix the spelling of 'exclusive'` - `Fix the spelling of 'until'` - `Fix the spelling of 'discriminator'` - `Fix the spelling of 'nonexistent'` - `Fix the spelling of 'subsystem'` - `Fix the spelling of 'indices'` - `Fix the spelling of 'committed'` - `Fix the spelling of 'topology'` - `Fix the spelling of 'response'` - `Fix the spelling of 'beneficiary'` - `Fix the spelling of 'formatted'` - `Fix the spelling of 'UNKNOWN_PROOF_REQUEST'` - `Fix the spelling of 'succeeded'` - `Fix the spelling of 'reopened'` - `Fix the spelling of 'proposer'` - `Fix the spelling of 'InstantiationNonce'` - `Fix the spelling of 'depositor'` - `Fix the spelling of 'expiration'` - `Fix the spelling of 'phantom'` - `Fix the spelling of 'AggregatedKeyValue'` - `Fix the spelling of 'randomness'` - `Fix the spelling of 'defendant'` - `Fix the spelling of 'AquaticMammal'` - `Fix the spelling of 'transactions'` - `Fix the spelling of 'PassingTracingSubscriber'` - `Fix the spelling of 'TxSignaturePayload'` - `Fix the spelling of 'versioning'` - `Fix the spelling of 'descendant'` - `Fix the spelling of 'overridden'` - `Fix the spelling of 'network'` Let me know if this structure is adequate. **Note:** The usage of the words `Merkle`, `Merkelize`, `Merklization`, `Merkelization`, `Merkleization`, is somewhat inconsistent but I left it as it is. ~~**Note:** In some places the term `Receival` is used to refer to message reception, IMO `Reception` is the correct word here, but I left it as it is.~~ ~~**Note:** In some places the term `Overlayed` is used instead of the more acceptable version `Overlaid` but I also left it as it is.~~ ~~**Note:** In some places the term `Applyable` is used instead of the correct version `Applicable` but I also left it as it is.~~ **Note:** Some usage of British vs American english e.g. `judgement` vs `judgment`, `initialise` vs `initialize`, `optimise` vs `optimize` etc. are both present in different places, but I suppose that's understandable given the number of contributors. ~~**Note:** There is a spelling mistake in `.github/CODEOWNERS` but it triggers errors in CI when I make changes to it, so I left it as it is.~~
1238 lines
35 KiB
Rust
1238 lines
35 KiB
Rust
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// This file is part of Polkadot.
|
|
|
|
// Polkadot is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Polkadot is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! Assignment criteria VRF generation and checking.
|
|
|
|
use itertools::Itertools;
|
|
use parity_scale_codec::{Decode, Encode};
|
|
use polkadot_node_primitives::approval::{
|
|
self as approval_types,
|
|
v1::{AssignmentCert, AssignmentCertKind, DelayTranche, RelayVRFStory},
|
|
v2::{
|
|
AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, VrfPreOutput, VrfProof, VrfSignature,
|
|
},
|
|
};
|
|
use polkadot_primitives::{
|
|
AssignmentId, AssignmentPair, CandidateHash, CoreIndex, GroupIndex, IndexedVec, SessionInfo,
|
|
ValidatorIndex,
|
|
};
|
|
use rand::{seq::SliceRandom, SeedableRng};
|
|
use rand_chacha::ChaCha20Rng;
|
|
use sc_keystore::LocalKeystore;
|
|
use sp_application_crypto::ByteArray;
|
|
|
|
use merlin::Transcript;
|
|
use schnorrkel::vrf::VRFInOut;
|
|
|
|
use std::{
|
|
cmp::min,
|
|
collections::{hash_map::Entry, HashMap},
|
|
};
|
|
|
|
use super::LOG_TARGET;
|
|
|
|
/// Details pertaining to our assignment on a block.
|
|
#[derive(Debug, Clone, Encode, Decode, PartialEq)]
|
|
pub struct OurAssignment {
|
|
cert: AssignmentCertV2,
|
|
tranche: DelayTranche,
|
|
validator_index: ValidatorIndex,
|
|
// Whether the assignment has been triggered already.
|
|
triggered: bool,
|
|
}
|
|
|
|
impl OurAssignment {
|
|
pub fn cert(&self) -> &AssignmentCertV2 {
|
|
&self.cert
|
|
}
|
|
|
|
pub fn tranche(&self) -> DelayTranche {
|
|
self.tranche
|
|
}
|
|
|
|
pub(crate) fn validator_index(&self) -> ValidatorIndex {
|
|
self.validator_index
|
|
}
|
|
|
|
pub(crate) fn triggered(&self) -> bool {
|
|
self.triggered
|
|
}
|
|
|
|
pub(crate) fn mark_triggered(&mut self) {
|
|
self.triggered = true;
|
|
}
|
|
}
|
|
|
|
impl From<crate::approval_db::v2::OurAssignment> for OurAssignment {
|
|
fn from(entry: crate::approval_db::v2::OurAssignment) -> Self {
|
|
OurAssignment {
|
|
cert: entry.cert,
|
|
tranche: entry.tranche,
|
|
validator_index: entry.validator_index,
|
|
triggered: entry.triggered,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<OurAssignment> for crate::approval_db::v2::OurAssignment {
|
|
fn from(entry: OurAssignment) -> Self {
|
|
Self {
|
|
cert: entry.cert,
|
|
tranche: entry.tranche,
|
|
validator_index: entry.validator_index,
|
|
triggered: entry.triggered,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Combines the relay VRF story with a sample number if any.
|
|
fn relay_vrf_modulo_transcript_inner(
|
|
mut transcript: Transcript,
|
|
relay_vrf_story: RelayVRFStory,
|
|
sample: Option<u32>,
|
|
) -> Transcript {
|
|
transcript.append_message(b"RC-VRF", &relay_vrf_story.0);
|
|
|
|
if let Some(sample) = sample {
|
|
sample.using_encoded(|s| transcript.append_message(b"sample", s));
|
|
}
|
|
|
|
transcript
|
|
}
|
|
|
|
fn relay_vrf_modulo_transcript_v1(relay_vrf_story: RelayVRFStory, sample: u32) -> Transcript {
|
|
relay_vrf_modulo_transcript_inner(
|
|
Transcript::new(approval_types::v1::RELAY_VRF_MODULO_CONTEXT),
|
|
relay_vrf_story,
|
|
Some(sample),
|
|
)
|
|
}
|
|
|
|
fn relay_vrf_modulo_transcript_v2(relay_vrf_story: RelayVRFStory) -> Transcript {
|
|
relay_vrf_modulo_transcript_inner(
|
|
Transcript::new(approval_types::v2::RELAY_VRF_MODULO_CONTEXT),
|
|
relay_vrf_story,
|
|
None,
|
|
)
|
|
}
|
|
|
|
/// A hard upper bound on num_cores * target_checkers / num_validators
|
|
const MAX_MODULO_SAMPLES: usize = 40;
|
|
|
|
/// Takes the VRF output as input and returns a Vec of cores the validator is assigned
|
|
/// to as a tranche0 checker.
|
|
fn relay_vrf_modulo_cores(
|
|
vrf_in_out: &VRFInOut,
|
|
// Configuration - `relay_vrf_modulo_samples`.
|
|
num_samples: u32,
|
|
// Configuration - `n_cores`.
|
|
max_cores: u32,
|
|
) -> Vec<CoreIndex> {
|
|
let rand_chacha =
|
|
ChaCha20Rng::from_seed(vrf_in_out.make_bytes::<<ChaCha20Rng as SeedableRng>::Seed>(
|
|
approval_types::v2::CORE_RANDOMNESS_CONTEXT,
|
|
));
|
|
generate_samples(rand_chacha, num_samples as usize, max_cores as usize)
|
|
}
|
|
|
|
/// Generates `num_samples` randomly from (0..max_cores) range
|
|
///
|
|
/// Note! The algorithm can't change because validators on the other
|
|
/// side won't be able to check the assignments until they update.
|
|
/// This invariant is tested with `generate_samples_invariant`, so the
|
|
/// tests will catch any subtle changes in the implementation of this function
|
|
/// and its dependencies.
|
|
fn generate_samples(
|
|
mut rand_chacha: ChaCha20Rng,
|
|
num_samples: usize,
|
|
max_cores: usize,
|
|
) -> Vec<CoreIndex> {
|
|
if num_samples as usize > MAX_MODULO_SAMPLES {
|
|
gum::warn!(
|
|
target: LOG_TARGET,
|
|
n_cores = max_cores,
|
|
num_samples,
|
|
max_modulo_samples = MAX_MODULO_SAMPLES,
|
|
"`num_samples` is greater than `MAX_MODULO_SAMPLES`",
|
|
);
|
|
}
|
|
|
|
if 2 * num_samples > max_cores {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
n_cores = max_cores,
|
|
num_samples,
|
|
max_modulo_samples = MAX_MODULO_SAMPLES,
|
|
"Suboptimal configuration `num_samples` should be less than `n_cores` / 2",
|
|
);
|
|
}
|
|
|
|
let num_samples = min(MAX_MODULO_SAMPLES, min(num_samples, max_cores));
|
|
|
|
let mut random_cores = (0..max_cores as u32).map(|val| val.into()).collect::<Vec<CoreIndex>>();
|
|
let (samples, _) = random_cores.partial_shuffle(&mut rand_chacha, num_samples as usize);
|
|
samples.into_iter().map(|val| *val).collect_vec()
|
|
}
|
|
|
|
fn relay_vrf_modulo_core(vrf_in_out: &VRFInOut, n_cores: u32) -> CoreIndex {
|
|
let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::v1::CORE_RANDOMNESS_CONTEXT);
|
|
|
|
// interpret as little-endian u32.
|
|
let random_core = u32::from_le_bytes(bytes) % n_cores;
|
|
CoreIndex(random_core)
|
|
}
|
|
|
|
fn relay_vrf_delay_transcript(relay_vrf_story: RelayVRFStory, core_index: CoreIndex) -> Transcript {
|
|
let mut t = Transcript::new(approval_types::v1::RELAY_VRF_DELAY_CONTEXT);
|
|
t.append_message(b"RC-VRF", &relay_vrf_story.0);
|
|
core_index.0.using_encoded(|s| t.append_message(b"core", s));
|
|
t
|
|
}
|
|
|
|
fn relay_vrf_delay_tranche(
|
|
vrf_in_out: &VRFInOut,
|
|
num_delay_tranches: u32,
|
|
zeroth_delay_tranche_width: u32,
|
|
) -> DelayTranche {
|
|
let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::v1::TRANCHE_RANDOMNESS_CONTEXT);
|
|
|
|
// interpret as little-endian u32 and reduce by the number of tranches.
|
|
let wide_tranche =
|
|
u32::from_le_bytes(bytes) % (num_delay_tranches + zeroth_delay_tranche_width);
|
|
|
|
// Consolidate early results to tranche zero so tranche zero is extra wide.
|
|
wide_tranche.saturating_sub(zeroth_delay_tranche_width)
|
|
}
|
|
|
|
fn assigned_core_transcript(core_index: CoreIndex) -> Transcript {
|
|
let mut t = Transcript::new(approval_types::v1::ASSIGNED_CORE_CONTEXT);
|
|
core_index.0.using_encoded(|s| t.append_message(b"core", s));
|
|
t
|
|
}
|
|
|
|
/// Information about the world assignments are being produced in.
|
|
#[derive(Clone, Debug)]
|
|
pub struct Config {
|
|
/// The assignment public keys for validators.
|
|
assignment_keys: Vec<AssignmentId>,
|
|
/// The groups of validators assigned to each core.
|
|
validator_groups: IndexedVec<GroupIndex, Vec<ValidatorIndex>>,
|
|
/// The number of availability cores used by the protocol during this session.
|
|
n_cores: u32,
|
|
/// The zeroth delay tranche width.
|
|
zeroth_delay_tranche_width: u32,
|
|
/// The number of samples we do of `relay_vrf_modulo`.
|
|
relay_vrf_modulo_samples: u32,
|
|
/// The number of delay tranches in total.
|
|
n_delay_tranches: u32,
|
|
}
|
|
|
|
impl<'a> From<&'a SessionInfo> for Config {
|
|
fn from(s: &'a SessionInfo) -> Self {
|
|
Config {
|
|
assignment_keys: s.assignment_keys.clone(),
|
|
validator_groups: s.validator_groups.clone(),
|
|
n_cores: s.n_cores,
|
|
zeroth_delay_tranche_width: s.zeroth_delay_tranche_width,
|
|
relay_vrf_modulo_samples: s.relay_vrf_modulo_samples,
|
|
n_delay_tranches: s.n_delay_tranches,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A trait for producing and checking assignments. Used to mock.
|
|
pub(crate) trait AssignmentCriteria {
|
|
fn compute_assignments(
|
|
&self,
|
|
keystore: &LocalKeystore,
|
|
relay_vrf_story: RelayVRFStory,
|
|
config: &Config,
|
|
leaving_cores: Vec<(CandidateHash, CoreIndex, GroupIndex)>,
|
|
enable_v2_assignments: bool,
|
|
) -> HashMap<CoreIndex, OurAssignment>;
|
|
|
|
fn check_assignment_cert(
|
|
&self,
|
|
claimed_core_bitfield: CoreBitfield,
|
|
validator_index: ValidatorIndex,
|
|
config: &Config,
|
|
relay_vrf_story: RelayVRFStory,
|
|
assignment: &AssignmentCertV2,
|
|
// Backing groups for each "leaving core".
|
|
backing_groups: Vec<GroupIndex>,
|
|
) -> Result<DelayTranche, InvalidAssignment>;
|
|
}
|
|
|
|
pub(crate) struct RealAssignmentCriteria;
|
|
|
|
impl AssignmentCriteria for RealAssignmentCriteria {
|
|
fn compute_assignments(
|
|
&self,
|
|
keystore: &LocalKeystore,
|
|
relay_vrf_story: RelayVRFStory,
|
|
config: &Config,
|
|
leaving_cores: Vec<(CandidateHash, CoreIndex, GroupIndex)>,
|
|
enable_v2_assignments: bool,
|
|
) -> HashMap<CoreIndex, OurAssignment> {
|
|
compute_assignments(keystore, relay_vrf_story, config, leaving_cores, enable_v2_assignments)
|
|
}
|
|
|
|
fn check_assignment_cert(
|
|
&self,
|
|
claimed_core_bitfield: CoreBitfield,
|
|
validator_index: ValidatorIndex,
|
|
config: &Config,
|
|
relay_vrf_story: RelayVRFStory,
|
|
assignment: &AssignmentCertV2,
|
|
backing_groups: Vec<GroupIndex>,
|
|
) -> Result<DelayTranche, InvalidAssignment> {
|
|
check_assignment_cert(
|
|
claimed_core_bitfield,
|
|
validator_index,
|
|
config,
|
|
relay_vrf_story,
|
|
assignment,
|
|
backing_groups,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Compute the assignments for a given block. Returns a map containing all assignments to cores in
|
|
/// the block. If more than one assignment targets the given core, only the earliest assignment is
|
|
/// kept.
|
|
///
|
|
/// The `leaving_cores` parameter indicates all cores within the block where a candidate was
|
|
/// included, as well as the group index backing those.
|
|
///
|
|
/// The current description of the protocol assigns every validator to check every core. But at
|
|
/// different times. The idea is that most assignments are never triggered and fall by the wayside.
|
|
///
|
|
/// This will not assign to anything the local validator was part of the backing group for.
|
|
pub fn compute_assignments(
|
|
keystore: &LocalKeystore,
|
|
relay_vrf_story: RelayVRFStory,
|
|
config: &Config,
|
|
leaving_cores: impl IntoIterator<Item = (CandidateHash, CoreIndex, GroupIndex)> + Clone,
|
|
enable_v2_assignments: bool,
|
|
) -> HashMap<CoreIndex, OurAssignment> {
|
|
if config.n_cores == 0 ||
|
|
config.assignment_keys.is_empty() ||
|
|
config.validator_groups.is_empty()
|
|
{
|
|
gum::trace!(
|
|
target: LOG_TARGET,
|
|
n_cores = config.n_cores,
|
|
has_assignment_keys = !config.assignment_keys.is_empty(),
|
|
has_validator_groups = !config.validator_groups.is_empty(),
|
|
"Not producing assignments because config is degenerate",
|
|
);
|
|
|
|
return HashMap::new()
|
|
}
|
|
|
|
let (index, assignments_key): (ValidatorIndex, AssignmentPair) = {
|
|
let key = config.assignment_keys.iter().enumerate().find_map(|(i, p)| {
|
|
match keystore.key_pair(p) {
|
|
Ok(Some(pair)) => Some((ValidatorIndex(i as _), pair)),
|
|
Ok(None) => None,
|
|
Err(sc_keystore::Error::Unavailable) => None,
|
|
Err(sc_keystore::Error::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => None,
|
|
Err(e) => {
|
|
gum::warn!(target: LOG_TARGET, "Encountered keystore error: {:?}", e);
|
|
None
|
|
},
|
|
}
|
|
});
|
|
|
|
match key {
|
|
None => {
|
|
gum::trace!(target: LOG_TARGET, "No assignment key");
|
|
return HashMap::new()
|
|
},
|
|
Some(k) => k,
|
|
}
|
|
};
|
|
|
|
// Ignore any cores where the assigned group is our own.
|
|
let leaving_cores = leaving_cores
|
|
.into_iter()
|
|
.filter(|(_, _, g)| !is_in_backing_group(&config.validator_groups, index, *g))
|
|
.map(|(c_hash, core, _)| (c_hash, core))
|
|
.collect::<Vec<_>>();
|
|
|
|
gum::trace!(
|
|
target: LOG_TARGET,
|
|
assignable_cores = leaving_cores.len(),
|
|
"Assigning to candidates from different backing groups"
|
|
);
|
|
|
|
let assignments_key: &sp_application_crypto::sr25519::Pair = assignments_key.as_ref();
|
|
let assignments_key: &schnorrkel::Keypair = assignments_key.as_ref();
|
|
|
|
let mut assignments = HashMap::new();
|
|
|
|
// First run `RelayVRFModulo` for each sample.
|
|
if enable_v2_assignments {
|
|
compute_relay_vrf_modulo_assignments_v2(
|
|
&assignments_key,
|
|
index,
|
|
config,
|
|
relay_vrf_story.clone(),
|
|
leaving_cores.clone(),
|
|
&mut assignments,
|
|
);
|
|
} else {
|
|
compute_relay_vrf_modulo_assignments_v1(
|
|
&assignments_key,
|
|
index,
|
|
config,
|
|
relay_vrf_story.clone(),
|
|
leaving_cores.clone(),
|
|
&mut assignments,
|
|
);
|
|
}
|
|
|
|
// Then run `RelayVRFDelay` once for the whole block.
|
|
compute_relay_vrf_delay_assignments(
|
|
&assignments_key,
|
|
index,
|
|
config,
|
|
relay_vrf_story,
|
|
leaving_cores,
|
|
&mut assignments,
|
|
);
|
|
|
|
assignments
|
|
}
|
|
|
|
fn compute_relay_vrf_modulo_assignments_v1(
|
|
assignments_key: &schnorrkel::Keypair,
|
|
validator_index: ValidatorIndex,
|
|
config: &Config,
|
|
relay_vrf_story: RelayVRFStory,
|
|
leaving_cores: impl IntoIterator<Item = (CandidateHash, CoreIndex)> + Clone,
|
|
assignments: &mut HashMap<CoreIndex, OurAssignment>,
|
|
) {
|
|
for rvm_sample in 0..config.relay_vrf_modulo_samples {
|
|
let mut core = CoreIndex::default();
|
|
|
|
let maybe_assignment = {
|
|
// Extra scope to ensure borrowing instead of moving core
|
|
// into closure.
|
|
let core = &mut core;
|
|
assignments_key.vrf_sign_extra_after_check(
|
|
relay_vrf_modulo_transcript_v1(relay_vrf_story.clone(), rvm_sample),
|
|
|vrf_in_out| {
|
|
*core = relay_vrf_modulo_core(&vrf_in_out, config.n_cores);
|
|
if let Some((candidate_hash, _)) =
|
|
leaving_cores.clone().into_iter().find(|(_, c)| c == core)
|
|
{
|
|
gum::trace!(
|
|
target: LOG_TARGET,
|
|
?candidate_hash,
|
|
?core,
|
|
?validator_index,
|
|
tranche = 0,
|
|
"RelayVRFModulo Assignment."
|
|
);
|
|
|
|
Some(assigned_core_transcript(*core))
|
|
} else {
|
|
None
|
|
}
|
|
},
|
|
)
|
|
};
|
|
|
|
if let Some((vrf_in_out, vrf_proof, _)) = maybe_assignment {
|
|
// Sanity: `core` is always initialized to non-default here, as the closure above
|
|
// has been executed.
|
|
let cert = AssignmentCert {
|
|
kind: AssignmentCertKind::RelayVRFModulo { sample: rvm_sample },
|
|
vrf: VrfSignature {
|
|
pre_output: VrfPreOutput(vrf_in_out.to_preout()),
|
|
proof: VrfProof(vrf_proof),
|
|
},
|
|
};
|
|
|
|
// All assignments of type RelayVRFModulo have tranche 0.
|
|
assignments.entry(core).or_insert(OurAssignment {
|
|
cert: cert.into(),
|
|
tranche: 0,
|
|
validator_index,
|
|
triggered: false,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn assigned_cores_transcript(core_bitfield: &CoreBitfield) -> Transcript {
|
|
let mut t = Transcript::new(approval_types::v2::ASSIGNED_CORE_CONTEXT);
|
|
core_bitfield.using_encoded(|s| t.append_message(b"cores", s));
|
|
t
|
|
}
|
|
|
|
fn compute_relay_vrf_modulo_assignments_v2(
|
|
assignments_key: &schnorrkel::Keypair,
|
|
validator_index: ValidatorIndex,
|
|
config: &Config,
|
|
relay_vrf_story: RelayVRFStory,
|
|
leaving_cores: Vec<(CandidateHash, CoreIndex)>,
|
|
assignments: &mut HashMap<CoreIndex, OurAssignment>,
|
|
) {
|
|
let mut assigned_cores = Vec::new();
|
|
let leaving_cores = leaving_cores.iter().map(|(_, core)| core).collect::<Vec<_>>();
|
|
|
|
let maybe_assignment = {
|
|
let assigned_cores = &mut assigned_cores;
|
|
assignments_key.vrf_sign_extra_after_check(
|
|
relay_vrf_modulo_transcript_v2(relay_vrf_story.clone()),
|
|
|vrf_in_out| {
|
|
*assigned_cores = relay_vrf_modulo_cores(
|
|
&vrf_in_out,
|
|
config.relay_vrf_modulo_samples,
|
|
config.n_cores,
|
|
)
|
|
.into_iter()
|
|
.filter(|core| leaving_cores.contains(&core))
|
|
.collect::<Vec<CoreIndex>>();
|
|
|
|
if !assigned_cores.is_empty() {
|
|
gum::trace!(
|
|
target: LOG_TARGET,
|
|
?assigned_cores,
|
|
?validator_index,
|
|
tranche = 0,
|
|
"RelayVRFModuloCompact Assignment."
|
|
);
|
|
|
|
let assignment_bitfield: CoreBitfield = assigned_cores
|
|
.clone()
|
|
.try_into()
|
|
.expect("Just checked `!assigned_cores.is_empty()`; qed");
|
|
|
|
Some(assigned_cores_transcript(&assignment_bitfield))
|
|
} else {
|
|
None
|
|
}
|
|
},
|
|
)
|
|
};
|
|
|
|
if let Some(assignment) = maybe_assignment.map(|(vrf_in_out, vrf_proof, _)| {
|
|
let assignment_bitfield: CoreBitfield = assigned_cores
|
|
.clone()
|
|
.try_into()
|
|
.expect("Just checked `!assigned_cores.is_empty()`; qed");
|
|
|
|
let cert = AssignmentCertV2 {
|
|
kind: AssignmentCertKindV2::RelayVRFModuloCompact {
|
|
core_bitfield: assignment_bitfield.clone(),
|
|
},
|
|
vrf: VrfSignature {
|
|
pre_output: VrfPreOutput(vrf_in_out.to_preout()),
|
|
proof: VrfProof(vrf_proof),
|
|
},
|
|
};
|
|
|
|
// All assignments of type RelayVRFModulo have tranche 0.
|
|
OurAssignment { cert, tranche: 0, validator_index, triggered: false }
|
|
}) {
|
|
for core_index in assigned_cores {
|
|
assignments.insert(core_index, assignment.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn compute_relay_vrf_delay_assignments(
|
|
assignments_key: &schnorrkel::Keypair,
|
|
validator_index: ValidatorIndex,
|
|
config: &Config,
|
|
relay_vrf_story: RelayVRFStory,
|
|
leaving_cores: impl IntoIterator<Item = (CandidateHash, CoreIndex)>,
|
|
assignments: &mut HashMap<CoreIndex, OurAssignment>,
|
|
) {
|
|
for (candidate_hash, core) in leaving_cores {
|
|
let (vrf_in_out, vrf_proof, _) =
|
|
assignments_key.vrf_sign(relay_vrf_delay_transcript(relay_vrf_story.clone(), core));
|
|
|
|
let tranche = relay_vrf_delay_tranche(
|
|
&vrf_in_out,
|
|
config.n_delay_tranches,
|
|
config.zeroth_delay_tranche_width,
|
|
);
|
|
|
|
let cert = AssignmentCertV2 {
|
|
kind: AssignmentCertKindV2::RelayVRFDelay { core_index: core },
|
|
vrf: VrfSignature {
|
|
pre_output: VrfPreOutput(vrf_in_out.to_preout()),
|
|
proof: VrfProof(vrf_proof),
|
|
},
|
|
};
|
|
|
|
let our_assignment = OurAssignment { cert, tranche, validator_index, triggered: false };
|
|
|
|
let used = match assignments.entry(core) {
|
|
Entry::Vacant(e) => {
|
|
let _ = e.insert(our_assignment);
|
|
true
|
|
},
|
|
Entry::Occupied(mut e) =>
|
|
if e.get().tranche > our_assignment.tranche {
|
|
e.insert(our_assignment);
|
|
true
|
|
} else {
|
|
false
|
|
},
|
|
};
|
|
|
|
if used {
|
|
gum::trace!(
|
|
target: LOG_TARGET,
|
|
?candidate_hash,
|
|
?core,
|
|
?validator_index,
|
|
tranche,
|
|
"RelayVRFDelay Assignment",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Assignment invalid.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct InvalidAssignment(pub(crate) InvalidAssignmentReason);
|
|
|
|
impl std::fmt::Display for InvalidAssignment {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(f, "Invalid Assignment: {:?}", self.0)
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for InvalidAssignment {}
|
|
|
|
/// Failure conditions when checking an assignment cert.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub(crate) enum InvalidAssignmentReason {
|
|
ValidatorIndexOutOfBounds,
|
|
SampleOutOfBounds,
|
|
CoreIndexOutOfBounds,
|
|
InvalidAssignmentKey,
|
|
IsInBackingGroup,
|
|
VRFModuloCoreIndexMismatch,
|
|
VRFModuloOutputMismatch,
|
|
VRFDelayCoreIndexMismatch,
|
|
VRFDelayOutputMismatch,
|
|
InvalidArguments,
|
|
/// Assignment vrf check resulted in 0 assigned cores.
|
|
NullAssignment,
|
|
}
|
|
|
|
/// Checks the crypto of an assignment cert. Failure conditions:
|
|
/// * Validator index out of bounds
|
|
/// * VRF signature check fails
|
|
/// * VRF output doesn't match assigned cores
|
|
/// * Core is not covered by extra data in signature
|
|
/// * Core index out of bounds
|
|
/// * Sample is out of bounds
|
|
/// * Validator is present in backing group.
|
|
///
|
|
/// This function does not check whether the core is actually a valid assignment or not. That should
|
|
/// be done outside the scope of this function.
|
|
pub(crate) fn check_assignment_cert(
|
|
claimed_core_indices: CoreBitfield,
|
|
validator_index: ValidatorIndex,
|
|
config: &Config,
|
|
relay_vrf_story: RelayVRFStory,
|
|
assignment: &AssignmentCertV2,
|
|
backing_groups: Vec<GroupIndex>,
|
|
) -> Result<DelayTranche, InvalidAssignment> {
|
|
use InvalidAssignmentReason as Reason;
|
|
|
|
let validator_public = config
|
|
.assignment_keys
|
|
.get(validator_index.0 as usize)
|
|
.ok_or(InvalidAssignment(Reason::ValidatorIndexOutOfBounds))?;
|
|
|
|
let public = schnorrkel::PublicKey::from_bytes(validator_public.as_slice())
|
|
.map_err(|_| InvalidAssignment(Reason::InvalidAssignmentKey))?;
|
|
|
|
// Check that we have all backing groups for claimed cores.
|
|
if claimed_core_indices.count_ones() == 0 ||
|
|
claimed_core_indices.count_ones() != backing_groups.len()
|
|
{
|
|
return Err(InvalidAssignment(Reason::InvalidArguments))
|
|
}
|
|
|
|
// Check that the validator was not part of the backing group
|
|
// and not already assigned.
|
|
for (claimed_core, backing_group) in claimed_core_indices.iter_ones().zip(backing_groups.iter())
|
|
{
|
|
if claimed_core >= config.n_cores as usize {
|
|
return Err(InvalidAssignment(Reason::CoreIndexOutOfBounds))
|
|
}
|
|
|
|
let is_in_backing =
|
|
is_in_backing_group(&config.validator_groups, validator_index, *backing_group);
|
|
|
|
if is_in_backing {
|
|
return Err(InvalidAssignment(Reason::IsInBackingGroup))
|
|
}
|
|
}
|
|
|
|
let vrf_pre_output = &assignment.vrf.pre_output;
|
|
let vrf_proof = &assignment.vrf.proof;
|
|
let first_claimed_core_index =
|
|
claimed_core_indices.first_one().expect("Checked above; qed") as u32;
|
|
|
|
match &assignment.kind {
|
|
AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => {
|
|
// Check that claimed core bitfield match the one from certificate.
|
|
if &claimed_core_indices != core_bitfield {
|
|
return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch))
|
|
}
|
|
|
|
let (vrf_in_out, _) = public
|
|
.vrf_verify_extra(
|
|
relay_vrf_modulo_transcript_v2(relay_vrf_story),
|
|
&vrf_pre_output.0,
|
|
&vrf_proof.0,
|
|
assigned_cores_transcript(core_bitfield),
|
|
)
|
|
.map_err(|_| InvalidAssignment(Reason::VRFModuloOutputMismatch))?;
|
|
|
|
let resulting_cores = relay_vrf_modulo_cores(
|
|
&vrf_in_out,
|
|
config.relay_vrf_modulo_samples,
|
|
config.n_cores,
|
|
);
|
|
|
|
// Currently validators can opt out of checking specific cores.
|
|
// This is the same issue to how validator can opt out and not send their assignments in
|
|
// the first place. Ensure that the `vrf_in_out` actually includes all of the claimed
|
|
// cores.
|
|
for claimed_core_index in claimed_core_indices.iter_ones() {
|
|
if !resulting_cores.contains(&CoreIndex(claimed_core_index as u32)) {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
?resulting_cores,
|
|
?claimed_core_indices,
|
|
vrf_modulo_cores = ?resulting_cores,
|
|
"Assignment claimed cores mismatch",
|
|
);
|
|
return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch))
|
|
}
|
|
}
|
|
|
|
Ok(0)
|
|
},
|
|
AssignmentCertKindV2::RelayVRFModulo { sample } => {
|
|
if *sample >= config.relay_vrf_modulo_samples {
|
|
return Err(InvalidAssignment(Reason::SampleOutOfBounds))
|
|
}
|
|
|
|
// Enforce claimed candidates is 1.
|
|
if claimed_core_indices.count_ones() != 1 {
|
|
gum::warn!(
|
|
target: LOG_TARGET,
|
|
?claimed_core_indices,
|
|
"`RelayVRFModulo` assignment must always claim 1 core",
|
|
);
|
|
return Err(InvalidAssignment(Reason::InvalidArguments))
|
|
}
|
|
|
|
let (vrf_in_out, _) = public
|
|
.vrf_verify_extra(
|
|
relay_vrf_modulo_transcript_v1(relay_vrf_story, *sample),
|
|
&vrf_pre_output.0,
|
|
&vrf_proof.0,
|
|
assigned_core_transcript(CoreIndex(first_claimed_core_index)),
|
|
)
|
|
.map_err(|_| InvalidAssignment(Reason::VRFModuloOutputMismatch))?;
|
|
|
|
let core = relay_vrf_modulo_core(&vrf_in_out, config.n_cores);
|
|
// ensure that the `vrf_in_out` actually gives us the claimed core.
|
|
if core.0 == first_claimed_core_index {
|
|
Ok(0)
|
|
} else {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
?core,
|
|
?claimed_core_indices,
|
|
"Assignment claimed cores mismatch",
|
|
);
|
|
Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch))
|
|
}
|
|
},
|
|
AssignmentCertKindV2::RelayVRFDelay { core_index } => {
|
|
// Enforce claimed candidates is 1.
|
|
if claimed_core_indices.count_ones() != 1 {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
?claimed_core_indices,
|
|
"`RelayVRFDelay` assignment must always claim 1 core",
|
|
);
|
|
return Err(InvalidAssignment(Reason::InvalidArguments))
|
|
}
|
|
|
|
if core_index.0 != first_claimed_core_index {
|
|
return Err(InvalidAssignment(Reason::VRFDelayCoreIndexMismatch))
|
|
}
|
|
|
|
let (vrf_in_out, _) = public
|
|
.vrf_verify(
|
|
relay_vrf_delay_transcript(relay_vrf_story, *core_index),
|
|
&vrf_pre_output.0,
|
|
&vrf_proof.0,
|
|
)
|
|
.map_err(|_| InvalidAssignment(Reason::VRFDelayOutputMismatch))?;
|
|
|
|
Ok(relay_vrf_delay_tranche(
|
|
&vrf_in_out,
|
|
config.n_delay_tranches,
|
|
config.zeroth_delay_tranche_width,
|
|
))
|
|
},
|
|
}
|
|
}
|
|
|
|
fn is_in_backing_group(
|
|
validator_groups: &IndexedVec<GroupIndex, Vec<ValidatorIndex>>,
|
|
validator: ValidatorIndex,
|
|
group: GroupIndex,
|
|
) -> bool {
|
|
validator_groups.get(group).map_or(false, |g| g.contains(&validator))
|
|
}
|
|
|
|
/// Migration helpers.
|
|
impl From<crate::approval_db::v1::OurAssignment> for OurAssignment {
|
|
fn from(value: crate::approval_db::v1::OurAssignment) -> Self {
|
|
Self {
|
|
cert: value.cert.into(),
|
|
tranche: value.tranche,
|
|
validator_index: value.validator_index,
|
|
// Whether the assignment has been triggered already.
|
|
triggered: value.triggered,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::import::tests::garbage_vrf_signature;
|
|
use polkadot_primitives::{Hash, ASSIGNMENT_KEY_TYPE_ID};
|
|
use sp_application_crypto::sr25519;
|
|
use sp_core::crypto::Pair as PairT;
|
|
use sp_keyring::sr25519::Keyring as Sr25519Keyring;
|
|
use sp_keystore::Keystore;
|
|
|
|
// sets up a keystore with the given keyring accounts.
|
|
fn make_keystore(accounts: &[Sr25519Keyring]) -> LocalKeystore {
|
|
let store = LocalKeystore::in_memory();
|
|
|
|
for s in accounts.iter().copied().map(|k| k.to_seed()) {
|
|
store.sr25519_generate_new(ASSIGNMENT_KEY_TYPE_ID, Some(s.as_str())).unwrap();
|
|
}
|
|
|
|
store
|
|
}
|
|
|
|
fn assignment_keys(accounts: &[Sr25519Keyring]) -> Vec<AssignmentId> {
|
|
assignment_keys_plus_random(accounts, 0)
|
|
}
|
|
|
|
fn assignment_keys_plus_random(
|
|
accounts: &[Sr25519Keyring],
|
|
random: usize,
|
|
) -> Vec<AssignmentId> {
|
|
let gen_random =
|
|
(0..random).map(|_| AssignmentId::from(sr25519::Pair::generate().0.public()));
|
|
|
|
accounts
|
|
.iter()
|
|
.map(|k| AssignmentId::from(k.public()))
|
|
.chain(gen_random)
|
|
.collect()
|
|
}
|
|
|
|
fn basic_groups(
|
|
n_validators: usize,
|
|
n_groups: usize,
|
|
) -> IndexedVec<GroupIndex, Vec<ValidatorIndex>> {
|
|
let size = n_validators / n_groups;
|
|
let big_groups = n_validators % n_groups;
|
|
let scraps = n_groups * size;
|
|
|
|
(0..n_groups)
|
|
.map(|i| {
|
|
(i * size..(i + 1) * size)
|
|
.chain(if i < big_groups { Some(scraps + i) } else { None })
|
|
.map(|j| ValidatorIndex(j as _))
|
|
.collect::<Vec<_>>()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn assignments_produced_for_non_backing() {
|
|
let keystore = make_keystore(&[Sr25519Keyring::Alice]);
|
|
|
|
let c_a = CandidateHash(Hash::repeat_byte(0));
|
|
let c_b = CandidateHash(Hash::repeat_byte(1));
|
|
|
|
let relay_vrf_story = RelayVRFStory([42u8; 32]);
|
|
let assignments = compute_assignments(
|
|
&keystore,
|
|
relay_vrf_story,
|
|
&Config {
|
|
assignment_keys: assignment_keys(&[
|
|
Sr25519Keyring::Alice,
|
|
Sr25519Keyring::Bob,
|
|
Sr25519Keyring::Charlie,
|
|
]),
|
|
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
|
|
vec![ValidatorIndex(0)],
|
|
vec![ValidatorIndex(1), ValidatorIndex(2)],
|
|
]),
|
|
n_cores: 2,
|
|
zeroth_delay_tranche_width: 10,
|
|
relay_vrf_modulo_samples: 10,
|
|
n_delay_tranches: 40,
|
|
},
|
|
vec![(c_a, CoreIndex(0), GroupIndex(1)), (c_b, CoreIndex(1), GroupIndex(0))],
|
|
false,
|
|
);
|
|
|
|
// Note that alice is in group 0, which was the backing group for core 1.
|
|
// Alice should have self-assigned to check core 0 but not 1.
|
|
assert_eq!(assignments.len(), 1);
|
|
assert!(assignments.get(&CoreIndex(0)).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn assign_to_nonzero_core() {
|
|
let keystore = make_keystore(&[Sr25519Keyring::Alice]);
|
|
|
|
let c_a = CandidateHash(Hash::repeat_byte(0));
|
|
let c_b = CandidateHash(Hash::repeat_byte(1));
|
|
|
|
let relay_vrf_story = RelayVRFStory([42u8; 32]);
|
|
let assignments = compute_assignments(
|
|
&keystore,
|
|
relay_vrf_story,
|
|
&Config {
|
|
assignment_keys: assignment_keys(&[
|
|
Sr25519Keyring::Alice,
|
|
Sr25519Keyring::Bob,
|
|
Sr25519Keyring::Charlie,
|
|
]),
|
|
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
|
|
vec![ValidatorIndex(0)],
|
|
vec![ValidatorIndex(1), ValidatorIndex(2)],
|
|
]),
|
|
n_cores: 2,
|
|
zeroth_delay_tranche_width: 10,
|
|
relay_vrf_modulo_samples: 10,
|
|
n_delay_tranches: 40,
|
|
},
|
|
vec![(c_a, CoreIndex(0), GroupIndex(0)), (c_b, CoreIndex(1), GroupIndex(1))],
|
|
false,
|
|
);
|
|
|
|
assert_eq!(assignments.len(), 1);
|
|
assert!(assignments.get(&CoreIndex(1)).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn succeeds_empty_for_0_cores() {
|
|
let keystore = make_keystore(&[Sr25519Keyring::Alice]);
|
|
|
|
let relay_vrf_story = RelayVRFStory([42u8; 32]);
|
|
let assignments = compute_assignments(
|
|
&keystore,
|
|
relay_vrf_story,
|
|
&Config {
|
|
assignment_keys: assignment_keys(&[
|
|
Sr25519Keyring::Alice,
|
|
Sr25519Keyring::Bob,
|
|
Sr25519Keyring::Charlie,
|
|
]),
|
|
validator_groups: Default::default(),
|
|
n_cores: 0,
|
|
zeroth_delay_tranche_width: 10,
|
|
relay_vrf_modulo_samples: 10,
|
|
n_delay_tranches: 40,
|
|
},
|
|
vec![],
|
|
false,
|
|
);
|
|
|
|
assert!(assignments.is_empty());
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct MutatedAssignment {
|
|
cores: CoreBitfield,
|
|
cert: AssignmentCertV2,
|
|
groups: Vec<GroupIndex>,
|
|
own_group: GroupIndex,
|
|
val_index: ValidatorIndex,
|
|
config: Config,
|
|
}
|
|
|
|
// This fails if the closure requests to skip everything.
|
|
fn check_mutated_assignments(
|
|
n_validators: usize,
|
|
n_cores: usize,
|
|
rotation_offset: usize,
|
|
f: impl Fn(&mut MutatedAssignment) -> Option<bool>, // None = skip
|
|
) {
|
|
let keystore = make_keystore(&[Sr25519Keyring::Alice]);
|
|
|
|
let group_for_core = |i| GroupIndex(((i + rotation_offset) % n_cores) as _);
|
|
|
|
let config = Config {
|
|
assignment_keys: assignment_keys_plus_random(
|
|
&[Sr25519Keyring::Alice],
|
|
n_validators - 1,
|
|
),
|
|
validator_groups: basic_groups(n_validators, n_cores),
|
|
n_cores: n_cores as u32,
|
|
zeroth_delay_tranche_width: 10,
|
|
relay_vrf_modulo_samples: 15,
|
|
n_delay_tranches: 40,
|
|
};
|
|
|
|
let relay_vrf_story = RelayVRFStory([42u8; 32]);
|
|
let mut assignments = compute_assignments(
|
|
&keystore,
|
|
relay_vrf_story.clone(),
|
|
&config,
|
|
(0..n_cores)
|
|
.map(|i| {
|
|
(
|
|
CandidateHash(Hash::repeat_byte(i as u8)),
|
|
CoreIndex(i as u32),
|
|
group_for_core(i),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>(),
|
|
false,
|
|
);
|
|
|
|
// Extend with v2 assignments as well
|
|
assignments.extend(compute_assignments(
|
|
&keystore,
|
|
relay_vrf_story.clone(),
|
|
&config,
|
|
(0..n_cores)
|
|
.map(|i| {
|
|
(
|
|
CandidateHash(Hash::repeat_byte(i as u8)),
|
|
CoreIndex(i as u32),
|
|
group_for_core(i),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>(),
|
|
true,
|
|
));
|
|
|
|
let mut counted = 0;
|
|
for (core, assignment) in assignments {
|
|
let cores = match assignment.cert.kind.clone() {
|
|
AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => core_bitfield,
|
|
AssignmentCertKindV2::RelayVRFModulo { sample: _ } => core.into(),
|
|
AssignmentCertKindV2::RelayVRFDelay { core_index } => core_index.into(),
|
|
};
|
|
|
|
let mut mutated = MutatedAssignment {
|
|
cores: cores.clone(),
|
|
groups: cores.iter_ones().map(|core| group_for_core(core)).collect(),
|
|
cert: assignment.cert,
|
|
own_group: GroupIndex(0),
|
|
val_index: ValidatorIndex(0),
|
|
config: config.clone(),
|
|
};
|
|
let expected = match f(&mut mutated) {
|
|
None => continue,
|
|
Some(e) => e,
|
|
};
|
|
|
|
counted += 1;
|
|
|
|
let is_good = check_assignment_cert(
|
|
mutated.cores,
|
|
mutated.val_index,
|
|
&mutated.config,
|
|
relay_vrf_story.clone(),
|
|
&mutated.cert,
|
|
mutated.groups,
|
|
)
|
|
.is_ok();
|
|
|
|
assert_eq!(expected, is_good);
|
|
}
|
|
|
|
assert!(counted > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn computed_assignments_pass_checks() {
|
|
check_mutated_assignments(200, 100, 25, |_| Some(true));
|
|
}
|
|
|
|
#[test]
|
|
fn check_rejects_claimed_core_out_of_bounds() {
|
|
check_mutated_assignments(200, 100, 25, |m| {
|
|
m.cores = CoreIndex(100).into();
|
|
Some(false)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn check_rejects_in_backing_group() {
|
|
check_mutated_assignments(200, 100, 25, |m| {
|
|
m.groups[0] = m.own_group;
|
|
Some(false)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn check_rejects_nonexistent_key() {
|
|
check_mutated_assignments(200, 100, 25, |m| {
|
|
m.val_index.0 += 200;
|
|
Some(false)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn check_rejects_delay_bad_vrf() {
|
|
check_mutated_assignments(40, 100, 8, |m| {
|
|
let vrf_signature = garbage_vrf_signature();
|
|
match m.cert.kind.clone() {
|
|
AssignmentCertKindV2::RelayVRFDelay { .. } => {
|
|
m.cert.vrf = vrf_signature;
|
|
Some(false)
|
|
},
|
|
_ => None, // skip everything else.
|
|
}
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn check_rejects_modulo_bad_vrf() {
|
|
check_mutated_assignments(200, 100, 25, |m| {
|
|
let vrf_signature = garbage_vrf_signature();
|
|
match m.cert.kind.clone() {
|
|
AssignmentCertKindV2::RelayVRFModulo { .. } => {
|
|
m.cert.vrf = vrf_signature;
|
|
Some(false)
|
|
},
|
|
AssignmentCertKindV2::RelayVRFModuloCompact { .. } => {
|
|
m.cert.vrf = vrf_signature;
|
|
Some(false)
|
|
},
|
|
_ => None, // skip everything else.
|
|
}
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn check_rejects_modulo_sample_out_of_bounds() {
|
|
check_mutated_assignments(200, 100, 25, |m| {
|
|
match m.cert.kind.clone() {
|
|
AssignmentCertKindV2::RelayVRFModulo { sample } => {
|
|
m.config.relay_vrf_modulo_samples = sample;
|
|
Some(false)
|
|
},
|
|
AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield: _ } => Some(true),
|
|
_ => None, // skip everything else.
|
|
}
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn check_rejects_delay_claimed_core_wrong() {
|
|
check_mutated_assignments(200, 100, 25, |m| {
|
|
match m.cert.kind.clone() {
|
|
AssignmentCertKindV2::RelayVRFDelay { .. } => {
|
|
// for core in &mut m.cores {
|
|
// core.0 = (core.0 + 1) % 100;
|
|
// }
|
|
m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into();
|
|
Some(false)
|
|
},
|
|
_ => None, // skip everything else.
|
|
}
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn check_rejects_modulo_core_wrong() {
|
|
check_mutated_assignments(200, 100, 25, |m| {
|
|
match m.cert.kind.clone() {
|
|
AssignmentCertKindV2::RelayVRFModulo { .. } |
|
|
AssignmentCertKindV2::RelayVRFModuloCompact { .. } => {
|
|
m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into();
|
|
|
|
Some(false)
|
|
},
|
|
_ => None, // skip everything else.
|
|
}
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn generate_samples_invariant() {
|
|
let seed = [
|
|
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,
|
|
0, 0, 0, 2, 92,
|
|
];
|
|
let rand_chacha = ChaCha20Rng::from_seed(seed);
|
|
|
|
let samples = generate_samples(rand_chacha.clone(), 6, 100);
|
|
let expected = vec![19, 79, 17, 75, 66, 30].into_iter().map(Into::into).collect_vec();
|
|
assert_eq!(samples, expected);
|
|
|
|
let samples = generate_samples(rand_chacha.clone(), 6, 7);
|
|
let expected = vec![0, 3, 6, 5, 4, 2].into_iter().map(Into::into).collect_vec();
|
|
assert_eq!(samples, expected);
|
|
|
|
let samples = generate_samples(rand_chacha.clone(), 6, 12);
|
|
let expected = vec![2, 4, 7, 5, 11, 3].into_iter().map(Into::into).collect_vec();
|
|
assert_eq!(samples, expected);
|
|
|
|
let samples = generate_samples(rand_chacha.clone(), 1, 100);
|
|
let expected = vec![30].into_iter().map(Into::into).collect_vec();
|
|
assert_eq!(samples, expected);
|
|
|
|
let samples = generate_samples(rand_chacha.clone(), 0, 100);
|
|
let expected = vec![];
|
|
assert_eq!(samples, expected);
|
|
|
|
let samples = generate_samples(rand_chacha, MAX_MODULO_SAMPLES + 1, 100);
|
|
let expected = vec![
|
|
42, 54, 55, 93, 64, 27, 49, 15, 83, 71, 62, 1, 43, 77, 97, 41, 7, 69, 0, 88, 59, 14,
|
|
23, 87, 47, 4, 51, 12, 74, 56, 50, 44, 9, 82, 19, 79, 17, 75, 66, 30,
|
|
]
|
|
.into_iter()
|
|
.map(Into::into)
|
|
.collect_vec();
|
|
assert_eq!(samples, expected);
|
|
}
|
|
}
|