use self::test_helpers::mock::new_leaf;
use super::*;
use crate::backend::V1ReadBackend;
use polkadot_node_primitives::{
approval::{
v1::{
AssignmentCert, AssignmentCertKind, DelayTranche, VrfPreOutput, VrfProof, VrfSignature,
RELAY_VRF_MODULO_CONTEXT,
},
v2::{AssignmentCertKindV2, AssignmentCertV2},
},
AvailableData, BlockData, PoV,
};
use polkadot_node_subsystem::{
messages::{
AllMessages, ApprovalVotingMessage, AssignmentCheckResult, AvailabilityRecoveryMessage,
},
ActiveLeavesUpdate,
};
use polkadot_node_subsystem_test_helpers as test_helpers;
use polkadot_node_subsystem_util::TimeoutExt;
use polkadot_overseer::HeadSupportsParachains;
use polkadot_primitives::{
vstaging::NodeFeatures, ApprovalVote, CandidateCommitments, CandidateEvent, CoreIndex,
GroupIndex, Header, Id as ParaId, IndexedVec, ValidationCode, ValidatorSignature,
};
use std::time::Duration;
use assert_matches::assert_matches;
use async_trait::async_trait;
use parking_lot::Mutex;
use sp_keyring::sr25519::Keyring as Sr25519Keyring;
use sp_keystore::Keystore;
use std::{
pin::Pin,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use super::{
approval_db::common::StoredBlockRange,
backend::BackendWriteOp,
import::tests::{
garbage_vrf_signature, AllowedSlots, BabeEpoch, BabeEpochConfiguration,
CompatibleDigestItem, Digest, DigestItem, PreDigest, SecondaryVRFPreDigest,
},
};
use ::test_helpers::{dummy_candidate_receipt, dummy_candidate_receipt_bad_sig};
const SLOT_DURATION_MILLIS: u64 = 5000;
const TIMEOUT: Duration = Duration::from_millis(2000);
#[derive(Clone)]
struct TestSyncOracle {
flag: Arc<AtomicBool>,
done_syncing_sender: Arc<Mutex<Option<oneshot::Sender<()>>>>,
}
struct TestSyncOracleHandle {
done_syncing_receiver: oneshot::Receiver<()>,
is_major_syncing: Arc<AtomicBool>,
}
impl TestSyncOracleHandle {
async fn await_mode_switch(self) {
let _ = self.done_syncing_receiver.await;
}
}
impl SyncOracle for TestSyncOracle {
fn is_major_syncing(&self) -> bool {
let is_major_syncing = self.flag.load(Ordering::SeqCst);
if !is_major_syncing {
if let Some(sender) = self.done_syncing_sender.lock().take() {
let _ = sender.send(());
}
}
is_major_syncing
}
fn is_offline(&self) -> bool {
unimplemented!("not used in network bridge")
}
}
fn make_sync_oracle(val: bool) -> (Box<dyn SyncOracle + Send>, TestSyncOracleHandle) {
let (tx, rx) = oneshot::channel();
let flag = Arc::new(AtomicBool::new(val));
let oracle =
TestSyncOracle { flag: flag.clone(), done_syncing_sender: Arc::new(Mutex::new(Some(tx))) };
let handle = TestSyncOracleHandle { done_syncing_receiver: rx, is_major_syncing: flag };
(Box::new(oracle), handle)
}
#[cfg(test)]
pub mod test_constants {
use crate::approval_db::common::Config as DatabaseConfig;
const DATA_COL: u32 = 0;
pub(crate) const NUM_COLUMNS: u32 = 1;
pub(crate) const TEST_CONFIG: DatabaseConfig = DatabaseConfig { col_approval_data: DATA_COL };
}
struct MockSupportsParachains;
#[async_trait]
impl HeadSupportsParachains for MockSupportsParachains {
async fn head_supports_parachains(&self, _head: &Hash) -> bool {
true
}
}
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");
})
}
}
#[derive(Default, Debug)]
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) {
let drain_up_to = self.wakeups.partition_point(|w| w.0 <= up_to);
for (_, wakeup) in self.wakeups.drain(..drain_up_to) {
let _ = wakeup.send(());
}
}
fn next_wakeup(&self) -> Option<Tick> {
self.wakeups.iter().map(|w| w.0).next()
}
fn current_wakeup_is(&mut self, tick: Tick) -> bool {
self.wakeups.retain(|(_, tx)| !tx.is_canceled());
self.wakeups.binary_search_by_key(&tick, |w| w.0).is_ok()
}
fn register_wakeup(&mut self, tick: Tick, pre_emptive: bool) -> oneshot::Receiver<()> {
let (tx, rx) = oneshot::channel();
let pos = self.wakeups.partition_point(|w| w.0 <= tick);
self.wakeups.insert(pos, (tick, tx));
if pre_emptive {
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::CoreIndex, criteria::OurAssignment>,
Check: Fn(ValidatorIndex) -> Result<DelayTranche, criteria::InvalidAssignment>,
{
fn compute_assignments(
&self,
_keystore: &LocalKeystore,
_relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory,
_config: &criteria::Config,
_leaving_cores: Vec<(
CandidateHash,
polkadot_primitives::CoreIndex,
polkadot_primitives::GroupIndex,
)>,
_enable_assignments_v2: bool,
) -> HashMap<polkadot_primitives::CoreIndex, criteria::OurAssignment> {
self.0()
}
fn check_assignment_cert(
&self,
_claimed_core_bitfield: polkadot_node_primitives::approval::v2::CoreBitfield,
validator_index: ValidatorIndex,
_config: &criteria::Config,
_relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory,
_assignment: &polkadot_node_primitives::approval::v2::AssignmentCertV2,
_backing_groups: Vec<polkadot_primitives::GroupIndex>,
) -> Result<polkadot_node_primitives::approval::v1::DelayTranche, criteria::InvalidAssignment> {
self.1(validator_index)
}
}
impl<F>
MockAssignmentCriteria<fn() -> HashMap<polkadot_primitives::CoreIndex, criteria::OurAssignment>, F>
{
fn check_only(f: F) -> Self {
MockAssignmentCriteria(Default::default, f)
}
}
#[derive(Default, Clone)]
struct TestStoreInner {
stored_block_range: Option<StoredBlockRange>,
blocks_at_height: HashMap<BlockNumber, Vec<Hash>>,
block_entries: HashMap<Hash, BlockEntry>,
candidate_entries: HashMap<CandidateHash, CandidateEntry>,
}
impl V1ReadBackend for TestStoreInner {
fn load_candidate_entry_v1(
&self,
candidate_hash: &CandidateHash,
_candidate_index: CandidateIndex,
) -> SubsystemResult<Option<CandidateEntry>> {
self.load_candidate_entry(candidate_hash)
}
fn load_block_entry_v1(&self, block_hash: &Hash) -> SubsystemResult<Option<BlockEntry>> {
self.load_block_entry(block_hash)
}
}
impl Backend for TestStoreInner {
fn load_block_entry(&self, block_hash: &Hash) -> SubsystemResult<Option<BlockEntry>> {
Ok(self.block_entries.get(block_hash).cloned())
}
fn load_candidate_entry(
&self,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<CandidateEntry>> {
Ok(self.candidate_entries.get(candidate_hash).cloned())
}
fn load_blocks_at_height(&self, height: &BlockNumber) -> SubsystemResult<Vec<Hash>> {
Ok(self.blocks_at_height.get(height).cloned().unwrap_or_default())
}
fn load_all_blocks(&self) -> SubsystemResult<Vec<Hash>> {
let mut hashes: Vec<_> = self.block_entries.keys().cloned().collect();
hashes.sort_by_key(|k| self.block_entries.get(k).unwrap().block_number());
Ok(hashes)
}
fn load_stored_blocks(&self) -> SubsystemResult<Option<StoredBlockRange>> {
Ok(self.stored_block_range.clone())
}
fn write<I>(&mut self, ops: I) -> SubsystemResult<()>
where
I: IntoIterator<Item = BackendWriteOp>,
{
for op in ops {
match op {
BackendWriteOp::WriteStoredBlockRange(stored_block_range) => {
self.stored_block_range = Some(stored_block_range);
},
BackendWriteOp::DeleteStoredBlockRange => {
self.stored_block_range = None;
},
BackendWriteOp::WriteBlocksAtHeight(h, blocks) => {
self.blocks_at_height.insert(h, blocks);
},
BackendWriteOp::DeleteBlocksAtHeight(h) => {
let _ = self.blocks_at_height.remove(&h);
},
BackendWriteOp::WriteBlockEntry(block_entry) => {
self.block_entries.insert(block_entry.block_hash(), block_entry);
},
BackendWriteOp::DeleteBlockEntry(hash) => {
let _ = self.block_entries.remove(&hash);
},
BackendWriteOp::WriteCandidateEntry(candidate_entry) => {
self.candidate_entries
.insert(candidate_entry.candidate_receipt().hash(), candidate_entry);
},
BackendWriteOp::DeleteCandidateEntry(candidate_hash) => {
let _ = self.candidate_entries.remove(&candidate_hash);
},
}
}
Ok(())
}
}
#[derive(Default, Clone)]
pub struct TestStore {
store: Arc<Mutex<TestStoreInner>>,
}
impl V1ReadBackend for TestStore {
fn load_candidate_entry_v1(
&self,
candidate_hash: &CandidateHash,
_candidate_index: CandidateIndex,
) -> SubsystemResult<Option<CandidateEntry>> {
self.load_candidate_entry(candidate_hash)
}
fn load_block_entry_v1(&self, block_hash: &Hash) -> SubsystemResult<Option<BlockEntry>> {
self.load_block_entry(block_hash)
}
}
impl Backend for TestStore {
fn load_block_entry(&self, block_hash: &Hash) -> SubsystemResult<Option<BlockEntry>> {
let store = self.store.lock();
store.load_block_entry(block_hash)
}
fn load_candidate_entry(
&self,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<CandidateEntry>> {
let store = self.store.lock();
store.load_candidate_entry(candidate_hash)
}
fn load_blocks_at_height(&self, height: &BlockNumber) -> SubsystemResult<Vec<Hash>> {
let store = self.store.lock();
store.load_blocks_at_height(height)
}
fn load_all_blocks(&self) -> SubsystemResult<Vec<Hash>> {
let store = self.store.lock();
store.load_all_blocks()
}
fn load_stored_blocks(&self) -> SubsystemResult<Option<StoredBlockRange>> {
let store = self.store.lock();
store.load_stored_blocks()
}
fn write<I>(&mut self, ops: I) -> SubsystemResult<()>
where
I: IntoIterator<Item = BackendWriteOp>,
{
let mut store = self.store.lock();
store.write(ops)
}
}
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 preout = inout.to_preout();
AssignmentCert {
kind,
vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) },
}
}
fn garbage_assignment_cert_v2(kind: AssignmentCertKindV2) -> AssignmentCertV2 {
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 preout = inout.to_preout();
AssignmentCertV2 {
kind,
vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) },
}
}
fn sign_approval(
key: Sr25519Keyring,
candidate_hash: CandidateHash,
session_index: SessionIndex,
) -> ValidatorSignature {
key.sign(&ApprovalVote(candidate_hash).signing_payload(session_index)).into()
}
fn sign_approval_multiple_candidates(
key: Sr25519Keyring,
candidate_hashes: Vec<CandidateHash>,
session_index: SessionIndex,
) -> ValidatorSignature {
key.sign(&ApprovalVoteMultipleCandidates(&candidate_hashes).signing_payload(session_index))
.into()
}
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<ApprovalVotingMessage>;
#[derive(Default)]
struct HarnessConfigBuilder {
sync_oracle: Option<(Box<dyn SyncOracle + Send>, TestSyncOracleHandle)>,
clock: Option<MockClock>,
backend: Option<TestStore>,
assignment_criteria: Option<Box<dyn AssignmentCriteria + Send + Sync + 'static>>,
major_syncing: bool,
}
impl HarnessConfigBuilder {
pub fn assignment_criteria(
&mut self,
assignment_criteria: Box<dyn AssignmentCriteria + Send + Sync + 'static>,
) -> &mut Self {
self.assignment_criteria = Some(assignment_criteria);
self
}
pub fn major_syncing(&mut self, value: bool) -> &mut Self {
self.major_syncing = value;
self
}
pub fn backend(&mut self, store: TestStore) -> &mut Self {
self.backend = Some(store);
self
}
pub fn build(&mut self) -> HarnessConfig {
let (sync_oracle, sync_oracle_handle) =
self.sync_oracle.take().unwrap_or_else(|| make_sync_oracle(self.major_syncing));
let assignment_criteria = self
.assignment_criteria
.take()
.unwrap_or_else(|| Box::new(MockAssignmentCriteria::check_only(|_| Ok(0))));
HarnessConfig {
sync_oracle,
sync_oracle_handle,
clock: self.clock.take().unwrap_or_else(|| MockClock::new(0)),
backend: self.backend.take().unwrap_or_else(|| TestStore::default()),
assignment_criteria,
}
}
}
struct HarnessConfig {
sync_oracle: Box<dyn SyncOracle + Send>,
sync_oracle_handle: TestSyncOracleHandle,
clock: MockClock,
backend: TestStore,
assignment_criteria: Box<dyn AssignmentCriteria + Send + Sync + 'static>,
}
impl HarnessConfig {
pub fn backend(&self) -> TestStore {
self.backend.clone()
}
}
impl Default for HarnessConfig {
fn default() -> Self {
HarnessConfigBuilder::default().build()
}
}
struct TestHarness {
virtual_overseer: VirtualOverseer,
clock: Box<MockClock>,
sync_oracle_handle: TestSyncOracleHandle,
}
fn test_harness<T: Future<Output = VirtualOverseer>>(
config: HarnessConfig,
test: impl FnOnce(TestHarness) -> T,
) {
let _ = env_logger::builder()
.is_test(true)
.filter(Some("polkadot_node_core_approval_voting"), log::LevelFilter::Trace)
.filter(Some(LOG_TARGET), log::LevelFilter::Trace)
.try_init();
let HarnessConfig { sync_oracle, sync_oracle_handle, clock, backend, assignment_criteria } =
config;
let pool = sp_core::testing::TaskExecutor::new();
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool);
let keystore = LocalKeystore::in_memory();
let _ = keystore.sr25519_generate_new(
polkadot_primitives::PARACHAIN_KEY_TYPE_ID,
Some(&Sr25519Keyring::Alice.to_seed()),
);
let clock = Box::new(clock);
let db = kvdb_memorydb::create(test_constants::NUM_COLUMNS);
let db = polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]);
let subsystem = run(
context,
ApprovalVotingSubsystem::with_config_and_clock(
Config {
col_approval_data: test_constants::TEST_CONFIG.col_approval_data,
slot_duration_millis: SLOT_DURATION_MILLIS,
},
Arc::new(db),
Arc::new(keystore),
sync_oracle,
Metrics::default(),
clock.clone(),
),
assignment_criteria,
backend,
);
let test_fut = test(TestHarness { virtual_overseer, clock, sync_oracle_handle });
futures::pin_mut!(test_fut);
futures::pin_mut!(subsystem);
futures::executor::block_on(future::join(
async move {
let mut overseer = test_fut.await;
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
},
subsystem,
))
.1
.unwrap();
}
async fn overseer_send(overseer: &mut VirtualOverseer, msg: FromOrchestra<ApprovalVotingMessage>) {
gum::trace!("Sending message:\n{:?}", &msg);
overseer
.send(msg)
.timeout(TIMEOUT)
.await
.expect(&format!("{:?} is enough for sending messages.", TIMEOUT));
}
async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages {
let msg = overseer_recv_with_timeout(overseer, TIMEOUT)
.await
.expect(&format!("{:?} is enough to receive messages.", TIMEOUT));
gum::trace!("Received message:\n{:?}", &msg);
msg
}
async fn overseer_recv_with_timeout(
overseer: &mut VirtualOverseer,
timeout: Duration,
) -> Option<AllMessages> {
gum::trace!("Waiting for message...");
overseer.recv().timeout(timeout).await
}
async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) {
overseer
.send(FromOrchestra::Signal(signal))
.timeout(TIMEOUT)
.await
.expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT));
}
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 make_candidate(para_id: ParaId, hash: &Hash) -> CandidateReceipt {
let mut r = dummy_candidate_receipt_bad_sig(*hash, Some(Default::default()));
r.descriptor.para_id = para_id;
r
}
async fn check_and_import_approval(
overseer: &mut VirtualOverseer,
block_hash: Hash,
candidate_index: CandidateIndex,
validator: ValidatorIndex,
candidate_hash: CandidateHash,
session_index: SessionIndex,
expect_chain_approved: bool,
signature_opt: Option<ValidatorSignature>,
) -> oneshot::Receiver<ApprovalCheckResult> {
let signature = signature_opt.unwrap_or(sign_approval(
Sr25519Keyring::Alice,
candidate_hash,
session_index,
));
let (tx, rx) = oneshot::channel();
overseer_send(
overseer,
FromOrchestra::Communication {
msg: ApprovalVotingMessage::CheckAndImportApproval(
IndirectSignedApprovalVoteV2 {
block_hash,
candidate_indices: candidate_index.into(),
validator,
signature,
},
tx,
),
},
)
.await;
if expect_chain_approved {
assert_matches!(
overseer_recv(overseer).await,
AllMessages::ChainSelection(ChainSelectionMessage::Approved(b_hash)) => {
assert_eq!(b_hash, block_hash);
}
);
}
rx
}
async fn check_and_import_assignment(
overseer: &mut VirtualOverseer,
block_hash: Hash,
candidate_index: CandidateIndex,
validator: ValidatorIndex,
) -> oneshot::Receiver<AssignmentCheckResult> {
let (tx, rx) = oneshot::channel();
overseer_send(
overseer,
FromOrchestra::Communication {
msg: ApprovalVotingMessage::CheckAndImportAssignment(
IndirectAssignmentCertV2 {
block_hash,
validator,
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 })
.into(),
},
candidate_index.into(),
tx,
),
},
)
.await;
rx
}
async fn check_and_import_assignment_v2(
overseer: &mut VirtualOverseer,
block_hash: Hash,
core_indices: Vec<u32>,
validator: ValidatorIndex,
) -> oneshot::Receiver<AssignmentCheckResult> {
let (tx, rx) = oneshot::channel();
overseer_send(
overseer,
FromOrchestra::Communication {
msg: ApprovalVotingMessage::CheckAndImportAssignment(
IndirectAssignmentCertV2 {
block_hash,
validator,
cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact {
core_bitfield: core_indices
.clone()
.into_iter()
.map(|c| CoreIndex(c))
.collect::<Vec<_>>()
.try_into()
.unwrap(),
}),
},
core_indices.try_into().unwrap(),
tx,
),
},
)
.await;
rx
}
struct BlockConfig {
slot: Slot,
candidates: Option<Vec<(CandidateReceipt, CoreIndex, GroupIndex)>>,
session_info: Option<SessionInfo>,
end_syncing: bool,
}
struct ChainBuilder {
blocks_by_hash: HashMap<Hash, (Header, BlockConfig)>,
blocks_at_height: BTreeMap<u32, Vec<Hash>>,
is_major_syncing: Arc<AtomicBool>,
}
impl ChainBuilder {
const GENESIS_HASH: Hash = Hash::repeat_byte(0xff);
const GENESIS_PARENT_HASH: Hash = Hash::repeat_byte(0x00);
pub fn new() -> Self {
let mut builder = Self {
blocks_by_hash: HashMap::new(),
blocks_at_height: BTreeMap::new(),
is_major_syncing: Arc::new(AtomicBool::new(false)),
};
builder.add_block_inner(
Self::GENESIS_HASH,
Self::GENESIS_PARENT_HASH,
0,
BlockConfig {
slot: Slot::from(0),
candidates: None,
session_info: None,
end_syncing: false,
},
);
builder
}
pub fn major_syncing(&mut self, major_syncing: Arc<AtomicBool>) -> &mut Self {
self.is_major_syncing = major_syncing;
self
}
pub fn add_block(
&mut self,
hash: Hash,
parent_hash: Hash,
number: u32,
config: BlockConfig,
) -> &mut Self {
assert!(number != 0, "cannot add duplicate genesis block");
assert!(hash != Self::GENESIS_HASH, "cannot add block with genesis hash");
assert!(
parent_hash != Self::GENESIS_PARENT_HASH,
"cannot add block with genesis parent hash"
);
assert!(self.blocks_by_hash.len() < u8::MAX.into());
self.add_block_inner(hash, parent_hash, number, config)
}
fn add_block_inner(
&mut self,
hash: Hash,
parent_hash: Hash,
number: u32,
config: BlockConfig,
) -> &mut Self {
let header = ChainBuilder::make_header(parent_hash, config.slot, number);
assert!(
self.blocks_by_hash.insert(hash, (header, config)).is_none(),
"block with hash {:?} already exists",
hash,
);
self.blocks_at_height.entry(number).or_insert_with(Vec::new).push(hash);
self
}
pub async fn build(&self, overseer: &mut VirtualOverseer) {
for (number, blocks) in self.blocks_at_height.iter() {
for (i, hash) in blocks.iter().enumerate() {
let mut cur_hash = *hash;
let (_, block_config) =
self.blocks_by_hash.get(&cur_hash).expect("block not found");
let mut ancestry = Vec::new();
while cur_hash != Self::GENESIS_PARENT_HASH {
let (cur_header, _) =
self.blocks_by_hash.get(&cur_hash).expect("chain is not contiguous");
ancestry.push((cur_hash, cur_header.clone()));
cur_hash = cur_header.parent_hash;
}
ancestry.reverse();
import_block(
overseer,
ancestry.as_ref(),
*number,
block_config,
false,
i > 0,
self.is_major_syncing.clone(),
)
.await;
let _: Option<()> = future::pending().timeout(Duration::from_millis(100)).await;
}
}
}
fn make_header(parent_hash: Hash, slot: Slot, number: u32) -> Header {
let digest = {
let mut digest = Digest::default();
let vrf_signature = garbage_vrf_signature();
digest.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
SecondaryVRFPreDigest { authority_index: 0, slot, vrf_signature },
)));
digest
};
Header {
digest,
extrinsics_root: Default::default(),
number,
state_root: Default::default(),
parent_hash,
}
}
}
fn session_info(keys: &[Sr25519Keyring]) -> SessionInfo {
SessionInfo {
validators: keys.iter().map(|v| v.public().into()).collect(),
discovery_keys: keys.iter().map(|v| v.public().into()).collect(),
assignment_keys: keys.iter().map(|v| v.public().into()).collect(),
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0)],
vec![ValidatorIndex(1)],
]),
n_cores: 10,
needed_approvals: 2,
zeroth_delay_tranche_width: 5,
relay_vrf_modulo_samples: 3,
n_delay_tranches: 50,
no_show_slots: 2,
active_validator_indices: vec![],
dispute_period: 6,
random_seed: [0u8; 32],
}
}
async fn import_block(
overseer: &mut VirtualOverseer,
hashes: &[(Hash, Header)],
number: u32,
config: &BlockConfig,
gap: bool,
fork: bool,
major_syncing: Arc<AtomicBool>,
) {
let (new_head, new_header) = &hashes[hashes.len() - 1];
let candidates = config.candidates.clone().unwrap_or(vec![(
make_candidate(ParaId::from(0_u32), &new_head),
CoreIndex(0),
GroupIndex(0),
)]);
let session_info = config.session_info.clone().unwrap_or({
let validators = vec![Sr25519Keyring::Alice, Sr25519Keyring::Bob];
SessionInfo { needed_approvals: 1, ..session_info(&validators) }
});
overseer_send(
overseer,
FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(
new_leaf(*new_head, number),
))),
)
.await;
assert_matches!(
overseer_recv(overseer).await,
AllMessages::ChainApi(ChainApiMessage::BlockHeader(head, h_tx)) => {
assert_eq!(*new_head, head);
h_tx.send(Ok(Some(new_header.clone()))).unwrap();
}
);
let is_major_syncing = major_syncing.load(Ordering::SeqCst);
if config.end_syncing {
major_syncing.store(false, Ordering::SeqCst);
}
if !fork {
let mut _ancestry_step = 0;
if gap {
assert_matches!(
overseer_recv(overseer).await,
AllMessages::ChainApi(ChainApiMessage::Ancestors {
hash,
k,
response_channel,
}) => {
assert_eq!(hash, *new_head);
let history: Vec<Hash> = hashes.iter().map(|v| v.0).take(k).collect();
let _ = response_channel.send(Ok(history));
_ancestry_step = k;
}
);
for i in 0.._ancestry_step {
match overseer_recv(overseer).await {
AllMessages::ChainApi(ChainApiMessage::BlockHeader(_, h_tx)) => {
let (hash, header) = hashes[i as usize].clone();
assert_eq!(hash, *new_head);
h_tx.send(Ok(Some(header))).unwrap();
},
AllMessages::ChainApi(ChainApiMessage::Ancestors {
hash,
k,
response_channel,
}) => {
assert_eq!(hash, *new_head);
assert_eq!(k as u32, number - 1);
let history: Vec<Hash> = hashes.iter().map(|v| v.0).take(k).collect();
response_channel.send(Ok(history)).unwrap();
},
_ => unreachable! {},
}
}
}
}
if number > 0 && !is_major_syncing {
assert_matches!(
overseer_recv(overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(hash, RuntimeApiRequest::CandidateEvents(c_tx))
) => {
assert_eq!(hash, *new_head);
let inclusion_events = candidates.into_iter()
.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
.collect::<Vec<_>>();
c_tx.send(Ok(inclusion_events)).unwrap();
}
);
assert_matches!(
overseer_recv(overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(
req_block_hash,
RuntimeApiRequest::SessionIndexForChild(s_tx)
)
) => {
let hash = &hashes[(number-1) as usize];
assert_eq!(req_block_hash, hash.0.clone());
s_tx.send(Ok(number.into())).unwrap();
}
);
assert_matches!(
overseer_recv(overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(
req_block_hash,
RuntimeApiRequest::CurrentBabeEpoch(c_tx),
)
) => {
let hash = &hashes[number as usize];
assert_eq!(req_block_hash, hash.0.clone());
let _ = c_tx.send(Ok(BabeEpoch {
epoch_index: number as _,
start_slot: Slot::from(0),
duration: 200,
authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)],
randomness: [0u8; 32],
config: BabeEpochConfiguration {
c: (1, 4),
allowed_slots: AllowedSlots::PrimarySlots,
},
}));
}
);
}
if number == 0 && !is_major_syncing {
assert_matches!(
overseer_recv(overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks(v)) => {
assert_eq!(v.len(), 0usize);
}
);
} else if number > 0 && !is_major_syncing {
if !fork {
assert_matches!(
overseer_recv(overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(
req_block_hash,
RuntimeApiRequest::SessionInfo(_, si_tx),
)
) => {
assert_ne!(req_block_hash, hashes[(number-1) as usize].0);
si_tx.send(Ok(Some(session_info.clone()))).unwrap();
}
);
assert_matches!(
overseer_recv(overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(
req_block_hash,
RuntimeApiRequest::SessionExecutorParams(_, si_tx),
)
) => {
assert_ne!(req_block_hash, hashes[(number-1) as usize].0);
si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap();
}
);
assert_matches!(
overseer_recv(overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), )
) => {
si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap();
}
);
}
if !is_major_syncing {
assert_matches!(
overseer_recv(overseer).await,
AllMessages::ApprovalDistribution(
ApprovalDistributionMessage::NewBlocks(mut approval_vec)
) => {
assert_eq!(approval_vec.len(), 1);
let metadata = approval_vec.pop().unwrap();
let hash = &hashes[number as usize];
let parent_hash = &hashes[(number - 1) as usize];
assert_eq!(metadata.hash, hash.0.clone());
assert_eq!(metadata.parent_hash, parent_hash.0.clone());
assert_eq!(metadata.slot, config.slot);
}
);
}
}
}
#[test]
fn subsystem_rejects_bad_assignment_ok_criteria() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 0;
let validator = ValidatorIndex(0);
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let slot = Slot::from(1 as u64);
builder.add_block(
block_hash,
head,
1,
BlockConfig { slot, candidates: None, session_info: None, end_syncing: false },
);
builder.build(&mut virtual_overseer).await;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted),);
let unknown_hash = Hash::repeat_byte(0x02);
let rx = check_and_import_assignment(
&mut virtual_overseer,
unknown_hash,
candidate_index,
validator,
)
.await;
assert_eq!(
rx.await,
Ok(AssignmentCheckResult::Bad(AssignmentCheckError::UnknownBlock(unknown_hash))),
);
virtual_overseer
});
}
#[test]
fn subsystem_rejects_bad_assignment_err_criteria() {
let assignment_criteria = Box::new(MockAssignmentCriteria::check_only(move |_| {
Err(criteria::InvalidAssignment(
criteria::InvalidAssignmentReason::ValidatorIndexOutOfBounds,
))
}));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 0;
let validator = ValidatorIndex(0);
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let slot = Slot::from(1 as u64);
builder.add_block(
block_hash,
head,
1,
BlockConfig { slot, candidates: None, session_info: None, end_syncing: false },
);
builder.build(&mut virtual_overseer).await;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(
rx.await,
Ok(AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCert(
ValidatorIndex(0),
"ValidatorIndexOutOfBounds".to_string(),
))),
);
virtual_overseer
});
}
#[test]
fn blank_subsystem_act_on_bad_block() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle, .. } = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let (tx, rx) = oneshot::channel();
let bad_block_hash: Hash = Default::default();
overseer_send(
&mut virtual_overseer,
FromOrchestra::Communication {
msg: ApprovalVotingMessage::CheckAndImportAssignment(
IndirectAssignmentCertV2 {
block_hash: bad_block_hash,
validator: 0u32.into(),
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo {
sample: 0,
})
.into(),
},
0u32.into(),
tx,
),
},
)
.await;
sync_oracle_handle.await_mode_switch().await;
assert_matches!(
rx.await,
Ok(
AssignmentCheckResult::Bad(AssignmentCheckError::UnknownBlock(hash))
) => {
assert_eq!(hash, bad_block_hash);
}
);
virtual_overseer
});
}
#[test]
fn subsystem_rejects_approval_if_no_candidate_entry() {
let config = HarnessConfig::default();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 0;
let validator = ValidatorIndex(0);
let candidate_descriptor = make_candidate(ParaId::from(1_u32), &block_hash);
let candidate_hash = candidate_descriptor.hash();
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let slot = Slot::from(1 as u64);
builder.add_block(
block_hash,
head,
1,
BlockConfig {
slot,
candidates: Some(vec![(candidate_descriptor, CoreIndex(1), GroupIndex(1))]),
session_info: None,
end_syncing: false,
},
);
builder.build(&mut virtual_overseer).await;
overlay_txn(&mut store.clone(), |overlay_db| {
overlay_db.delete_candidate_entry(&candidate_hash)
});
let session_index = 1;
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
candidate_hash,
session_index,
false,
None,
)
.await;
assert_matches!(
rx.await,
Ok(ApprovalCheckResult::Bad(ApprovalCheckError::InvalidCandidate(0, hash))) => {
assert_eq!(candidate_hash, hash);
}
);
virtual_overseer
});
}
#[test]
fn subsystem_rejects_approval_if_no_block_entry() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 0;
let validator = ValidatorIndex(0);
let candidate_hash = dummy_candidate_receipt(block_hash).hash();
let session_index = 1;
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
candidate_hash,
session_index,
false,
None,
)
.await;
assert_matches!(
rx.await,
Ok(ApprovalCheckResult::Bad(ApprovalCheckError::UnknownBlock(hash))) => {
assert_eq!(hash, block_hash);
}
);
virtual_overseer
});
}
#[test]
fn subsystem_rejects_approval_before_assignment() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_hash = {
let mut candidate_receipt =
dummy_candidate_receipt_bad_sig(block_hash, Some(Default::default()));
candidate_receipt.descriptor.para_id = ParaId::from(0_u32);
candidate_receipt.descriptor.relay_parent = block_hash;
candidate_receipt.hash()
};
let candidate_index = 0;
let validator = ValidatorIndex(0);
let session_index = 1;
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(1),
candidates: None,
session_info: None,
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
candidate_hash,
session_index,
false,
None,
)
.await;
assert_matches!(
rx.await,
Ok(ApprovalCheckResult::Bad(ApprovalCheckError::NoAssignment(v))) => {
assert_eq!(v, validator);
}
);
virtual_overseer
});
}
#[test]
fn subsystem_rejects_assignment_in_future() {
let assignment_criteria =
Box::new(MockAssignmentCriteria::check_only(|_| Ok(TICK_TOO_FAR_IN_FUTURE as _)));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle: _sync_oracle_handle } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 0;
let validator = ValidatorIndex(0);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(0),
candidates: None,
session_info: None,
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::TooFarInFuture));
clock.inner.lock().set_tick(9);
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
virtual_overseer
});
}
#[test]
fn subsystem_accepts_duplicate_assignment() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let validator = ValidatorIndex(0);
let candidate_index = 0;
let block_hash = Hash::repeat_byte(0x01);
let candidate_receipt1 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(1_u32);
receipt
};
let candidate_receipt2 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(2_u32);
receipt
};
let candidate_index1 = 0;
let candidate_index2 = 1;
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(1),
candidates: Some(vec![
(candidate_receipt1, CoreIndex(0), GroupIndex(1)),
(candidate_receipt2, CoreIndex(1), GroupIndex(1)),
]),
session_info: None,
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let rx = check_and_import_assignment_v2(
&mut virtual_overseer,
block_hash,
vec![candidate_index1, candidate_index2],
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::AcceptedDuplicate));
let rx = check_and_import_assignment_v2(
&mut virtual_overseer,
block_hash,
vec![candidate_index1, candidate_index2],
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::AcceptedDuplicate));
virtual_overseer
});
}
#[test]
fn subsystem_rejects_assignment_with_unknown_candidate() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 7;
let validator = ValidatorIndex(0);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(1),
candidates: None,
session_info: None,
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(
rx.await,
Ok(AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidateIndex(
candidate_index
))),
);
virtual_overseer
});
}
#[test]
fn subsystem_rejects_oversized_bitfields() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 10;
let validator = ValidatorIndex(0);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(1),
candidates: None,
session_info: None,
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(
rx.await,
Ok(AssignmentCheckResult::Bad(AssignmentCheckError::InvalidBitfield(
candidate_index as usize + 1
))),
);
let rx = check_and_import_assignment_v2(
&mut virtual_overseer,
block_hash,
vec![1, 2, 10, 50],
validator,
)
.await;
assert_eq!(
rx.await,
Ok(AssignmentCheckResult::Bad(AssignmentCheckError::InvalidBitfield(51))),
);
virtual_overseer
});
}
#[test]
fn subsystem_accepts_and_imports_approval_after_assignment() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_hash = {
let mut candidate_receipt =
dummy_candidate_receipt_bad_sig(block_hash, Some(Default::default()));
candidate_receipt.descriptor.para_id = ParaId::from(0_u32);
candidate_receipt.descriptor.relay_parent = block_hash;
candidate_receipt.hash()
};
let candidate_index = 0;
let validator = ValidatorIndex(0);
let session_index = 1;
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(1),
candidates: None,
session_info: None,
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
candidate_hash,
session_index,
true,
None,
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted));
virtual_overseer
});
}
#[test]
fn subsystem_second_approval_import_only_schedules_wakeups() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness {
mut virtual_overseer,
clock,
sync_oracle_handle: _sync_oracle_handle,
..
} = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
clock.inner.lock().set_tick(APPROVAL_DELAY);
let block_hash = Hash::repeat_byte(0x01);
let candidate_hash = {
let mut candidate_receipt =
dummy_candidate_receipt_bad_sig(block_hash, Some(Default::default()));
candidate_receipt.descriptor.para_id = ParaId::from(0_u32);
candidate_receipt.descriptor.relay_parent = block_hash;
candidate_receipt.hash()
};
let candidate_index = 0;
let validator = ValidatorIndex(0);
let session_index = 1;
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
needed_approvals: 1,
..session_info(&validators)
};
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(0),
candidates: None,
session_info: Some(session_info),
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
assert!(clock.inner.lock().current_wakeup_is(APPROVAL_DELAY + 2));
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
candidate_hash,
session_index,
false,
None,
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted));
futures_timer::Delay::new(Duration::from_millis(100)).await;
assert!(clock.inner.lock().current_wakeup_is(APPROVAL_DELAY + 2));
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
candidate_hash,
session_index,
false,
None,
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted));
futures_timer::Delay::new(Duration::from_millis(100)).await;
assert!(clock.inner.lock().current_wakeup_is(APPROVAL_DELAY + 2));
virtual_overseer
});
}
#[test]
fn subsystem_assignment_import_updates_candidate_entry_and_schedules_wakeup() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness {
mut virtual_overseer,
clock,
sync_oracle_handle: _sync_oracle_handle,
..
} = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 0;
let validator = ValidatorIndex(0);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(1),
candidates: None,
session_info: None,
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
assert!(clock.inner.lock().current_wakeup_is(2));
virtual_overseer
});
}
#[test]
fn subsystem_process_wakeup_schedules_wakeup() {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness {
mut virtual_overseer,
clock,
sync_oracle_handle: _sync_oracle_handle,
..
} = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_index = 0;
let validator = ValidatorIndex(0);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(1),
candidates: None,
session_info: None,
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
assert!(clock.inner.lock().current_wakeup_is(2));
clock.inner.lock().set_tick(2);
futures_timer::Delay::new(Duration::from_millis(100)).await;
assert!(clock.inner.lock().current_wakeup_is(30));
virtual_overseer
});
}
#[test]
fn linear_import_act_on_leaf() {
let session = 3u32;
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, .. } = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let mut head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
for i in 1..session {
let slot = Slot::from(i as u64);
let hash = Hash::repeat_byte(i as u8);
builder.add_block(
hash,
head,
i,
BlockConfig { slot, candidates: None, session_info: None, end_syncing: false },
);
head = hash;
}
builder.build(&mut virtual_overseer).await;
let (tx, rx) = oneshot::channel();
overseer_send(
&mut virtual_overseer,
FromOrchestra::Communication {
msg: ApprovalVotingMessage::CheckAndImportAssignment(
IndirectAssignmentCertV2 {
block_hash: head,
validator: 0u32.into(),
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo {
sample: 0,
})
.into(),
},
0u32.into(),
tx,
),
},
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
virtual_overseer
});
}
#[test]
fn forkful_import_at_same_height_act_on_leaf() {
let session = 3u32;
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let mut head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
for i in 1..session {
let slot = Slot::from(i as u64);
let hash = Hash::repeat_byte(i as u8);
builder.add_block(
hash,
head,
i,
BlockConfig { slot, candidates: None, session_info: None, end_syncing: false },
);
head = hash;
}
let num_forks = 3;
let forks = Vec::new();
for i in 0..num_forks {
let slot = Slot::from(session as u64);
let hash = Hash::repeat_byte(session as u8 + i);
builder.add_block(
hash,
head,
session,
BlockConfig { slot, candidates: None, session_info: None, end_syncing: false },
);
}
builder.build(&mut virtual_overseer).await;
for head in forks.into_iter() {
let (tx, rx) = oneshot::channel();
overseer_send(
&mut virtual_overseer,
FromOrchestra::Communication {
msg: ApprovalVotingMessage::CheckAndImportAssignment(
IndirectAssignmentCertV2 {
block_hash: head,
validator: 0u32.into(),
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo {
sample: 0,
})
.into(),
},
0u32.into(),
tx,
),
},
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
}
virtual_overseer
});
}
#[test]
fn test_signing_a_single_candidate_is_backwards_compatible() {
let session_index = 1;
let block_hash = Hash::repeat_byte(0x01);
let candidate_descriptors = (1..10)
.into_iter()
.map(|val| make_candidate(ParaId::from(val as u32), &block_hash))
.collect::<Vec<CandidateReceipt>>();
let candidate_hashes = candidate_descriptors
.iter()
.map(|candidate_descriptor| candidate_descriptor.hash())
.collect_vec();
let first_descriptor = candidate_descriptors.first().unwrap();
let candidate_hash = first_descriptor.hash();
let sig_a = sign_approval(Sr25519Keyring::Alice, candidate_hash, session_index);
let sig_b = sign_approval(Sr25519Keyring::Alice, candidate_hash, session_index);
assert!(DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking)
.check_signature(
&Sr25519Keyring::Alice.public().into(),
candidate_hash,
session_index,
&sig_a,
)
.is_ok());
assert!(DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking)
.check_signature(
&Sr25519Keyring::Alice.public().into(),
candidate_hash,
session_index,
&sig_b,
)
.is_ok());
let sig_c = sign_approval_multiple_candidates(
Sr25519Keyring::Alice,
vec![candidate_hash],
session_index,
);
assert!(DisputeStatement::Valid(
ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(vec![candidate_hash])
)
.check_signature(&Sr25519Keyring::Alice.public().into(), candidate_hash, session_index, &sig_c,)
.is_ok());
assert!(DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking)
.check_signature(
&Sr25519Keyring::Alice.public().into(),
candidate_hash,
session_index,
&sig_c,
)
.is_ok());
assert!(DisputeStatement::Valid(
ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(vec![candidate_hash])
)
.check_signature(&Sr25519Keyring::Alice.public().into(), candidate_hash, session_index, &sig_a,)
.is_ok());
let sig_all = sign_approval_multiple_candidates(
Sr25519Keyring::Alice,
candidate_hashes.clone(),
session_index,
);
assert!(DisputeStatement::Valid(
ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes.clone())
)
.check_signature(
&Sr25519Keyring::Alice.public().into(),
*candidate_hashes.first().expect("test"),
session_index,
&sig_all,
)
.is_ok());
}
#[test]
fn import_checked_approval_updates_entries_and_schedules() {
let config = HarnessConfig::default();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness {
mut virtual_overseer,
clock,
sync_oracle_handle: _sync_oracle_handle,
..
} = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let validator_index_a = ValidatorIndex(0);
let validator_index_b = ValidatorIndex(1);
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
let candidate_descriptor = make_candidate(ParaId::from(1_u32), &block_hash);
let candidate_hash = candidate_descriptor.hash();
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let slot = Slot::from(1 as u64);
builder.add_block(
block_hash,
head,
1,
BlockConfig {
slot,
candidates: Some(vec![(candidate_descriptor, CoreIndex(0), GroupIndex(0))]),
session_info: Some(session_info),
end_syncing: false,
},
);
builder.build(&mut virtual_overseer).await;
let candidate_index = 0;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_a,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted),);
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_b,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted),);
let session_index = 1;
let sig_a = sign_approval(Sr25519Keyring::Alice, candidate_hash, session_index);
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_a,
candidate_hash,
session_index,
false,
Some(sig_a),
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted),);
futures_timer::Delay::new(Duration::from_millis(100)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap();
assert!(!candidate_entry.approval_entry(&block_hash).unwrap().is_approved());
assert!(clock.inner.lock().current_wakeup_is(2));
clock.inner.lock().wakeup_all(2);
let sig_b = sign_approval(Sr25519Keyring::Bob, candidate_hash, session_index);
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_b,
candidate_hash,
session_index,
true,
Some(sig_b),
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted),);
futures_timer::Delay::new(Duration::from_millis(100)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap();
assert!(candidate_entry.approval_entry(&block_hash).unwrap().is_approved());
virtual_overseer
});
}
#[test]
fn subsystem_import_checked_approval_sets_one_block_bit_at_a_time() {
let config = HarnessConfig::default();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_receipt1 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(1_u32);
receipt
};
let candidate_receipt2 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(2_u32);
receipt
};
let candidate_hash1 = candidate_receipt1.hash();
let candidate_hash2 = candidate_receipt2.hash();
let candidate_index1 = 0;
let candidate_index2 = 1;
let validator1 = ValidatorIndex(0);
let validator2 = ValidatorIndex(1);
let session_index = 1;
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(0),
candidates: Some(vec![
(candidate_receipt1, CoreIndex(1), GroupIndex(1)),
(candidate_receipt2, CoreIndex(1), GroupIndex(1)),
]),
session_info: Some(session_info),
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
let assignments = vec![
(candidate_index1, validator1),
(candidate_index2, validator1),
(candidate_index1, validator2),
(candidate_index2, validator2),
];
for (candidate_index, validator) in assignments {
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
}
let approvals = vec![
(candidate_index1, validator1, candidate_hash1),
(candidate_index1, validator2, candidate_hash1),
(candidate_index2, validator1, candidate_hash2),
(candidate_index2, validator2, candidate_hash2),
];
for (i, (candidate_index, validator, candidate_hash)) in approvals.iter().enumerate() {
let expect_candidate1_approved = i >= 1;
let expect_candidate2_approved = i >= 3;
let expect_block_approved = expect_candidate2_approved;
let signature = if *validator == validator1 {
sign_approval(Sr25519Keyring::Alice, *candidate_hash, session_index)
} else {
sign_approval(Sr25519Keyring::Bob, *candidate_hash, session_index)
};
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
*candidate_index,
*validator,
*candidate_hash,
session_index,
expect_block_approved,
Some(signature),
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted));
futures_timer::Delay::new(Duration::from_millis(200)).await;
let block_entry = store.load_block_entry(&block_hash).unwrap().unwrap();
assert_eq!(block_entry.is_fully_approved(), expect_block_approved);
assert_eq!(
block_entry.is_candidate_approved(&candidate_hash1),
expect_candidate1_approved
);
assert_eq!(
block_entry.is_candidate_approved(&candidate_hash2),
expect_candidate2_approved
);
}
virtual_overseer
});
}
fn approved_ancestor_test(
skip_approval: impl Fn(BlockNumber) -> bool,
approved_height: BlockNumber,
) {
test_harness(HarnessConfig::default(), |test_harness| async move {
let TestHarness { mut virtual_overseer, sync_oracle_handle: _sync_oracle_handle, .. } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hashes = vec![
Hash::repeat_byte(0x01),
Hash::repeat_byte(0x02),
Hash::repeat_byte(0x03),
Hash::repeat_byte(0x04),
];
let candidate_receipts: Vec<_> = block_hashes
.iter()
.enumerate()
.map(|(i, hash)| {
let mut candidate_receipt = dummy_candidate_receipt(*hash);
candidate_receipt.descriptor.para_id = i.into();
candidate_receipt
})
.collect();
let candidate_hashes: Vec<_> = candidate_receipts.iter().map(|r| r.hash()).collect();
let candidate_index = 0;
let validator = ValidatorIndex(0);
let mut builder = ChainBuilder::new();
for (i, (block_hash, candidate_receipt)) in
block_hashes.iter().zip(candidate_receipts).enumerate()
{
let parent_hash = if i == 0 { ChainBuilder::GENESIS_HASH } else { block_hashes[i - 1] };
builder.add_block(
*block_hash,
parent_hash,
i as u32 + 1,
BlockConfig {
slot: Slot::from(i as u64),
candidates: Some(vec![(candidate_receipt, CoreIndex(0), GroupIndex(0))]),
session_info: None,
end_syncing: false,
},
);
}
builder.build(&mut virtual_overseer).await;
for (i, (block_hash, candidate_hash)) in
block_hashes.iter().zip(candidate_hashes).enumerate()
{
let rx = check_and_import_assignment(
&mut virtual_overseer,
*block_hash,
candidate_index,
validator,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
if skip_approval(i as BlockNumber + 1) {
continue
}
let rx = check_and_import_approval(
&mut virtual_overseer,
*block_hash,
candidate_index,
validator,
candidate_hash,
i as u32 + 1,
true,
None,
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted));
}
let target = block_hashes[block_hashes.len() - 1];
let block_number = block_hashes.len();
let (tx, rx) = oneshot::channel();
overseer_send(
&mut virtual_overseer,
FromOrchestra::Communication {
msg: ApprovalVotingMessage::ApprovedAncestor(target, 0, tx),
},
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::BlockNumber(hash, tx)) => {
assert_eq!(target, hash);
tx.send(Ok(Some(block_number as BlockNumber))).unwrap();
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::Ancestors {
hash,
k,
response_channel: tx,
}) => {
assert_eq!(target, hash);
assert_eq!(k, block_number - (0 + 1));
let ancestors = block_hashes.iter()
.take(block_number-1)
.rev()
.cloned()
.collect::<Vec<Hash>>();
tx.send(Ok(ancestors)).unwrap();
}
);
let approved_hash = block_hashes[approved_height as usize - 1];
let HighestApprovedAncestorBlock { hash, number, .. } = rx.await.unwrap().unwrap();
assert_eq!(approved_hash, hash);
assert_eq!(number, approved_height);
virtual_overseer
});
}
#[test]
fn subsystem_approved_ancestor_all_approved() {
approved_ancestor_test(|_| false, 4);
}
#[test]
fn subsystem_approved_ancestor_missing_approval() {
approved_ancestor_test(|i| i == 3, 2);
}
#[test]
fn subsystem_validate_approvals_cache() {
let assignment_criteria = Box::new(MockAssignmentCriteria(
|| {
let mut assignments = HashMap::new();
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 })
.into(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact {
core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)]
.try_into()
.unwrap(),
}),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
assignments
},
|_| Ok(0),
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle: _sync_oracle_handle } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let fork_block_hash = Hash::repeat_byte(0x02);
let candidate_commitments = CandidateCommitments::default();
let mut candidate_receipt = dummy_candidate_receipt(block_hash);
candidate_receipt.commitments_hash = candidate_commitments.hash();
let candidate_hash = candidate_receipt.hash();
let slot = Slot::from(1);
let candidate_index = 0;
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
let candidates = Some(vec![(candidate_receipt.clone(), CoreIndex(0), GroupIndex(0))]);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot,
candidates: candidates.clone(),
session_info: Some(session_info.clone()),
end_syncing: false,
},
)
.add_block(
fork_block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot,
candidates,
session_info: Some(session_info),
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
assert!(!clock.inner.lock().current_wakeup_is(1));
clock.inner.lock().wakeup_all(1);
assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot)));
clock.inner.lock().wakeup_all(slot_to_tick(slot));
futures_timer::Delay::new(Duration::from_millis(200)).await;
clock.inner.lock().wakeup_all(slot_to_tick(slot + 2));
assert_eq!(clock.inner.lock().wakeups.len(), 0);
futures_timer::Delay::new(Duration::from_millis(200)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap();
let our_assignment =
candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap();
assert!(our_assignment.triggered());
handle_double_assignment_import(&mut virtual_overseer, candidate_index).await;
virtual_overseer
});
}
#[test]
fn subsystem_doesnt_distribute_duplicate_compact_assignments() {
let assignment_criteria = Box::new(MockAssignmentCriteria(
|| {
let mut assignments = HashMap::new();
let cert = garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact {
core_bitfield: vec![CoreIndex(0), CoreIndex(1)].try_into().unwrap(),
});
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: cert.clone(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let _ = assignments.insert(
CoreIndex(1),
approval_db::v2::OurAssignment {
cert,
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
assignments
},
|_| Ok(0),
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness {
mut virtual_overseer,
sync_oracle_handle: _sync_oracle_handle,
clock,
..
} = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_receipt1 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(1_u32);
receipt
};
let candidate_receipt2 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(2_u32);
receipt
};
let candidate_index1 = 0;
let candidate_index2 = 1;
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot: Slot::from(0),
candidates: Some(vec![
(candidate_receipt1.clone(), CoreIndex(0), GroupIndex(1)),
(candidate_receipt2.clone(), CoreIndex(1), GroupIndex(1)),
]),
session_info: None,
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
assert_eq!(Some(2), clock.inner.lock().next_wakeup());
gum::trace!("clock \n{:?}\n", clock.inner.lock());
clock.inner.lock().wakeup_all(100);
assert_eq!(clock.inner.lock().wakeups.len(), 0);
futures_timer::Delay::new(Duration::from_millis(100)).await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
c_indices,
)) => {
assert_eq!(c_indices, vec![candidate_index1, candidate_index2].try_into().unwrap());
}
);
recover_available_data(&mut virtual_overseer).await;
fetch_validation_code(&mut virtual_overseer).await;
recover_available_data(&mut virtual_overseer).await;
fetch_validation_code(&mut virtual_overseer).await;
let candidate_entry =
store.load_candidate_entry(&candidate_receipt1.hash()).unwrap().unwrap();
let our_assignment =
candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap();
assert!(our_assignment.triggered());
let candidate_entry =
store.load_candidate_entry(&candidate_receipt2.hash()).unwrap().unwrap();
let our_assignment =
candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap();
assert!(our_assignment.triggered());
virtual_overseer
});
}
async fn handle_double_assignment_import(
virtual_overseer: &mut VirtualOverseer,
candidate_index: CandidateIndex,
) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
c_indices,
)) => {
assert_eq!(Into::<CandidateBitfield>::into(candidate_index), c_indices);
}
);
recover_available_data(virtual_overseer).await;
fetch_validation_code(virtual_overseer).await;
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
c_index
)) => {
assert_eq!(Into::<CandidateBitfield>::into(candidate_index), c_index);
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive {
exec_kind,
response_sender,
..
}) if exec_kind == PvfExecKind::Approval => {
response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default())))
.unwrap();
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 1,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_))
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 1,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_))
);
assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
}
async fn fetch_validation_code(virtual_overseer: &mut VirtualOverseer) -> Hash {
let validation_code = ValidationCode(Vec::new());
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
hash,
RuntimeApiRequest::ValidationCodeByHash(
_,
tx,
)
)) => {
tx.send(Ok(Some(validation_code))).unwrap();
hash
},
"overseer did not receive runtime API request for validation code",
)
}
async fn recover_available_data(virtual_overseer: &mut VirtualOverseer) {
let pov_block = PoV { block_data: BlockData(Vec::new()) };
let available_data =
AvailableData { pov: Arc::new(pov_block), validation_data: Default::default() };
assert_matches!(
virtual_overseer.recv().await,
AllMessages::AvailabilityRecovery(
AvailabilityRecoveryMessage::RecoverAvailableData(_, _, _, tx)
) => {
tx.send(Ok(available_data)).unwrap();
},
"overseer did not receive recover available data message",
);
}
struct TriggersAssignmentConfig<F1, F2> {
our_assigned_tranche: DelayTranche,
assign_validator_tranche: F1,
no_show_slots: u32,
assignments_to_import: Vec<u32>,
approvals_to_import: Vec<u32>,
ticks: Vec<Tick>,
should_be_triggered: F2,
}
fn triggers_assignment_test<F1, F2>(config: TriggersAssignmentConfig<F1, F2>)
where
F1: 'static
+ Fn(ValidatorIndex) -> Result<DelayTranche, criteria::InvalidAssignment>
+ Send
+ Sync,
F2: Fn(Tick) -> bool,
{
let TriggersAssignmentConfig {
our_assigned_tranche,
assign_validator_tranche,
no_show_slots,
assignments_to_import,
approvals_to_import,
ticks,
should_be_triggered,
} = config;
let assignment_criteria = Box::new(MockAssignmentCriteria(
move || {
let mut assignments = HashMap::new();
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 })
.into(),
tranche: our_assigned_tranche,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
assignments
},
assign_validator_tranche,
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness {
mut virtual_overseer,
clock,
sync_oracle_handle: _sync_oracle_handle,
..
} = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_receipt = dummy_candidate_receipt(block_hash);
let candidate_hash = candidate_receipt.hash();
let slot = Slot::from(1);
let candidate_index = 0;
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
Sr25519Keyring::Ferdie,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2), ValidatorIndex(3)],
vec![ValidatorIndex(4), ValidatorIndex(5)],
]),
relay_vrf_modulo_samples: 2,
no_show_slots,
..session_info(&validators)
};
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot,
candidates: Some(vec![(candidate_receipt, CoreIndex(0), GroupIndex(2))]),
session_info: Some(session_info),
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
for validator in assignments_to_import {
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
ValidatorIndex(validator),
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
}
let n_validators = validators.len();
for (i, &validator_index) in approvals_to_import.iter().enumerate() {
let expect_chain_approved = 3 * (i + 1) > n_validators;
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
ValidatorIndex(validator_index),
candidate_hash,
1,
expect_chain_approved,
Some(sign_approval(validators[validator_index as usize], candidate_hash, 1)),
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted));
}
let debug = false;
if debug {
step_until_done(&clock).await;
return virtual_overseer
}
futures_timer::Delay::new(Duration::from_millis(200)).await;
for tick in ticks {
assert_eq!(Some(tick), clock.inner.lock().next_wakeup());
clock.inner.lock().set_tick(tick);
futures_timer::Delay::new(Duration::from_millis(100)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap();
let our_assignment =
candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap();
assert_eq!(our_assignment.triggered(), should_be_triggered(tick), "at tick {:?}", tick);
}
virtual_overseer
});
}
async fn step_until_done(clock: &MockClock) {
let mut relevant_ticks = Vec::new();
loop {
futures_timer::Delay::new(Duration::from_millis(200)).await;
let mut clock = clock.inner.lock();
if let Some(tick) = clock.next_wakeup() {
relevant_ticks.push(tick);
clock.set_tick(tick);
} else {
break
}
}
}
#[test]
fn subsystem_process_wakeup_trigger_assignment_launch_approval() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 0,
assign_validator_tranche: |_| Ok(0),
no_show_slots: 0,
assignments_to_import: vec![1],
approvals_to_import: vec![1],
ticks: vec![
10, ],
should_be_triggered: |_| true,
});
}
#[test]
fn subsystem_assignment_triggered_solo_zero_tranche() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 0,
assign_validator_tranche: |_| Ok(0),
no_show_slots: 2,
assignments_to_import: vec![],
approvals_to_import: vec![],
ticks: vec![
10, ],
should_be_triggered: |_| true,
});
}
#[test]
fn subsystem_assignment_triggered_by_all_with_less_than_threshold() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 11,
assign_validator_tranche: |_| Ok(0),
no_show_slots: 2,
assignments_to_import: vec![1, 2, 3, 4, 5],
approvals_to_import: vec![2, 4],
ticks: vec![
2, 21, ],
should_be_triggered: |t| t == 20,
});
}
#[test]
fn subsystem_assignment_not_triggered_by_all_with_threshold() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 11,
assign_validator_tranche: |_| Ok(0),
no_show_slots: 2,
assignments_to_import: vec![1, 2, 3, 4, 5],
approvals_to_import: vec![1, 3, 5],
ticks: vec![
2, 21, ],
should_be_triggered: |_| false,
});
}
#[test]
fn subsystem_assignment_triggered_if_below_maximum_and_clock_is_equal() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 11,
assign_validator_tranche: |_| Ok(0),
no_show_slots: 2,
assignments_to_import: vec![1],
approvals_to_import: vec![],
ticks: vec![
21, 23, ],
should_be_triggered: |tick| tick >= 21,
});
}
#[test]
fn subsystem_assignment_not_triggered_more_than_maximum() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 3,
assign_validator_tranche: |_| Ok(0),
no_show_slots: 2,
assignments_to_import: vec![2, 3],
approvals_to_import: vec![],
ticks: vec![
2, 13, 30, ],
should_be_triggered: |_| false,
});
}
#[test]
fn subsystem_assignment_triggered_if_at_maximum() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 21,
assign_validator_tranche: |_| Ok(2),
no_show_slots: 2,
assignments_to_import: vec![1],
approvals_to_import: vec![],
ticks: vec![
12, 30, ],
should_be_triggered: |_| false,
});
}
#[test]
fn subsystem_assignment_not_triggered_by_exact() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 2,
assign_validator_tranche: |_| Ok(1),
no_show_slots: 2,
assignments_to_import: vec![2, 3],
approvals_to_import: vec![],
ticks: vec![
11, ],
should_be_triggered: |_| false,
});
}
#[test]
fn subsystem_assignment_not_triggered_if_at_maximum_but_clock_is_before() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 6,
assign_validator_tranche: |validator: ValidatorIndex| Ok(validator.0 as _),
no_show_slots: 0,
assignments_to_import: vec![2, 3, 4],
approvals_to_import: vec![],
ticks: vec![
12, 13, 14, ],
should_be_triggered: |_| false,
});
}
#[test]
fn subsystem_assignment_not_triggered_if_at_maximum_but_clock_is_before_with_drift() {
triggers_assignment_test(TriggersAssignmentConfig {
our_assigned_tranche: 5,
assign_validator_tranche: |validator: ValidatorIndex| Ok(validator.0 as _),
no_show_slots: 2,
assignments_to_import: vec![2, 3, 4],
approvals_to_import: vec![],
ticks: vec![
12, 13, 15, 30, 34, ],
should_be_triggered: |_| false,
});
}
#[test]
fn pre_covers_dont_stall_approval() {
let assignment_criteria = Box::new(MockAssignmentCriteria::check_only(
move |validator_index| match validator_index {
ValidatorIndex(0 | 1) => Ok(0),
ValidatorIndex(2) => Ok(1),
ValidatorIndex(_) => Err(criteria::InvalidAssignment(
criteria::InvalidAssignmentReason::ValidatorIndexOutOfBounds,
)),
},
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness {
mut virtual_overseer,
clock,
sync_oracle_handle: _sync_oracle_handle,
..
} = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let validator_index_a = ValidatorIndex(0);
let validator_index_b = ValidatorIndex(1);
let validator_index_c = ValidatorIndex(2);
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
Sr25519Keyring::One,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2), ValidatorIndex(5)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
let candidate_descriptor = make_candidate(ParaId::from(1_u32), &block_hash);
let candidate_hash = candidate_descriptor.hash();
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let slot = Slot::from(1 as u64);
builder.add_block(
block_hash,
head,
1,
BlockConfig {
slot,
candidates: Some(vec![(candidate_descriptor, CoreIndex(0), GroupIndex(0))]),
session_info: Some(session_info),
end_syncing: false,
},
);
builder.build(&mut virtual_overseer).await;
let candidate_index = 0;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_a,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted),);
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_b,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted),);
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_c,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted),);
let session_index = 1;
let sig_b = sign_approval(Sr25519Keyring::Bob, candidate_hash, session_index);
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_b,
candidate_hash,
session_index,
false,
Some(sig_b),
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted),);
let sig_c = sign_approval(Sr25519Keyring::Charlie, candidate_hash, session_index);
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_c,
candidate_hash,
session_index,
false,
Some(sig_c),
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted),);
futures_timer::Delay::new(Duration::from_millis(100)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap();
assert!(!candidate_entry.approval_entry(&block_hash).unwrap().is_approved());
assert!(clock.inner.lock().current_wakeup_is(2));
clock.inner.lock().set_tick(30);
futures_timer::Delay::new(Duration::from_millis(100)).await;
assert_eq!(clock.inner.lock().next_wakeup(), Some(31));
clock.inner.lock().set_tick(31);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainSelection(ChainSelectionMessage::Approved(b_hash)) => {
assert_eq!(b_hash, block_hash);
}
);
let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap();
assert!(candidate_entry.approval_entry(&block_hash).unwrap().is_approved());
assert!(clock.inner.lock().next_wakeup().is_none());
let block_entry = store.load_block_entry(&block_hash).unwrap().unwrap();
assert!(block_entry.is_fully_approved());
virtual_overseer
});
}
#[test]
fn waits_until_approving_assignments_are_old_enough() {
let assignment_criteria = Box::new(MockAssignmentCriteria::check_only(|_| Ok(0)));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness {
mut virtual_overseer,
clock,
sync_oracle_handle: _sync_oracle_handle,
..
} = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
clock.inner.lock().set_tick(APPROVAL_DELAY);
let block_hash = Hash::repeat_byte(0x01);
let validator_index_a = ValidatorIndex(0);
let validator_index_b = ValidatorIndex(1);
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
Sr25519Keyring::One,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2), ValidatorIndex(5)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
let candidate_descriptor = make_candidate(ParaId::from(1_u32), &block_hash);
let candidate_hash = candidate_descriptor.hash();
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let slot = Slot::from(1 as u64);
builder.add_block(
block_hash,
head,
1,
BlockConfig {
slot,
candidates: Some(vec![(candidate_descriptor, CoreIndex(0), GroupIndex(0))]),
session_info: Some(session_info),
end_syncing: false,
},
);
builder.build(&mut virtual_overseer).await;
let candidate_index = 0;
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_a,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted),);
let rx = check_and_import_assignment(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_b,
)
.await;
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted),);
assert!(clock.inner.lock().current_wakeup_is(APPROVAL_DELAY + APPROVAL_DELAY));
let session_index = 1;
let sig_a = sign_approval(Sr25519Keyring::Alice, candidate_hash, session_index);
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_a,
candidate_hash,
session_index,
false,
Some(sig_a),
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted),);
let sig_b = sign_approval(Sr25519Keyring::Bob, candidate_hash, session_index);
let rx = check_and_import_approval(
&mut virtual_overseer,
block_hash,
candidate_index,
validator_index_b,
candidate_hash,
session_index,
false,
Some(sig_b),
)
.await;
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted),);
futures_timer::Delay::new(Duration::from_millis(100)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap();
assert!(!candidate_entry.approval_entry(&block_hash).unwrap().is_approved());
assert!(clock.inner.lock().current_wakeup_is(APPROVAL_DELAY + APPROVAL_DELAY));
clock.inner.lock().set_tick(APPROVAL_DELAY + APPROVAL_DELAY);
futures_timer::Delay::new(Duration::from_millis(100)).await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainSelection(ChainSelectionMessage::Approved(b_hash)) => {
assert_eq!(b_hash, block_hash);
}
);
let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap();
assert!(candidate_entry.approval_entry(&block_hash).unwrap().is_approved());
assert!(clock.inner.lock().next_wakeup().is_none());
let block_entry = store.load_block_entry(&block_hash).unwrap().unwrap();
assert!(block_entry.is_fully_approved());
virtual_overseer
});
}
#[test]
fn test_approval_is_sent_on_max_approval_coalesce_count() {
let assignment_criteria = Box::new(MockAssignmentCriteria(
|| {
let mut assignments = HashMap::new();
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 })
.into(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let assignments_cert =
garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact {
core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)]
.try_into()
.unwrap(),
});
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: assignments_cert.clone(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let _ = assignments.insert(
CoreIndex(1),
approval_db::v2::OurAssignment {
cert: assignments_cert.clone(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
assignments
},
|_| Ok(0),
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle: _sync_oracle_handle } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_commitments = CandidateCommitments::default();
let candidate_receipt1 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(1_u32);
receipt.commitments_hash = candidate_commitments.hash();
receipt
};
let candidate_hash1 = candidate_receipt1.hash();
let candidate_receipt2 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(2_u32);
receipt.commitments_hash = candidate_commitments.hash();
receipt
};
let slot = Slot::from(1);
let candidate_index1 = 0;
let candidate_index2 = 1;
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
let candidates = Some(vec![
(candidate_receipt1.clone(), CoreIndex(0), GroupIndex(0)),
(candidate_receipt2.clone(), CoreIndex(1), GroupIndex(1)),
]);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot,
candidates: candidates.clone(),
session_info: Some(session_info.clone()),
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
assert!(!clock.inner.lock().current_wakeup_is(1));
clock.inner.lock().wakeup_all(1);
assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot)));
clock.inner.lock().wakeup_all(slot_to_tick(slot));
futures_timer::Delay::new(Duration::from_millis(200)).await;
clock.inner.lock().wakeup_all(slot_to_tick(slot + 2));
assert_eq!(clock.inner.lock().wakeups.len(), 0);
futures_timer::Delay::new(Duration::from_millis(200)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash1).unwrap().unwrap();
let our_assignment =
candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap();
assert!(our_assignment.triggered());
handle_approval_on_max_coalesce_count(
&mut virtual_overseer,
vec![candidate_index1, candidate_index2],
)
.await;
virtual_overseer
});
}
async fn handle_approval_on_max_coalesce_count(
virtual_overseer: &mut VirtualOverseer,
candidate_indicies: Vec<CandidateIndex>,
) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
c_indices,
)) => {
assert_eq!(TryInto::<CandidateBitfield>::try_into(candidate_indicies.clone()).unwrap(), c_indices);
}
);
for _ in &candidate_indicies {
recover_available_data(virtual_overseer).await;
fetch_validation_code(virtual_overseer).await;
}
for _ in &candidate_indicies {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive{exec_kind, response_sender, ..}) if exec_kind == PvfExecKind::Approval => {
response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default())))
.unwrap();
}
);
}
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 2,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 2,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(vote)) => {
assert_eq!(TryInto::<CandidateBitfield>::try_into(candidate_indicies).unwrap(), vote.candidate_indices);
}
);
assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
}
async fn handle_approval_on_max_wait_time(
virtual_overseer: &mut VirtualOverseer,
candidate_indicies: Vec<CandidateIndex>,
clock: Box<MockClock>,
) {
const TICK_NOW_BEGIN: u64 = 1;
const MAX_COALESCE_COUNT: u32 = 3;
clock.inner.lock().set_tick(TICK_NOW_BEGIN);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
c_indices,
)) => {
assert_eq!(TryInto::<CandidateBitfield>::try_into(candidate_indicies.clone()).unwrap(), c_indices);
}
);
for _ in &candidate_indicies {
recover_available_data(virtual_overseer).await;
fetch_validation_code(virtual_overseer).await;
}
for _ in &candidate_indicies {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive{exec_kind, response_sender, ..}) if exec_kind == PvfExecKind::Approval => {
response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default())))
.unwrap();
}
);
}
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: MAX_COALESCE_COUNT,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: MAX_COALESCE_COUNT,
}));
}
);
assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
clock
.inner
.lock()
.set_tick(MAX_APPROVAL_COALESCE_WAIT_TICKS as Tick + TICK_NOW_BEGIN - 1);
assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
clock
.inner
.lock()
.set_tick(MAX_APPROVAL_COALESCE_WAIT_TICKS as Tick + TICK_NOW_BEGIN);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 3,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(vote)) => {
assert_eq!(TryInto::<CandidateBitfield>::try_into(candidate_indicies).unwrap(), vote.candidate_indices);
}
);
assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
}
#[test]
fn test_approval_is_sent_on_max_approval_coalesce_wait() {
let assignment_criteria = Box::new(MockAssignmentCriteria(
|| {
let mut assignments = HashMap::new();
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 })
.into(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let assignments_cert =
garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact {
core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)]
.try_into()
.unwrap(),
});
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: assignments_cert.clone(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let _ = assignments.insert(
CoreIndex(1),
approval_db::v2::OurAssignment {
cert: assignments_cert.clone(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
assignments
},
|_| Ok(0),
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle: _ } = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_commitments = CandidateCommitments::default();
let candidate_receipt1 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(1_u32);
receipt.commitments_hash = candidate_commitments.hash();
receipt
};
let candidate_hash1 = candidate_receipt1.hash();
let candidate_receipt2 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(2_u32);
receipt.commitments_hash = candidate_commitments.hash();
receipt
};
let slot = Slot::from(1);
let candidate_index1 = 0;
let candidate_index2 = 1;
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
let candidates = Some(vec![
(candidate_receipt1.clone(), CoreIndex(0), GroupIndex(0)),
(candidate_receipt2.clone(), CoreIndex(1), GroupIndex(1)),
]);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot,
candidates: candidates.clone(),
session_info: Some(session_info.clone()),
end_syncing: false,
},
)
.build(&mut virtual_overseer)
.await;
assert!(!clock.inner.lock().current_wakeup_is(1));
clock.inner.lock().wakeup_all(1);
assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot)));
clock.inner.lock().wakeup_all(slot_to_tick(slot));
futures_timer::Delay::new(Duration::from_millis(200)).await;
clock.inner.lock().wakeup_all(slot_to_tick(slot + 2));
assert_eq!(clock.inner.lock().wakeups.len(), 0);
futures_timer::Delay::new(Duration::from_millis(200)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash1).unwrap().unwrap();
let our_assignment =
candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap();
assert!(our_assignment.triggered());
handle_approval_on_max_wait_time(
&mut virtual_overseer,
vec![candidate_index1, candidate_index2],
clock,
)
.await;
virtual_overseer
});
}
async fn build_chain_with_two_blocks_with_one_candidate_each(
block_hash1: Hash,
block_hash2: Hash,
slot: Slot,
sync_oracle_handle: TestSyncOracleHandle,
candidate_receipt: CandidateReceipt,
) -> (ChainBuilder, SessionInfo) {
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
let candidates = Some(vec![(candidate_receipt.clone(), CoreIndex(0), GroupIndex(0))]);
let mut chain_builder = ChainBuilder::new();
chain_builder
.major_syncing(sync_oracle_handle.is_major_syncing.clone())
.add_block(
block_hash1,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot,
candidates: candidates.clone(),
session_info: Some(session_info.clone()),
end_syncing: false,
},
)
.add_block(
block_hash2,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot,
candidates,
session_info: Some(session_info.clone()),
end_syncing: true,
},
);
(chain_builder, session_info)
}
async fn setup_overseer_with_two_blocks_each_with_one_assignment_triggered(
virtual_overseer: &mut VirtualOverseer,
store: TestStore,
clock: &Box<MockClock>,
sync_oracle_handle: TestSyncOracleHandle,
) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let fork_block_hash = Hash::repeat_byte(0x02);
let candidate_commitments = CandidateCommitments::default();
let mut candidate_receipt = dummy_candidate_receipt(block_hash);
candidate_receipt.commitments_hash = candidate_commitments.hash();
let candidate_hash = candidate_receipt.hash();
let slot = Slot::from(1);
let (chain_builder, _session_info) = build_chain_with_two_blocks_with_one_candidate_each(
block_hash,
fork_block_hash,
slot,
sync_oracle_handle,
candidate_receipt,
)
.await;
chain_builder.build(virtual_overseer).await;
assert!(!clock.inner.lock().current_wakeup_is(1));
clock.inner.lock().wakeup_all(1);
assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot)));
clock.inner.lock().wakeup_all(slot_to_tick(slot));
futures_timer::Delay::new(Duration::from_millis(200)).await;
clock.inner.lock().wakeup_all(slot_to_tick(slot + 2));
assert_eq!(clock.inner.lock().wakeups.len(), 0);
futures_timer::Delay::new(Duration::from_millis(200)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap();
let our_assignment =
candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap();
assert!(our_assignment.triggered());
}
#[test]
fn subsystem_relaunches_approval_work_on_restart() {
let assignment_criteria = Box::new(MockAssignmentCriteria(
|| {
let mut assignments = HashMap::new();
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 })
.into(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact {
core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)]
.try_into()
.unwrap(),
}),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
assignments
},
|_| Ok(0),
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
let store_clone = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness;
setup_overseer_with_two_blocks_each_with_one_assignment_triggered(
&mut virtual_overseer,
store,
&clock,
sync_oracle_handle,
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
_,
)) => {
}
);
recover_available_data(&mut virtual_overseer).await;
fetch_validation_code(&mut virtual_overseer).await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
_
)) => {
}
);
virtual_overseer
});
let config = HarnessConfigBuilder::default().backend(store_clone).major_syncing(true).build();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let fork_block_hash = Hash::repeat_byte(0x02);
let candidate_commitments = CandidateCommitments::default();
let mut candidate_receipt = dummy_candidate_receipt(block_hash);
candidate_receipt.commitments_hash = candidate_commitments.hash();
let slot = Slot::from(1);
clock.inner.lock().set_tick(slot_to_tick(slot + 2));
let (chain_builder, session_info) = build_chain_with_two_blocks_with_one_candidate_each(
block_hash,
fork_block_hash,
slot,
sync_oracle_handle,
candidate_receipt,
)
.await;
chain_builder.build(&mut virtual_overseer).await;
futures_timer::Delay::new(Duration::from_millis(2000)).await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(
_,
RuntimeApiRequest::SessionInfo(_, si_tx),
)
) => {
si_tx.send(Ok(Some(session_info.clone()))).unwrap();
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(
_,
RuntimeApiRequest::SessionExecutorParams(_, si_tx),
)
) => {
si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap();
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), )
) => {
si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap();
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks(
_,
)) => {
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
_,
)) => {
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
_,
)) => {
}
);
recover_available_data(&mut virtual_overseer).await;
fetch_validation_code(&mut virtual_overseer).await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive {
exec_kind,
response_sender,
..
}) if exec_kind == PvfExecKind::Approval => {
response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default())))
.unwrap();
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 1,
}));
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_))
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 1,
}));
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_))
);
assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
virtual_overseer
});
}
#[test]
fn subsystem_sends_pending_approvals_on_approval_restart() {
let assignment_criteria = Box::new(MockAssignmentCriteria(
|| {
let mut assignments = HashMap::new();
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 })
.into(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact {
core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)]
.try_into()
.unwrap(),
}),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
assignments
},
|_| Ok(0),
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
let store_clone = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness;
setup_overseer_with_two_blocks_each_with_one_assignment_triggered(
&mut virtual_overseer,
store,
&clock,
sync_oracle_handle,
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
_,
)) => {
}
);
recover_available_data(&mut virtual_overseer).await;
fetch_validation_code(&mut virtual_overseer).await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
_
)) => {
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive {
exec_kind,
response_sender,
..
}) if exec_kind == PvfExecKind::Approval => {
response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default())))
.unwrap();
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 6,
}));
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 6,
}));
}
);
assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
virtual_overseer
});
let config = HarnessConfigBuilder::default().backend(store_clone).major_syncing(true).build();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let fork_block_hash = Hash::repeat_byte(0x02);
let candidate_commitments = CandidateCommitments::default();
let mut candidate_receipt = dummy_candidate_receipt(block_hash);
candidate_receipt.commitments_hash = candidate_commitments.hash();
let slot = Slot::from(1);
clock.inner.lock().set_tick(slot_to_tick(slot + 2));
let (chain_builder, session_info) = build_chain_with_two_blocks_with_one_candidate_each(
block_hash,
fork_block_hash,
slot,
sync_oracle_handle,
candidate_receipt,
)
.await;
chain_builder.build(&mut virtual_overseer).await;
futures_timer::Delay::new(Duration::from_millis(2000)).await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks(
_,
)) => {
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
_,
)) => {
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
_,
)) => {
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 1,
}));
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(
_,
RuntimeApiRequest::SessionInfo(_, si_tx),
)
) => {
si_tx.send(Ok(Some(session_info.clone()))).unwrap();
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(
_,
RuntimeApiRequest::SessionExecutorParams(_, si_tx),
)
) => {
si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap();
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), )
) => {
si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap();
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_))
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 1,
}));
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_))
);
assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
virtual_overseer
});
}