Unverified Commit b900c5a8 authored by Lldenaurois's avatar Lldenaurois Committed by GitHub
Browse files

node/approval-voting: Continue to migrate tests to subsystem tests (#3471)

* node/approval-voting: Continue to migrate tests to subsystem tests

* node/approval-voting: Continue to implement subsystem tests.

* Add more tests

* node/approval-voting: Add more tests

* node/approval-voting: Difficuly determining the proper test scenario for threshold tests

* node/approval-voting: Introduce should_trigger_assignment tests

* node/approval-voting: Finalize threshold tests

* node/approval-voting: Address Feedback and add comments

* node/approval-voting: Tidy up approval tests

* Tidy up tests

* Fix rustfmt
parent e7eaec7e
Pipeline #151162 passed with stages
in 33 minutes and 50 seconds
......@@ -92,9 +92,6 @@ use crate::{
#[cfg(test)]
mod tests;
#[cfg(test)]
mod old_tests;
const APPROVAL_SESSIONS: SessionIndex = 6;
const APPROVAL_CHECKING_TIMEOUT: Duration = Duration::from_secs(120);
const APPROVAL_CACHE_SIZE: usize = 1024;
......@@ -455,8 +452,9 @@ impl Wakeups {
Some(tick) => {
clock.wait(tick).await;
match self.wakeups.entry(tick) {
Entry::Vacant(_) =>
panic!("entry is known to exist since `first` was `Some`; qed"),
Entry::Vacant(_) => {
panic!("entry is known to exist since `first` was `Some`; qed")
},
Entry::Occupied(mut entry) => {
let (hash, candidate_hash) = entry.get_mut().pop()
.expect("empty entries are removed here and in `schedule`; no other mutation of this map; qed");
......@@ -919,8 +917,9 @@ async fn handle_actions(
.await;
match confirmation_rx.await {
Err(oneshot::Canceled) =>
tracing::warn!(target: LOG_TARGET, "Dispute coordinator confirmation lost",),
Err(oneshot::Canceled) => {
tracing::warn!(target: LOG_TARGET, "Dispute coordinator confirmation lost",)
},
Ok(ImportStatementsResult::ValidImport) => {},
Ok(ImportStatementsResult::InvalidImport) => tracing::warn!(
target: LOG_TARGET,
......
// Copyright 2021 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/>.
use super::{
approval_db::v1::Config,
backend::{Backend, BackendWriteOp},
*,
};
use polkadot_node_primitives::approval::{
AssignmentCert, AssignmentCertKind, DelayTranche, VRFOutput, VRFProof, RELAY_VRF_MODULO_CONTEXT,
};
use polkadot_node_subsystem::messages::{AllMessages, HighestApprovedAncestorBlock};
use polkadot_node_subsystem_test_helpers::make_subsystem_context;
use polkadot_primitives::v1::{
CandidateDescriptor, CoreIndex, DisputeStatement, GroupIndex, ValidDisputeStatementKind,
ValidatorSignature,
};
use sp_core::testing::TaskExecutor;
use assert_matches::assert_matches;
use bitvec::order::Lsb0 as BitOrderLsb0;
use parking_lot::Mutex;
use sp_keyring::sr25519::Keyring as Sr25519Keyring;
use std::{pin::Pin, sync::Arc};
const SLOT_DURATION_MILLIS: u64 = 5000;
const DATA_COL: u32 = 0;
const NUM_COLUMNS: u32 = 1;
const TEST_CONFIG: Config = Config { col_data: DATA_COL };
fn make_db() -> DbBackend {
let db_writer: Arc<dyn KeyValueDB> = Arc::new(kvdb_memorydb::create(NUM_COLUMNS));
DbBackend::new(db_writer.clone(), TEST_CONFIG)
}
fn overlay_txn<T, F>(db: &mut T, mut f: F)
where
T: Backend,
F: FnMut(&mut OverlayedBackend<'_, T>),
{
let mut overlay_db = OverlayedBackend::new(db);
f(&mut overlay_db);
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
}
fn slot_to_tick(t: impl Into<Slot>) -> crate::time::Tick {
crate::time::slot_number_to_tick(SLOT_DURATION_MILLIS, t.into())
}
#[derive(Default, Clone)]
struct MockClock {
inner: Arc<Mutex<MockClockInner>>,
}
impl MockClock {
fn new(tick: Tick) -> Self {
let me = Self::default();
me.inner.lock().set_tick(tick);
me
}
}
impl Clock for MockClock {
fn tick_now(&self) -> Tick {
self.inner.lock().tick
}
fn wait(&self, tick: Tick) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
let rx = self.inner.lock().register_wakeup(tick, true);
Box::pin(async move {
rx.await.expect("i exist in a timeless void. yet, i remain");
})
}
}
// This mock clock allows us to manipulate the time and
// be notified when wakeups have been triggered.
#[derive(Default)]
struct MockClockInner {
tick: Tick,
wakeups: Vec<(Tick, oneshot::Sender<()>)>,
}
impl MockClockInner {
fn set_tick(&mut self, tick: Tick) {
self.tick = tick;
self.wakeup_all(tick);
}
fn wakeup_all(&mut self, up_to: Tick) {
// This finds the position of the first wakeup after
// the given tick, or the end of the map.
let drain_up_to =
self.wakeups.binary_search_by_key(&(up_to + 1), |w| w.0).unwrap_or_else(|i| i);
for (_, wakeup) in self.wakeups.drain(..drain_up_to) {
let _ = wakeup.send(());
}
}
// If `pre_emptive` is true, we compare the given tick to the internal
// tick of the clock for an early return.
//
// Otherwise, the wakeup will only trigger alongside another wakeup of
// equal or greater tick.
//
// When the pre-emptive wakeup is disabled, this can be used in combination with
// a preceding call to `set_tick` to wait until some other wakeup at that same tick
// has been triggered.
fn register_wakeup(&mut self, tick: Tick, pre_emptive: bool) -> oneshot::Receiver<()> {
let (tx, rx) = oneshot::channel();
let pos = self.wakeups.binary_search_by_key(&tick, |w| w.0).unwrap_or_else(|i| i);
self.wakeups.insert(pos, (tick, tx));
if pre_emptive {
// if `tick > self.tick`, this won't wake up the new
// listener.
self.wakeup_all(self.tick);
}
rx
}
}
struct MockAssignmentCriteria<Compute, Check>(Compute, Check);
impl<Compute, Check> AssignmentCriteria for MockAssignmentCriteria<Compute, Check>
where
Compute: Fn() -> HashMap<polkadot_primitives::v1::CoreIndex, criteria::OurAssignment>,
Check: Fn() -> Result<DelayTranche, criteria::InvalidAssignment>,
{
fn compute_assignments(
&self,
_keystore: &LocalKeystore,
_relay_vrf_story: polkadot_node_primitives::approval::RelayVRFStory,
_config: &criteria::Config,
_leaving_cores: Vec<(
CandidateHash,
polkadot_primitives::v1::CoreIndex,
polkadot_primitives::v1::GroupIndex,
)>,
) -> HashMap<polkadot_primitives::v1::CoreIndex, criteria::OurAssignment> {
self.0()
}
fn check_assignment_cert(
&self,
_claimed_core_index: polkadot_primitives::v1::CoreIndex,
_validator_index: ValidatorIndex,
_config: &criteria::Config,
_relay_vrf_story: polkadot_node_primitives::approval::RelayVRFStory,
_assignment: &polkadot_node_primitives::approval::AssignmentCert,
_backing_group: polkadot_primitives::v1::GroupIndex,
) -> Result<polkadot_node_primitives::approval::DelayTranche, criteria::InvalidAssignment> {
self.1()
}
}
impl<F>
MockAssignmentCriteria<
fn() -> HashMap<polkadot_primitives::v1::CoreIndex, criteria::OurAssignment>,
F,
>
{
fn check_only(f: F) -> Self {
MockAssignmentCriteria(Default::default, f)
}
}
fn blank_state() -> State {
State {
session_window: RollingSessionWindow::new(APPROVAL_SESSIONS),
keystore: Arc::new(LocalKeystore::in_memory()),
slot_duration_millis: SLOT_DURATION_MILLIS,
clock: Box::new(MockClock::default()),
assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|| Ok(0))),
}
}
fn single_session_state(index: SessionIndex, info: SessionInfo) -> State {
State {
session_window: RollingSessionWindow::with_session_info(
APPROVAL_SESSIONS,
index,
vec![info],
),
..blank_state()
}
}
fn garbage_assignment_cert(kind: AssignmentCertKind) -> AssignmentCert {
let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT);
let msg = b"test-garbage";
let mut prng = rand_core::OsRng;
let keypair = schnorrkel::Keypair::generate_with(&mut prng);
let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg));
let out = inout.to_output();
AssignmentCert { kind, vrf: (VRFOutput(out), VRFProof(proof)) }
}
fn sign_approval(
key: Sr25519Keyring,
candidate_hash: CandidateHash,
session_index: SessionIndex,
) -> ValidatorSignature {
key.sign(&ApprovalVote(candidate_hash).signing_payload(session_index)).into()
}
#[derive(Clone)]
struct StateConfig {
session_index: SessionIndex,
slot: Slot,
tick: Tick,
validators: Vec<Sr25519Keyring>,
validator_groups: Vec<Vec<ValidatorIndex>>,
needed_approvals: u32,
no_show_slots: u32,
candidate_hash: Option<CandidateHash>,
}
impl Default for StateConfig {
fn default() -> Self {
StateConfig {
session_index: 1,
slot: Slot::from(0),
tick: 0,
validators: vec![Sr25519Keyring::Alice, Sr25519Keyring::Bob],
validator_groups: vec![vec![ValidatorIndex(0)], vec![ValidatorIndex(1)]],
needed_approvals: 1,
no_show_slots: 2,
candidate_hash: None,
}
}
}
// one block with one candidate. Alice and Bob are in the assignment keys.
fn some_state(config: StateConfig, db: &mut DbBackend) -> State {
let StateConfig {
session_index,
slot,
tick,
validators,
validator_groups,
needed_approvals,
no_show_slots,
candidate_hash,
} = config;
let n_validators = validators.len();
let state = State {
clock: Box::new(MockClock::new(tick)),
..single_session_state(
session_index,
SessionInfo {
validators: validators.iter().map(|v| v.public().into()).collect(),
discovery_keys: validators.iter().map(|v| v.public().into()).collect(),
assignment_keys: validators.iter().map(|v| v.public().into()).collect(),
validator_groups: validator_groups.clone(),
n_cores: validator_groups.len() as _,
zeroth_delay_tranche_width: 5,
relay_vrf_modulo_samples: 3,
n_delay_tranches: 50,
no_show_slots,
needed_approvals,
..Default::default()
},
)
};
let core_index = 0.into();
let block_hash = Hash::repeat_byte(0x01);
let candidate_hash = candidate_hash.unwrap_or_else(|| CandidateHash(Hash::repeat_byte(0xCC)));
add_block(db, block_hash, session_index, slot);
add_candidate_to_block(
db,
block_hash,
candidate_hash,
n_validators,
core_index,
GroupIndex(0),
None,
);
state
}
fn add_block(db: &mut DbBackend, block_hash: Hash, session: SessionIndex, slot: Slot) {
overlay_txn(db, |overlay_db| {
overlay_db.write_block_entry(
approval_db::v1::BlockEntry {
block_hash,
parent_hash: Default::default(),
block_number: 0,
session,
slot,
candidates: Vec::new(),
relay_vrf_story: Default::default(),
approved_bitfield: Default::default(),
children: Default::default(),
}
.into(),
)
});
}
fn add_candidate_to_block(
db: &mut DbBackend,
block_hash: Hash,
candidate_hash: CandidateHash,
n_validators: usize,
core: CoreIndex,
backing_group: GroupIndex,
candidate_receipt: Option<CandidateReceipt>,
) {
let mut block_entry = db.load_block_entry(&block_hash).unwrap().unwrap();
let mut candidate_entry =
db.load_candidate_entry(&candidate_hash).unwrap().unwrap_or_else(|| {
approval_db::v1::CandidateEntry {
session: block_entry.session(),
block_assignments: Default::default(),
candidate: candidate_receipt.unwrap_or_default(),
approvals: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
}
.into()
});
block_entry.add_candidate(core, candidate_hash);
candidate_entry.add_approval_entry(
block_hash,
approval_db::v1::ApprovalEntry {
tranches: Vec::new(),
backing_group,
our_assignment: None,
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
approved: false,
}
.into(),
);
overlay_txn(db, |overlay_db| {
overlay_db.write_block_entry(block_entry.clone());
overlay_db.write_candidate_entry(candidate_entry.clone());
})
}
fn import_assignment<F>(
db: &mut DbBackend,
candidate_hash: &CandidateHash,
block_hash: &Hash,
validator_index: ValidatorIndex,
mut f: F,
) where
F: FnMut(&mut CandidateEntry),
{
let mut candidate_entry = db.load_candidate_entry(candidate_hash).unwrap().unwrap();
candidate_entry.approval_entry_mut(block_hash).unwrap().import_assignment(
0,
validator_index,
0,
);
f(&mut candidate_entry);
overlay_txn(db, |overlay_db| overlay_db.write_candidate_entry(candidate_entry.clone()));
}
fn set_our_assignment<F>(
db: &mut DbBackend,
candidate_hash: &CandidateHash,
block_hash: &Hash,
tranche: DelayTranche,
mut f: F,
) where
F: FnMut(&mut CandidateEntry),
{
let mut candidate_entry = db.load_candidate_entry(&candidate_hash).unwrap().unwrap();
let approval_entry = candidate_entry.approval_entry_mut(&block_hash).unwrap();
approval_entry.set_our_assignment(
approval_db::v1::OurAssignment {
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }),
tranche,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
f(&mut candidate_entry);
overlay_txn(db, |overlay_db| overlay_db.write_candidate_entry(candidate_entry.clone()));
}
#[test]
fn rejects_bad_assignment() {
let mut db = make_db();
let block_hash = Hash::repeat_byte(0x01);
let candidate_hash = CandidateReceipt::<Hash>::default().hash();
let assignment_good = IndirectAssignmentCert {
block_hash,
validator: ValidatorIndex(0),
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }),
};
let mut state = some_state(
StateConfig { candidate_hash: Some(candidate_hash), ..Default::default() },
&mut db,
);
let candidate_index = 0;
let mut overlay_db = OverlayedBackend::new(&db);
let res = check_and_import_assignment(
&mut state,
&mut overlay_db,
assignment_good.clone(),
candidate_index,
)
.unwrap();
assert_eq!(res.0, AssignmentCheckResult::Accepted);
// Check that the assignment's been imported.
assert_eq!(res.1.len(), 1);
let write_ops = overlay_db.into_write_ops().collect::<Vec<BackendWriteOp>>();
assert_eq!(write_ops.len(), 1);
assert_matches!(write_ops.get(0).unwrap(), BackendWriteOp::WriteCandidateEntry(..));
db.write(write_ops).unwrap();
// unknown hash
let unknown_hash = Hash::repeat_byte(0x02);
let assignment = IndirectAssignmentCert {
block_hash: unknown_hash,
validator: ValidatorIndex(0),
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }),
};
let mut overlay_db = OverlayedBackend::new(&db);
let res = check_and_import_assignment(&mut state, &mut overlay_db, assignment, candidate_index)
.unwrap();
assert_eq!(res.0, AssignmentCheckResult::Bad(AssignmentCheckError::UnknownBlock(unknown_hash)));
assert_eq!(overlay_db.into_write_ops().count(), 0);
let mut state = State {
assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|| {
Err(criteria::InvalidAssignment)
})),
..some_state(
StateConfig { candidate_hash: Some(candidate_hash), ..Default::default() },
&mut db,
)
};
// same assignment, but this time rejected
let mut overlay_db = OverlayedBackend::new(&db);
let res =
check_and_import_assignment(&mut state, &mut overlay_db, assignment_good, candidate_index)
.unwrap();
assert_eq!(
res.0,
AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCert(ValidatorIndex(0)))
);
assert_eq!(overlay_db.into_write_ops().count(), 0);
}
#[test]
fn rejects_assignment_in_future() {
let mut db = make_db();
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 0;
let candidate_hash = CandidateReceipt::<Hash>::default().hash();
let assignment = IndirectAssignmentCert {
block_hash,
validator: ValidatorIndex(0),
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }),
};
let tick = 9;
let mut state = State {
assignment_criteria: Box::new(MockAssignmentCriteria::check_only(move || {
Ok((tick + 20) as _)
})),
..some_state(
StateConfig { tick, candidate_hash: Some(candidate_hash), ..Default::default() },
&mut db,
)
};
let mut overlay_db = OverlayedBackend::new(&db);
let res = check_and_import_assignment(
&mut state,
&mut overlay_db,
assignment.clone(),
candidate_index,
)
.unwrap();
assert_eq!(res.0, AssignmentCheckResult::TooFarInFuture);
let write_ops = overlay_db.into_write_ops().collect::<Vec<BackendWriteOp>>();
assert_eq!(write_ops.len(), 0);
db.write(write_ops).unwrap();
let mut state = State {
assignment_criteria: Box::new(MockAssignmentCriteria::check_only(move || {
Ok((tick + 20 - 1) as _)
})),
..some_state(
StateConfig { tick, candidate_hash: Some(candidate_hash), ..Default::default() },
&mut db,
)
};
let mut overlay_db = OverlayedBackend::new(&db);
let res = check_and_import_assignment(
&mut state,
&mut overlay_db,
assignment.clone(),
candidate_index,
)
.unwrap();
assert_eq!(res.0, AssignmentCheckResult::Accepted);
let write_ops = overlay_db.into_write_ops().collect::<Vec<BackendWriteOp>>();
assert_eq!(write_ops.len(), 1);
assert_matches!(write_ops.get(0).unwrap(), BackendWriteOp::WriteCandidateEntry(..));
}
#[test]
fn rejects_assignment_with_unknown_candidate() {
let mut db = make_db();
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 1;
let assignment = IndirectAssignmentCert {
block_hash,
validator: ValidatorIndex(0),
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }),
};
let mut state = some_state(Default::default(), &mut db);