use bitvec::{order::Lsb0 as BitOrderLsb0, slice::BitSlice};
use polkadot_node_primitives::approval::v1::DelayTranche;
use polkadot_primitives::ValidatorIndex;
use crate::{
persisted_entries::{ApprovalEntry, CandidateEntry, TrancheEntry},
MAX_RECORDED_NO_SHOW_VALIDATORS_PER_CANDIDATE,
};
use polkadot_node_primitives::approval::time::Tick;
#[derive(Debug, PartialEq, Clone)]
pub struct TranchesToApproveResult {
pub required_tranches: RequiredTranches,
pub total_observed_no_shows: usize,
pub no_show_validators: Vec<ValidatorIndex>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum RequiredTranches {
All,
Pending {
considered: DelayTranche,
next_no_show: Option<Tick>,
maximum_broadcast: DelayTranche,
clock_drift: Tick,
},
Exact {
needed: DelayTranche,
tolerated_missing: usize,
next_no_show: Option<Tick>,
last_assignment_tick: Option<Tick>,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Check {
Unapproved,
Approved(usize, Option<Tick>),
ApprovedOneThird,
}
impl Check {
pub fn is_approved(&self, max_assignment_tick: Tick) -> bool {
match *self {
Check::Unapproved => false,
Check::Approved(_, last_assignment_tick) =>
last_assignment_tick.map_or(true, |t| t <= max_assignment_tick),
Check::ApprovedOneThird => true,
}
}
pub fn known_no_shows(&self) -> usize {
match *self {
Check::Approved(n, _) => n,
_ => 0,
}
}
}
pub fn check_approval(
candidate: &CandidateEntry,
approval: &ApprovalEntry,
required: RequiredTranches,
) -> Check {
let approvals = candidate.approvals();
if 3 * approvals.count_ones() > approvals.len() {
return Check::ApprovedOneThird
}
match required {
RequiredTranches::Pending { .. } => Check::Unapproved,
RequiredTranches::All => Check::Unapproved,
RequiredTranches::Exact { needed, tolerated_missing, last_assignment_tick, .. } => {
let mut assigned_mask = approval.assignments_up_to(needed);
let approvals = candidate.approvals();
let n_assigned = assigned_mask.count_ones();
assigned_mask &= approvals;
let n_approved = assigned_mask.count_ones();
if n_approved + tolerated_missing >= n_assigned {
Check::Approved(tolerated_missing, last_assignment_tick)
} else {
Check::Unapproved
}
},
}
}
#[derive(Debug)]
struct State {
assignments: usize,
depth: usize,
covered: usize,
covering: usize,
uncovered: usize,
next_no_show: Option<Tick>,
last_assignment_tick: Option<Tick>,
total_observed_no_shows: usize,
no_show_validators: Vec<ValidatorIndex>,
}
impl State {
fn output(
&self,
tranche: DelayTranche,
needed_approvals: usize,
n_validators: usize,
no_show_duration: Tick,
) -> TranchesToApproveResult {
let covering = if self.depth == 0 { 0 } else { self.covering };
if self.depth != 0 && self.assignments + covering + self.uncovered >= n_validators {
return TranchesToApproveResult {
required_tranches: RequiredTranches::All,
total_observed_no_shows: self.total_observed_no_shows,
no_show_validators: self.no_show_validators.clone(),
}
}
if self.assignments >= needed_approvals && (covering + self.uncovered) == 0 {
return TranchesToApproveResult {
required_tranches: RequiredTranches::Exact {
needed: tranche,
tolerated_missing: self.covered,
next_no_show: self.next_no_show,
last_assignment_tick: self.last_assignment_tick,
},
total_observed_no_shows: self.total_observed_no_shows,
no_show_validators: self.no_show_validators.clone(),
}
}
let clock_drift = self.clock_drift(no_show_duration);
if self.depth == 0 {
TranchesToApproveResult {
required_tranches: RequiredTranches::Pending {
considered: tranche,
next_no_show: self.next_no_show,
maximum_broadcast: DelayTranche::max_value(),
clock_drift,
},
total_observed_no_shows: self.total_observed_no_shows,
no_show_validators: self.no_show_validators.clone(),
}
} else {
TranchesToApproveResult {
required_tranches: RequiredTranches::Pending {
considered: tranche,
next_no_show: self.next_no_show,
maximum_broadcast: tranche + (covering + self.uncovered) as DelayTranche,
clock_drift,
},
total_observed_no_shows: self.total_observed_no_shows,
no_show_validators: self.no_show_validators.clone(),
}
}
}
fn clock_drift(&self, no_show_duration: Tick) -> Tick {
self.depth as Tick * no_show_duration
}
fn advance(
mut self,
new_assignments: usize,
new_no_shows: usize,
next_no_show: Option<Tick>,
last_assignment_tick: Option<Tick>,
no_show_validators: Vec<ValidatorIndex>,
) -> State {
let new_covered = if self.depth == 0 {
new_assignments
} else {
new_assignments.min(1)
};
let assignments = self.assignments + new_assignments;
let covering = self.covering.saturating_sub(new_covered);
let covered = if self.depth == 0 {
0
} else {
self.covered + new_covered
};
let uncovered = self.uncovered + new_no_shows;
let next_no_show = super::min_prefer_some(self.next_no_show, next_no_show);
let last_assignment_tick = std::cmp::max(self.last_assignment_tick, last_assignment_tick);
let (depth, covering, uncovered) = if covering == 0 {
if uncovered == 0 {
(self.depth, 0, uncovered)
} else {
(self.depth + 1, uncovered, 0)
}
} else {
(self.depth, covering, uncovered)
};
if self.no_show_validators.len() + no_show_validators.len() <
MAX_RECORDED_NO_SHOW_VALIDATORS_PER_CANDIDATE
{
self.no_show_validators.extend(no_show_validators);
}
State {
assignments,
depth,
covered,
covering,
uncovered,
next_no_show,
last_assignment_tick,
total_observed_no_shows: self.total_observed_no_shows + new_no_shows,
no_show_validators: self.no_show_validators,
}
}
}
fn filled_tranche_iterator(
tranches: &[TrancheEntry],
) -> impl Iterator<Item = (DelayTranche, &[(ValidatorIndex, Tick)])> {
let mut gap_end = None;
let approval_entries_filled = tranches.iter().flat_map(move |tranche_entry| {
let tranche = tranche_entry.tranche();
let assignments = tranche_entry.assignments();
let gap_start = gap_end.map(|end| end + 1).unwrap_or(tranche);
gap_end = Some(tranche);
(gap_start..tranche)
.map(|i| (i, &[] as &[_]))
.chain(std::iter::once((tranche, assignments)))
});
let pre_end = tranches.first().map(|t| t.tranche());
let post_start = tranches.last().map_or(0, |t| t.tranche() + 1);
let pre = pre_end.into_iter().flat_map(|pre_end| (0..pre_end).map(|i| (i, &[] as &[_])));
let post = (post_start..).map(|i| (i, &[] as &[_]));
pre.chain(approval_entries_filled).chain(post)
}
fn count_no_shows(
assignments: &[(ValidatorIndex, Tick)],
approvals: &BitSlice<u8, BitOrderLsb0>,
clock_drift: Tick,
block_tick: Tick,
no_show_duration: Tick,
drifted_tick_now: Tick,
) -> (usize, Option<u64>, Vec<ValidatorIndex>) {
let mut next_no_show = None;
let mut no_show_validators = Vec::new();
let no_shows = assignments
.iter()
.map(|(v_index, tick)| {
(v_index, tick.max(&block_tick).saturating_sub(clock_drift) + no_show_duration)
})
.filter(|&(v_index, no_show_at)| {
let has_approved = if let Some(approved) = approvals.get(v_index.0 as usize) {
*approved
} else {
return false
};
let is_no_show = !has_approved && no_show_at <= drifted_tick_now;
if !is_no_show && !has_approved {
next_no_show = super::min_prefer_some(next_no_show, Some(no_show_at + clock_drift));
}
if is_no_show {
no_show_validators.push(*v_index);
}
is_no_show
})
.count();
(no_shows, next_no_show, no_show_validators)
}
pub fn tranches_to_approve(
approval_entry: &ApprovalEntry,
approvals: &BitSlice<u8, BitOrderLsb0>,
tranche_now: DelayTranche,
block_tick: Tick,
no_show_duration: Tick,
needed_approvals: usize,
) -> TranchesToApproveResult {
let tick_now = tranche_now as Tick + block_tick;
let n_validators = approval_entry.n_validators();
let initial_state = State {
assignments: 0,
depth: 0,
covered: 0,
covering: needed_approvals,
uncovered: 0,
next_no_show: None,
last_assignment_tick: None,
total_observed_no_shows: 0,
no_show_validators: Vec::new(),
};
let tranches_with_gaps_filled = filled_tranche_iterator(approval_entry.tranches());
tranches_with_gaps_filled
.scan(Some(initial_state), |state, (tranche, assignments)| {
let s = state.take()?;
let clock_drift = s.clock_drift(no_show_duration);
let drifted_tick_now = tick_now.saturating_sub(clock_drift);
let drifted_tranche_now = drifted_tick_now.saturating_sub(block_tick) as DelayTranche;
if tranche > drifted_tranche_now {
return None;
}
let n_assignments = assignments.iter()
.filter(|(v_index, _)| v_index.0 < n_validators as u32)
.count();
let last_assignment_tick = assignments.iter()
.map(|(_, t)| *t)
.max();
let (no_shows, next_no_show, no_show_validators) = count_no_shows(
assignments,
approvals,
clock_drift,
block_tick,
no_show_duration,
drifted_tick_now,
);
let s = s.advance(n_assignments, no_shows, next_no_show, last_assignment_tick, no_show_validators);
let output = s.output(tranche, needed_approvals, n_validators, no_show_duration);
*state = match output.required_tranches {
RequiredTranches::Exact { .. } | RequiredTranches::All => {
None
}
RequiredTranches::Pending { .. } => {
Some(s)
}
};
Some(output)
})
.last()
.expect("the underlying iterator is infinite, starts at 0, and never exits early before tranche 1; qed")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{approval_db, BTreeMap};
use bitvec::{bitvec, order::Lsb0 as BitOrderLsb0, vec::BitVec};
use polkadot_primitives::GroupIndex;
use polkadot_primitives_test_helpers::{dummy_candidate_receipt_v2, dummy_hash};
#[test]
fn pending_is_not_approved() {
let candidate = CandidateEntry::from_v1(
approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt_v2(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: BitVec::default(),
},
0,
);
let approval_entry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: BitVec::default(),
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
assert!(!check_approval(
&candidate,
&approval_entry,
RequiredTranches::Pending {
considered: 0,
next_no_show: None,
maximum_broadcast: 0,
clock_drift: 0,
},
)
.is_approved(Tick::max_value()));
}
#[test]
fn exact_takes_only_assignments_up_to() {
let mut candidate: CandidateEntry = CandidateEntry::from_v1(
approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt_v2(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: bitvec![u8, BitOrderLsb0; 0; 10],
},
0,
);
for i in 0..3 {
candidate.mark_approval(ValidatorIndex(i));
}
let approval_entry = approval_db::v3::ApprovalEntry {
tranches: vec![
approval_db::v3::TrancheEntry {
tranche: 0,
assignments: (0..2).map(|i| (ValidatorIndex(i), 0.into())).collect(),
},
approval_db::v3::TrancheEntry {
tranche: 1,
assignments: (2..5).map(|i| (ValidatorIndex(i), 1.into())).collect(),
},
approval_db::v3::TrancheEntry {
tranche: 2,
assignments: (5..10).map(|i| (ValidatorIndex(i), 0.into())).collect(),
},
],
assigned_validators: bitvec![u8, BitOrderLsb0; 1; 10],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
assert!(check_approval(
&candidate,
&approval_entry,
RequiredTranches::Exact {
needed: 0,
tolerated_missing: 0,
next_no_show: None,
last_assignment_tick: None
},
)
.is_approved(Tick::max_value()));
assert!(!check_approval(
&candidate,
&approval_entry,
RequiredTranches::Exact {
needed: 1,
tolerated_missing: 0,
next_no_show: None,
last_assignment_tick: None
},
)
.is_approved(Tick::max_value()));
assert!(check_approval(
&candidate,
&approval_entry,
RequiredTranches::Exact {
needed: 1,
tolerated_missing: 2,
next_no_show: None,
last_assignment_tick: None
},
)
.is_approved(Tick::max_value()));
}
#[test]
fn one_honest_node_always_approves() {
let mut candidate: CandidateEntry = CandidateEntry::from_v1(
approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt_v2(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: bitvec![u8, BitOrderLsb0; 0; 10],
},
0,
);
for i in 0..3 {
candidate.mark_approval(ValidatorIndex(i));
}
let approval_entry = approval_db::v3::ApprovalEntry {
tranches: vec![
approval_db::v3::TrancheEntry {
tranche: 0,
assignments: (0..4).map(|i| (ValidatorIndex(i), 0.into())).collect(),
},
approval_db::v3::TrancheEntry {
tranche: 1,
assignments: (4..6).map(|i| (ValidatorIndex(i), 1.into())).collect(),
},
approval_db::v3::TrancheEntry {
tranche: 2,
assignments: (6..10).map(|i| (ValidatorIndex(i), 0.into())).collect(),
},
],
assigned_validators: bitvec![u8, BitOrderLsb0; 1; 10],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
let exact_all = RequiredTranches::Exact {
needed: 10,
tolerated_missing: 0,
next_no_show: None,
last_assignment_tick: None,
};
let pending_all = RequiredTranches::Pending {
considered: 5,
next_no_show: None,
maximum_broadcast: 8,
clock_drift: 12,
};
assert!(!check_approval(&candidate, &approval_entry, RequiredTranches::All,)
.is_approved(Tick::max_value()));
assert!(!check_approval(&candidate, &approval_entry, exact_all.clone(),)
.is_approved(Tick::max_value()));
assert!(!check_approval(&candidate, &approval_entry, pending_all.clone(),)
.is_approved(Tick::max_value()));
candidate.mark_approval(ValidatorIndex(3));
assert!(check_approval(&candidate, &approval_entry, RequiredTranches::All,)
.is_approved(Tick::max_value()));
assert!(
check_approval(&candidate, &approval_entry, exact_all,).is_approved(Tick::max_value())
);
assert!(check_approval(&candidate, &approval_entry, pending_all,)
.is_approved(Tick::max_value()));
}
#[test]
fn tranches_to_approve_everyone_present() {
let block_tick = 20;
let no_show_duration = 10;
let needed_approvals = 4;
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; 5],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1);
approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1);
approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + 2);
let approvals = bitvec![u8, BitOrderLsb0; 1; 5];
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
2,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Exact {
needed: 1,
tolerated_missing: 0,
next_no_show: None,
last_assignment_tick: Some(21)
},
);
}
#[test]
fn tranches_to_approve_not_enough_initial_count() {
let block_tick = 20;
let no_show_duration = 10;
let needed_approvals = 4;
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; 10],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
approval_entry.import_assignment(1, ValidatorIndex(2), block_tick);
let approvals = bitvec![u8, BitOrderLsb0; 0; 10];
let tranche_now = 2;
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Pending {
considered: 2,
next_no_show: Some(block_tick + no_show_duration),
maximum_broadcast: DelayTranche::max_value(),
clock_drift: 0,
},
);
}
#[test]
fn tranches_to_approve_no_shows_before_initial_count_treated_same_as_not_initial() {
let block_tick = 20;
let no_show_duration = 10;
let needed_approvals = 4;
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; 10],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
approval_entry.import_assignment(1, ValidatorIndex(2), block_tick);
let mut approvals = bitvec![u8, BitOrderLsb0; 0; 10];
approvals.set(0, true);
approvals.set(1, true);
let tranche_now = no_show_duration as DelayTranche + 1;
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Pending {
considered: 11,
next_no_show: None,
maximum_broadcast: DelayTranche::max_value(),
clock_drift: 0,
},
);
}
#[test]
fn tranches_to_approve_cover_no_show_not_enough() {
let block_tick = 20;
let no_show_duration = 10;
let needed_approvals = 4;
let n_validators = 8;
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
approval_entry.import_assignment(1, ValidatorIndex(2), block_tick);
approval_entry.import_assignment(1, ValidatorIndex(3), block_tick);
let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators];
approvals.set(0, true);
approvals.set(1, true);
approvals.set(3, true);
let tranche_now = no_show_duration as DelayTranche + 1;
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Pending {
considered: 1,
next_no_show: None,
maximum_broadcast: 2, clock_drift: 1 * no_show_duration,
}
);
approvals.set(0, false);
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Pending {
considered: 1,
next_no_show: None,
maximum_broadcast: 3, clock_drift: 1 * no_show_duration,
}
);
}
#[test]
fn tranches_to_approve_multi_cover_not_enough() {
let block_tick = 20;
let no_show_duration = 10;
let needed_approvals = 4;
let n_validators = 8;
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1);
approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1);
approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + no_show_duration + 2);
approval_entry.import_assignment(2, ValidatorIndex(5), block_tick + no_show_duration + 2);
let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators];
approvals.set(0, true);
approvals.set(1, true);
approvals.set(3, true);
approvals.set(5, true);
let tranche_now = 1;
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Exact {
needed: 1,
tolerated_missing: 0,
next_no_show: Some(block_tick + no_show_duration + 1),
last_assignment_tick: Some(block_tick + 1),
},
);
let tranche_now = no_show_duration as DelayTranche + 2;
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Exact {
needed: 2,
tolerated_missing: 1,
next_no_show: Some(block_tick + 2 * no_show_duration + 2),
last_assignment_tick: Some(block_tick + no_show_duration + 2),
},
);
let tranche_now = (no_show_duration * 2) as DelayTranche + 2;
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Pending {
considered: 2,
next_no_show: None,
maximum_broadcast: 3, clock_drift: 2 * no_show_duration,
},
);
}
#[test]
fn tranches_to_approve_cover_no_show() {
let block_tick = 20;
let no_show_duration = 10;
let needed_approvals = 4;
let n_validators = 8;
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1);
approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1);
approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + no_show_duration + 2);
approval_entry.import_assignment(2, ValidatorIndex(5), block_tick + no_show_duration + 2);
let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators];
approvals.set(0, true);
approvals.set(1, true);
approvals.set(3, true);
approvals.set(4, true);
approvals.set(5, true);
let tranche_now = no_show_duration as DelayTranche + 2;
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Exact {
needed: 2,
tolerated_missing: 1,
next_no_show: None,
last_assignment_tick: Some(block_tick + no_show_duration + 2)
},
);
approvals.set(0, false);
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Pending {
considered: 2,
next_no_show: None,
maximum_broadcast: 3,
clock_drift: no_show_duration,
},
);
approval_entry.import_assignment(3, ValidatorIndex(6), block_tick);
approvals.set(6, true);
let tranche_now = no_show_duration as DelayTranche + 3;
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Exact {
needed: 3,
tolerated_missing: 2,
next_no_show: None,
last_assignment_tick: Some(block_tick + no_show_duration + 2),
},
);
}
#[test]
fn validator_indexes_out_of_range_are_ignored_in_assignments() {
let block_tick = 20;
let no_show_duration = 10;
let needed_approvals = 3;
let mut candidate: CandidateEntry = CandidateEntry::from_v1(
approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt_v2(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: bitvec![u8, BitOrderLsb0; 0; 3],
},
0,
);
for i in 0..3 {
candidate.mark_approval(ValidatorIndex(i));
}
let approval_entry = approval_db::v3::ApprovalEntry {
tranches: vec![
approval_db::v3::TrancheEntry {
tranche: 1,
assignments: (2..5).map(|i| (ValidatorIndex(i), 1.into())).collect(),
},
],
assigned_validators: bitvec![u8, BitOrderLsb0; 1; 3],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}
.into();
let approvals = bitvec![u8, BitOrderLsb0; 0; 3];
let tranche_now = 10;
assert_eq!(
tranches_to_approve(
&approval_entry,
&approvals,
tranche_now,
block_tick,
no_show_duration,
needed_approvals,
)
.required_tranches,
RequiredTranches::Pending {
considered: 10,
next_no_show: None,
maximum_broadcast: DelayTranche::max_value(),
clock_drift: 0,
},
);
}
#[test]
fn filled_tranche_iterator_yields_sequential_tranches() {
const PREFIX: u32 = 10;
let test_tranches = vec![
vec![], vec![0], vec![0, 3], vec![2], vec![2, 4], vec![0, 1, 2], vec![2, 3, 4, 8], vec![0, 1, 2, 5, 6, 7], ];
for test_tranche in test_tranches {
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
backing_group: GroupIndex(0),
our_assignment: None,
our_approval_sig: None,
assigned_validators: bitvec![u8, BitOrderLsb0; 0; 3],
approved: false,
}
.into();
for &t in &test_tranche {
approval_entry.import_assignment(t, ValidatorIndex(0), 0)
}
let filled_tranches = filled_tranche_iterator(approval_entry.tranches());
let tranches: Vec<DelayTranche> =
filled_tranches.take(PREFIX as usize).map(|e| e.0).collect();
let exp_tranches: Vec<DelayTranche> = (0..PREFIX).collect();
assert_eq!(tranches, exp_tranches, "for test tranches: {:?}", test_tranche);
}
}
#[derive(Debug)]
struct NoShowTest {
assignments: Vec<(ValidatorIndex, Tick)>,
approvals: Vec<usize>,
clock_drift: Tick,
no_show_duration: Tick,
drifted_tick_now: Tick,
exp_no_shows: usize,
exp_next_no_show: Option<u64>,
}
fn test_count_no_shows(test: NoShowTest) {
let n_validators = 4;
let block_tick = 20;
let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators];
for &v_index in &test.approvals {
approvals.set(v_index, true);
}
let (no_shows, next_no_show, _) = count_no_shows(
&test.assignments,
&approvals,
test.clock_drift,
block_tick,
test.no_show_duration,
test.drifted_tick_now,
);
assert_eq!(no_shows, test.exp_no_shows, "for test: {:?}", test);
assert_eq!(next_no_show, test.exp_next_no_show, "for test {:?}", test);
}
#[test]
fn count_no_shows_empty_assignments() {
test_count_no_shows(NoShowTest {
assignments: vec![],
approvals: vec![],
clock_drift: 0,
no_show_duration: 0,
drifted_tick_now: 0,
exp_no_shows: 0,
exp_next_no_show: None,
})
}
#[test]
fn count_no_shows_single_validator_is_next_no_show() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1), 31)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: Some(41),
})
}
#[test]
fn count_no_shows_single_validator_approval_at_drifted_tick_now() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1), 20)],
approvals: vec![1],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: None,
})
}
#[test]
fn count_no_shows_single_validator_approval_after_drifted_tick_now() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1), 21)],
approvals: vec![1],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: None,
})
}
#[test]
fn count_no_shows_two_validators_next_no_show_ordered_first() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1), 31), (ValidatorIndex(2), 32)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: Some(41),
})
}
#[test]
fn count_no_shows_two_validators_next_no_show_ordered_last() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1), 32), (ValidatorIndex(2), 31)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: Some(41),
})
}
#[test]
fn count_no_shows_three_validators_one_almost_late_one_no_show_one_approving() {
test_count_no_shows(NoShowTest {
assignments: vec![
(ValidatorIndex(1), 31),
(ValidatorIndex(2), 19),
(ValidatorIndex(3), 19),
],
approvals: vec![3],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 1,
exp_next_no_show: Some(41),
})
}
#[test]
fn count_no_shows_three_no_show_validators() {
test_count_no_shows(NoShowTest {
assignments: vec![
(ValidatorIndex(1), 20),
(ValidatorIndex(2), 20),
(ValidatorIndex(3), 20),
],
approvals: vec![],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 3,
exp_next_no_show: None,
})
}
#[test]
fn count_no_shows_three_approving_validators() {
test_count_no_shows(NoShowTest {
assignments: vec![
(ValidatorIndex(1), 20),
(ValidatorIndex(2), 20),
(ValidatorIndex(3), 20),
],
approvals: vec![1, 2, 3],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: None,
})
}
#[test]
fn count_no_shows_earliest_possible_next_no_show_is_clock_drift_plus_no_show_duration() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1), 0)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 20,
drifted_tick_now: 0,
exp_no_shows: 0,
exp_next_no_show: Some(40),
})
}
#[test]
fn count_no_shows_assignment_tick_equal_to_clock_drift_yields_earliest_possible_next_no_show() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1), 10)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 20,
drifted_tick_now: 0,
exp_no_shows: 0,
exp_next_no_show: Some(40),
})
}
#[test]
fn count_no_shows_validator_index_out_of_approvals_range_is_ignored_as_no_show() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1000), 20)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: None,
})
}
#[test]
fn count_no_shows_validator_index_out_of_approvals_range_is_ignored_as_next_no_show() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1000), 21)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: None,
})
}
#[test]
fn depth_0_covering_not_treated_as_such() {
let state = State {
assignments: 0,
depth: 0,
covered: 0,
covering: 10,
uncovered: 0,
next_no_show: None,
last_assignment_tick: None,
total_observed_no_shows: 0,
no_show_validators: Default::default(),
};
assert_eq!(
state.output(0, 10, 10, 20).required_tranches,
RequiredTranches::Pending {
considered: 0,
next_no_show: None,
maximum_broadcast: DelayTranche::max_value(),
clock_drift: 0,
},
);
}
#[test]
fn depth_0_issued_as_exact_even_when_all() {
let state = State {
assignments: 10,
depth: 0,
covered: 0,
covering: 0,
uncovered: 0,
next_no_show: None,
last_assignment_tick: None,
total_observed_no_shows: 0,
no_show_validators: Default::default(),
};
assert_eq!(
state.output(0, 10, 10, 20).required_tranches,
RequiredTranches::Exact {
needed: 0,
tolerated_missing: 0,
next_no_show: None,
last_assignment_tick: None
},
);
}
}