Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Extract and test count_no_shows method for approval voting #3264

Merged
3 commits merged into from
Jun 17, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 242 additions & 20 deletions node/core/approval-voting/src/approval_checking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,42 @@ fn filled_tranche_iterator<'a>(
pre.chain(approval_entries_filled).chain(post)
}

/// Computes the number of no_show validators in a set of assignments given the relevant approvals
/// and tick parameters. This method also returns the next tick at which a no_show will occur
/// amongst the set of validators that have not submitted an approval.
///
/// If the returned `next_no_show` is not None, there are two possible cases for the value of
/// based on the earliest assignment `tick` of a non-approving, yet-to-be-no-show validator:
/// - if `tick` <= `clock_drift`: the value will always be `clock_drift` + `no_show_duration`.
/// - if `tick` > `clock_drift`: the value is equal to `tick` + `no_show_duration`.
fn count_no_shows(
assignments: &[(ValidatorIndex, Tick)],
approvals: &BitSlice<BitOrderLsb0, u8>,
clock_drift: Tick,
no_show_duration: Tick,
drifted_tick_now: Tick,
) -> (usize, Option<u64>) {
let mut next_no_show = None;
let no_shows = assignments.iter()
.map(|(v_index, tick)| (v_index, tick.saturating_sub(clock_drift) + no_show_duration))
.filter(|&(v_index, no_show_at)| {
let has_approved = approvals.get(v_index.0 as usize).map(|b| *b).unwrap_or(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),
);
}

is_no_show
}).count();

(no_shows, next_no_show)
}

/// Determine the amount of tranches of assignments needed to determine approval of a candidate.
pub fn tranches_to_approve(
approval_entry: &ApprovalEntry,
Expand Down Expand Up @@ -346,26 +382,13 @@ pub fn tranches_to_approve(
//
// While we count the no-shows, we also determine the next possible no-show we might
// see within this tranche.
let mut next_no_show = None;
let no_shows = {
let next_no_show = &mut next_no_show;
assignments.iter()
.map(|(v_index, tick)| (v_index, tick.saturating_sub(clock_drift) + no_show_duration))
.filter(|&(v_index, no_show_at)| {
let has_approved = approvals.get(v_index.0 as usize).map(|b| *b).unwrap_or(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),
);
}

is_no_show
}).count()
};
let (no_shows, next_no_show) = count_no_shows(
assignments,
approvals,
clock_drift,
no_show_duration,
drifted_tick_now,
);

let s = s.advance(n_assignments, no_shows, next_no_show);
let output = s.output(tranche, needed_approvals, n_validators, no_show_duration);
Expand Down Expand Up @@ -993,6 +1016,205 @@ mod tests {
assert_eq!(tranches, exp_tranches, "for test tranches: {:?}", test_tranche);
}
}

#[derive(Debug)]
struct NoShowTest {
assignments: Vec<(ValidatorIndex, Tick)>,
approvals: Vec<usize>,
clock_drift: crate::time::Tick,
no_show_duration: crate::time::Tick,
drifted_tick_now: crate::time::Tick,
exp_no_shows: usize,
exp_next_no_show: Option<u64>,
}

fn test_count_no_shows(test: NoShowTest) {
let n_validators = 4;

let mut approvals = bitvec![BitOrderLsb0, u8; 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,
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), 21)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: Some(31),
})
}

#[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), 21), (ValidatorIndex(2), 22)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: Some(31),
})
}

#[test]
fn count_no_shows_two_validators_next_no_show_ordered_last() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1), 22), (ValidatorIndex(2), 21)],
approvals: vec![],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 0,
exp_next_no_show: Some(31),
})
}

#[test]
fn count_no_shows_three_validators_one_almost_late_one_no_show_one_approving() {
test_count_no_shows(NoShowTest {
assignments: vec![(ValidatorIndex(1), 21), (ValidatorIndex(2), 20), (ValidatorIndex(3), 20)],
approvals: vec![3],
clock_drift: 10,
no_show_duration: 10,
drifted_tick_now: 20,
exp_no_shows: 1,
exp_next_no_show: Some(31),
})
}

#[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(30),
})
}

#[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(30),
})
}

#[test]
fn count_no_shows_validator_index_out_of_approvals_range_is_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: 1, // TODO(ladi): should this be counted or ignored?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should validator indexes past the length of the approvals vector be ignored or handled?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be ignored; Garbage-in-Garbage-out

exp_next_no_show: None,
})
}

#[test]
fn count_no_shows_validator_index_out_of_approvals_range_is_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: Some(31), // TODO(ladi) should this be included or ignored?
})
}
}

#[test]
Expand Down