mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 10:01:17 +00:00
342d720573
* Use same rustfmt.toml as Substrate Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * format format file Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Format with new config Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add Substrate Clippy config Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Print Clippy version in CI Otherwise its difficult to reproduce locally. Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Make fmt happy Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update node/core/pvf/src/error.rs Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io> * Update node/core/pvf/src/error.rs Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io> --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>
938 lines
29 KiB
Rust
938 lines
29 KiB
Rust
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// This file is part of Polkadot.
|
|
|
|
// Polkadot is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Polkadot is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use ::test_helpers::{dummy_digest, dummy_hash};
|
|
use futures::{channel::oneshot, future::BoxFuture, prelude::*};
|
|
use polkadot_node_subsystem::{
|
|
jaeger,
|
|
messages::{
|
|
AllMessages, CandidateValidationMessage, PreCheckOutcome, PvfCheckerMessage,
|
|
RuntimeApiMessage, RuntimeApiRequest,
|
|
},
|
|
ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, LeafStatus, OverseerSignal, RuntimeApiError,
|
|
};
|
|
use polkadot_node_subsystem_test_helpers::{make_subsystem_context, TestSubsystemContextHandle};
|
|
use polkadot_primitives::{
|
|
BlockNumber, Hash, Header, PvfCheckStatement, SessionIndex, ValidationCode, ValidationCodeHash,
|
|
ValidatorId,
|
|
};
|
|
use sp_application_crypto::AppCrypto;
|
|
use sp_core::testing::TaskExecutor;
|
|
use sp_keyring::Sr25519Keyring;
|
|
use sp_keystore::Keystore;
|
|
use sp_runtime::traits::AppVerify;
|
|
use std::{collections::HashMap, sync::Arc, time::Duration};
|
|
|
|
type VirtualOverseer = TestSubsystemContextHandle<PvfCheckerMessage>;
|
|
|
|
fn dummy_validation_code_hash(descriminator: u8) -> ValidationCodeHash {
|
|
ValidationCode(vec![descriminator]).hash()
|
|
}
|
|
|
|
struct StartsNewSession {
|
|
session_index: SessionIndex,
|
|
validators: Vec<Sr25519Keyring>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct FakeLeaf {
|
|
block_hash: Hash,
|
|
block_number: BlockNumber,
|
|
pvfs: Vec<ValidationCodeHash>,
|
|
}
|
|
|
|
impl FakeLeaf {
|
|
fn new(parent_hash: Hash, block_number: BlockNumber, pvfs: Vec<ValidationCodeHash>) -> Self {
|
|
let block_header = Header {
|
|
parent_hash,
|
|
number: block_number,
|
|
digest: dummy_digest(),
|
|
state_root: dummy_hash(),
|
|
extrinsics_root: dummy_hash(),
|
|
};
|
|
let block_hash = block_header.hash();
|
|
Self { block_hash, block_number, pvfs }
|
|
}
|
|
|
|
fn descendant(&self, pvfs: Vec<ValidationCodeHash>) -> FakeLeaf {
|
|
FakeLeaf::new(self.block_hash, self.block_number + 1, pvfs)
|
|
}
|
|
}
|
|
|
|
struct LeafState {
|
|
/// The session index at which this leaf was activated.
|
|
session_index: SessionIndex,
|
|
|
|
/// The list of PVFs that are pending in this leaf.
|
|
pvfs: Vec<ValidationCodeHash>,
|
|
}
|
|
|
|
/// The state we model about a session.
|
|
struct SessionState {
|
|
validators: Vec<ValidatorId>,
|
|
}
|
|
|
|
struct TestState {
|
|
leaves: HashMap<Hash, LeafState>,
|
|
sessions: HashMap<SessionIndex, SessionState>,
|
|
last_session_index: SessionIndex,
|
|
}
|
|
|
|
const OUR_VALIDATOR: Sr25519Keyring = Sr25519Keyring::Alice;
|
|
|
|
fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
|
|
val_ids.iter().map(|v| v.public().into()).collect()
|
|
}
|
|
|
|
impl TestState {
|
|
fn new() -> Self {
|
|
// Initialize the default session 1. No validators are present there.
|
|
let last_session_index = 1;
|
|
let mut sessions = HashMap::new();
|
|
sessions.insert(last_session_index, SessionState { validators: vec![] });
|
|
|
|
let mut leaves = HashMap::new();
|
|
leaves.insert(dummy_hash(), LeafState { session_index: last_session_index, pvfs: vec![] });
|
|
|
|
Self { leaves, sessions, last_session_index }
|
|
}
|
|
|
|
/// A convenience function to receive a message from the overseer and returning `None` if
|
|
/// nothing was received within a reasonable (for local tests anyway) timeout.
|
|
async fn recv_timeout(&mut self, handle: &mut VirtualOverseer) -> Option<AllMessages> {
|
|
futures::select! {
|
|
msg = handle.recv().fuse() => {
|
|
Some(msg)
|
|
}
|
|
_ = futures_timer::Delay::new(Duration::from_millis(500)).fuse() => {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn send_conclude(&mut self, handle: &mut VirtualOverseer) {
|
|
// To ensure that no messages are left in the queue there is no better way to just wait.
|
|
match self.recv_timeout(handle).await {
|
|
Some(msg) => {
|
|
panic!("we supposed to conclude, but received a message: {:#?}", msg);
|
|
},
|
|
None => {
|
|
// No messages are received. We are good.
|
|
},
|
|
}
|
|
|
|
handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
|
|
}
|
|
|
|
/// Convenience function to invoke [`active_leaves_update`] with the new leaf that starts a new
|
|
/// session and there are no deactivated leaves.
|
|
///
|
|
/// Returns the block hash of the newly activated leaf.
|
|
async fn activate_leaf_with_session(
|
|
&mut self,
|
|
handle: &mut VirtualOverseer,
|
|
leaf: FakeLeaf,
|
|
starts_new_session: StartsNewSession,
|
|
) {
|
|
self.active_leaves_update(handle, Some(leaf), Some(starts_new_session), &[])
|
|
.await
|
|
}
|
|
|
|
/// Convenience function to invoke [`active_leaves_update`] with a new leaf. The leaf does not
|
|
/// start a new session and there are no deactivated leaves.
|
|
async fn activate_leaf(&mut self, handle: &mut VirtualOverseer, leaf: FakeLeaf) {
|
|
self.active_leaves_update(handle, Some(leaf), None, &[]).await
|
|
}
|
|
|
|
async fn deactivate_leaves(
|
|
&mut self,
|
|
handle: &mut VirtualOverseer,
|
|
deactivated: impl IntoIterator<Item = &Hash>,
|
|
) {
|
|
self.active_leaves_update(handle, None, None, deactivated).await
|
|
}
|
|
|
|
/// Sends an `ActiveLeavesUpdate` message to the overseer and also updates the test state to
|
|
/// record leaves and session changes.
|
|
///
|
|
/// NOTE: This function may stall if there is an unhandled message for the overseer.
|
|
async fn active_leaves_update(
|
|
&mut self,
|
|
handle: &mut VirtualOverseer,
|
|
fake_leaf: Option<FakeLeaf>,
|
|
starts_new_session: Option<StartsNewSession>,
|
|
deactivated: impl IntoIterator<Item = &Hash>,
|
|
) {
|
|
if let Some(new_session) = starts_new_session {
|
|
assert!(fake_leaf.is_some(), "Session can be started only with an activated leaf");
|
|
self.last_session_index = new_session.session_index;
|
|
let prev = self.sessions.insert(
|
|
new_session.session_index,
|
|
SessionState { validators: validator_pubkeys(&new_session.validators) },
|
|
);
|
|
assert!(prev.is_none(), "Session {} already exists", new_session.session_index);
|
|
}
|
|
|
|
let activated = if let Some(activated_leaf) = fake_leaf {
|
|
self.leaves.insert(
|
|
activated_leaf.block_hash.clone(),
|
|
LeafState {
|
|
session_index: self.last_session_index,
|
|
pvfs: activated_leaf.pvfs.clone(),
|
|
},
|
|
);
|
|
|
|
Some(ActivatedLeaf {
|
|
hash: activated_leaf.block_hash,
|
|
span: Arc::new(jaeger::Span::Disabled),
|
|
number: activated_leaf.block_number,
|
|
status: LeafStatus::Fresh,
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
handle
|
|
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate {
|
|
activated,
|
|
deactivated: deactivated.into_iter().cloned().collect(),
|
|
})))
|
|
.await;
|
|
}
|
|
|
|
/// Expects that the subsystem has sent a `Validators` Runtime API request. Answers with the
|
|
/// mocked validators for the requested leaf.
|
|
async fn expect_validators(&mut self, handle: &mut VirtualOverseer) {
|
|
match self.recv_timeout(handle).await.expect("timeout waiting for a message") {
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
relay_parent,
|
|
RuntimeApiRequest::Validators(tx),
|
|
)) => match self.leaves.get(&relay_parent) {
|
|
Some(leaf) => {
|
|
let session_index = leaf.session_index;
|
|
let session = self.sessions.get(&session_index).unwrap();
|
|
tx.send(Ok(session.validators.clone())).unwrap();
|
|
},
|
|
None => {
|
|
panic!("a request to an unknown relay parent has been made");
|
|
},
|
|
},
|
|
msg => panic!("Unexpected message was received: {:#?}", msg),
|
|
}
|
|
}
|
|
|
|
/// Expects that the subsystem has sent a `SessionIndexForChild` Runtime API request. Answers
|
|
/// with the mocked session index for the requested leaf.
|
|
async fn expect_session_for_child(&mut self, handle: &mut VirtualOverseer) {
|
|
match self.recv_timeout(handle).await.expect("timeout waiting for a message") {
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
relay_parent,
|
|
RuntimeApiRequest::SessionIndexForChild(tx),
|
|
)) => match self.leaves.get(&relay_parent) {
|
|
Some(leaf) => {
|
|
tx.send(Ok(leaf.session_index)).unwrap();
|
|
},
|
|
None => {
|
|
panic!("a request to an unknown relay parent has been made");
|
|
},
|
|
},
|
|
msg => panic!("Unexpected message was received: {:#?}", msg),
|
|
}
|
|
}
|
|
|
|
/// Expects that the subsystem has sent a `PvfsRequirePrecheck` Runtime API request. Answers
|
|
/// with the mocked PVF set for the requested leaf.
|
|
async fn expect_pvfs_require_precheck(
|
|
&mut self,
|
|
handle: &mut VirtualOverseer,
|
|
) -> ExpectPvfsRequirePrecheck<'_> {
|
|
match self.recv_timeout(handle).await.expect("timeout waiting for a message") {
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
relay_parent,
|
|
RuntimeApiRequest::PvfsRequirePrecheck(tx),
|
|
)) => ExpectPvfsRequirePrecheck { test_state: self, relay_parent, tx },
|
|
msg => panic!("Unexpected message was received: {:#?}", msg),
|
|
}
|
|
}
|
|
|
|
/// Expects that the subsystem has sent a pre-checking request to candidate-validation. Returns
|
|
/// a mocked handle for the request.
|
|
async fn expect_candidate_precheck(
|
|
&mut self,
|
|
handle: &mut VirtualOverseer,
|
|
) -> ExpectCandidatePrecheck {
|
|
match self.recv_timeout(handle).await.expect("timeout waiting for a message") {
|
|
AllMessages::CandidateValidation(CandidateValidationMessage::PreCheck(
|
|
relay_parent,
|
|
validation_code_hash,
|
|
tx,
|
|
)) => ExpectCandidatePrecheck { relay_parent, validation_code_hash, tx },
|
|
msg => panic!("Unexpected message was received: {:#?}", msg),
|
|
}
|
|
}
|
|
|
|
/// Expects that the subsystem has sent a `SubmitPvfCheckStatement` runtime API request. Returns
|
|
/// a mocked handle for the request.
|
|
async fn expect_submit_vote(&mut self, handle: &mut VirtualOverseer) -> ExpectSubmitVote {
|
|
match self.recv_timeout(handle).await.expect("timeout waiting for a message") {
|
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
relay_parent,
|
|
RuntimeApiRequest::SubmitPvfCheckStatement(stmt, signature, tx),
|
|
)) => {
|
|
let signing_payload = stmt.signing_payload();
|
|
assert!(signature.verify(&signing_payload[..], &OUR_VALIDATOR.public().into()));
|
|
|
|
ExpectSubmitVote { relay_parent, stmt, tx }
|
|
},
|
|
msg => panic!("Unexpected message was received: {:#?}", msg),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
struct ExpectPvfsRequirePrecheck<'a> {
|
|
test_state: &'a mut TestState,
|
|
relay_parent: Hash,
|
|
tx: oneshot::Sender<Result<Vec<ValidationCodeHash>, RuntimeApiError>>,
|
|
}
|
|
|
|
impl<'a> ExpectPvfsRequirePrecheck<'a> {
|
|
fn reply_mock(self) {
|
|
match self.test_state.leaves.get(&self.relay_parent) {
|
|
Some(leaf) => {
|
|
self.tx.send(Ok(leaf.pvfs.clone())).unwrap();
|
|
},
|
|
None => {
|
|
panic!(
|
|
"a request to an unknown relay parent has been made: {:#?}",
|
|
self.relay_parent
|
|
);
|
|
},
|
|
}
|
|
}
|
|
|
|
fn reply_not_supported(self) {
|
|
self.tx
|
|
.send(Err(RuntimeApiError::NotSupported { runtime_api_name: "pvfs_require_precheck" }))
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
struct ExpectCandidatePrecheck {
|
|
relay_parent: Hash,
|
|
validation_code_hash: ValidationCodeHash,
|
|
tx: oneshot::Sender<PreCheckOutcome>,
|
|
}
|
|
|
|
impl ExpectCandidatePrecheck {
|
|
fn reply(self, outcome: PreCheckOutcome) {
|
|
self.tx.send(outcome).unwrap();
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
struct ExpectSubmitVote {
|
|
relay_parent: Hash,
|
|
stmt: PvfCheckStatement,
|
|
tx: oneshot::Sender<Result<(), RuntimeApiError>>,
|
|
}
|
|
|
|
impl ExpectSubmitVote {
|
|
fn reply_ok(self) {
|
|
self.tx.send(Ok(())).unwrap();
|
|
}
|
|
}
|
|
|
|
fn test_harness(test: impl FnOnce(TestState, VirtualOverseer) -> BoxFuture<'static, ()>) {
|
|
let pool = TaskExecutor::new();
|
|
let (ctx, handle) = make_subsystem_context::<PvfCheckerMessage, _>(pool.clone());
|
|
let keystore = Arc::new(sc_keystore::LocalKeystore::in_memory());
|
|
|
|
// Add OUR_VALIDATOR (which is Alice) to the keystore.
|
|
Keystore::sr25519_generate_new(&*keystore, ValidatorId::ID, Some(&OUR_VALIDATOR.to_seed()))
|
|
.expect("Generating keys for our node failed");
|
|
|
|
let subsystem_task = crate::run(ctx, keystore, crate::Metrics::default()).map(|x| x.unwrap());
|
|
|
|
let test_state = TestState::new();
|
|
let test_task = test(test_state, handle);
|
|
|
|
futures::executor::block_on(future::join(subsystem_task, test_task));
|
|
}
|
|
|
|
#[test]
|
|
fn concludes_correctly() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn reacts_to_new_pvfs_in_heads() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
let block = FakeLeaf::new(dummy_hash(), 1, vec![dummy_validation_code_hash(1)]);
|
|
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
block.clone(),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
let pre_check = test_state.expect_candidate_precheck(&mut handle).await;
|
|
assert_eq!(pre_check.relay_parent, block.block_hash);
|
|
pre_check.reply(PreCheckOutcome::Valid);
|
|
|
|
let vote = test_state.expect_submit_vote(&mut handle).await;
|
|
assert_eq!(vote.relay_parent, block.block_hash);
|
|
assert_eq!(vote.stmt.accept, true);
|
|
assert_eq!(vote.stmt.session_index, 2);
|
|
assert_eq!(vote.stmt.validator_index, 0.into());
|
|
assert_eq!(vote.stmt.subject, dummy_validation_code_hash(1));
|
|
vote.reply_ok();
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn no_new_session_no_validators_request() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 1, vec![]),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
test_state
|
|
.activate_leaf(&mut handle, FakeLeaf::new(dummy_hash(), 2, vec![]))
|
|
.await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn activation_of_descendant_leaves_pvfs_in_view() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
let block_1 = FakeLeaf::new(dummy_hash(), 1, vec![dummy_validation_code_hash(1)]);
|
|
let block_2 = block_1.descendant(vec![dummy_validation_code_hash(1)]);
|
|
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
block_1.clone(),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
test_state
|
|
.expect_candidate_precheck(&mut handle)
|
|
.await
|
|
.reply(PreCheckOutcome::Valid);
|
|
test_state.expect_submit_vote(&mut handle).await.reply_ok();
|
|
|
|
// Now we deactivate the first block and activate it's descendant.
|
|
test_state
|
|
.active_leaves_update(
|
|
&mut handle,
|
|
Some(block_2),
|
|
None, // no new session started
|
|
&[block_1.block_hash],
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn reactivating_pvf_leads_to_second_check() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
let pvf = dummy_validation_code_hash(1);
|
|
let block_1 = FakeLeaf::new(dummy_hash(), 1, vec![pvf.clone()]);
|
|
let block_2 = block_1.descendant(vec![]);
|
|
let block_3 = block_2.descendant(vec![pvf.clone()]);
|
|
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
block_1.clone(),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
test_state
|
|
.expect_candidate_precheck(&mut handle)
|
|
.await
|
|
.reply(PreCheckOutcome::Valid);
|
|
test_state.expect_submit_vote(&mut handle).await.reply_ok();
|
|
|
|
// Now activate a descdedant leaf, where the PVF is not present.
|
|
test_state
|
|
.active_leaves_update(
|
|
&mut handle,
|
|
Some(block_2.clone()),
|
|
None,
|
|
&[block_1.block_hash],
|
|
)
|
|
.await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
|
|
// Now the third block is activated, where the PVF is present.
|
|
test_state.activate_leaf(&mut handle, block_3).await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state
|
|
.expect_candidate_precheck(&mut handle)
|
|
.await
|
|
.reply(PreCheckOutcome::Valid);
|
|
|
|
// We do not vote here, because the PVF was already voted on within this session.
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn dont_double_vote_for_pvfs_in_view() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
let pvf = dummy_validation_code_hash(1);
|
|
let block_1_1 = FakeLeaf::new([1; 32].into(), 1, vec![pvf.clone()]);
|
|
let block_2_1 = FakeLeaf::new([2; 32].into(), 1, vec![pvf.clone()]);
|
|
let block_1_2 = block_1_1.descendant(vec![pvf.clone()]);
|
|
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
block_1_1.clone(),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
// Pre-checking will take quite some time.
|
|
let pre_check = test_state.expect_candidate_precheck(&mut handle).await;
|
|
|
|
// Activate a sibiling leaf, has the same PVF.
|
|
test_state.activate_leaf(&mut handle, block_2_1).await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
|
|
// Now activate a descendant leaf with the same PVF.
|
|
test_state
|
|
.active_leaves_update(
|
|
&mut handle,
|
|
Some(block_1_2.clone()),
|
|
None,
|
|
&[block_1_1.block_hash],
|
|
)
|
|
.await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
|
|
// Now finish the pre-checking request.
|
|
pre_check.reply(PreCheckOutcome::Valid);
|
|
test_state.expect_submit_vote(&mut handle).await.reply_ok();
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn judgements_come_out_of_order() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
let pvf_1 = dummy_validation_code_hash(1);
|
|
let pvf_2 = dummy_validation_code_hash(2);
|
|
|
|
let block_1 = FakeLeaf::new([1; 32].into(), 1, vec![pvf_1.clone()]);
|
|
let block_2 = FakeLeaf::new([2; 32].into(), 1, vec![pvf_2.clone()]);
|
|
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
block_1.clone(),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
let pre_check_1 = test_state.expect_candidate_precheck(&mut handle).await;
|
|
|
|
// Activate a sibiling leaf, has the second PVF.
|
|
test_state.activate_leaf(&mut handle, block_2).await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
|
|
let pre_check_2 = test_state.expect_candidate_precheck(&mut handle).await;
|
|
|
|
// Resolve the PVF pre-checks out of order.
|
|
pre_check_2.reply(PreCheckOutcome::Valid);
|
|
pre_check_1.reply(PreCheckOutcome::Invalid);
|
|
|
|
// Catch the vote for the second PVF.
|
|
let vote_2 = test_state.expect_submit_vote(&mut handle).await;
|
|
assert_eq!(vote_2.stmt.accept, true);
|
|
assert_eq!(vote_2.stmt.subject, pvf_2.clone());
|
|
vote_2.reply_ok();
|
|
|
|
// Catch the vote for the first PVF.
|
|
let vote_1 = test_state.expect_submit_vote(&mut handle).await;
|
|
assert_eq!(vote_1.stmt.accept, false);
|
|
assert_eq!(vote_1.stmt.subject, pvf_1.clone());
|
|
vote_1.reply_ok();
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn dont_vote_until_a_validator() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 1, vec![dummy_validation_code_hash(1)]),
|
|
StartsNewSession { session_index: 2, validators: vec![Sr25519Keyring::Bob] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
test_state
|
|
.expect_candidate_precheck(&mut handle)
|
|
.await
|
|
.reply(PreCheckOutcome::Invalid);
|
|
|
|
// Now a leaf brings a new session. In this session our validator comes into the active
|
|
// set. That means it will cast a vote for each judgement it has.
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 2, vec![dummy_validation_code_hash(1)]),
|
|
StartsNewSession {
|
|
session_index: 3,
|
|
validators: vec![Sr25519Keyring::Bob, OUR_VALIDATOR],
|
|
},
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
let vote = test_state.expect_submit_vote(&mut handle).await;
|
|
assert_eq!(vote.stmt.accept, false);
|
|
assert_eq!(vote.stmt.session_index, 3);
|
|
assert_eq!(vote.stmt.validator_index, 1.into());
|
|
assert_eq!(vote.stmt.subject, dummy_validation_code_hash(1));
|
|
vote.reply_ok();
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn resign_on_session_change() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
let pvf_1 = dummy_validation_code_hash(1);
|
|
let pvf_2 = dummy_validation_code_hash(2);
|
|
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 1, vec![pvf_1, pvf_2]),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
let pre_check_1 = test_state.expect_candidate_precheck(&mut handle).await;
|
|
assert_eq!(pre_check_1.validation_code_hash, pvf_1);
|
|
pre_check_1.reply(PreCheckOutcome::Valid);
|
|
let pre_check_2 = test_state.expect_candidate_precheck(&mut handle).await;
|
|
assert_eq!(pre_check_2.validation_code_hash, pvf_2);
|
|
pre_check_2.reply(PreCheckOutcome::Invalid);
|
|
|
|
test_state.expect_submit_vote(&mut handle).await.reply_ok();
|
|
test_state.expect_submit_vote(&mut handle).await.reply_ok();
|
|
|
|
// So far so good. Now we change the session.
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 2, vec![pvf_1, pvf_2]),
|
|
StartsNewSession { session_index: 3, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
// The votes should be re-signed and re-submitted.
|
|
let mut statements = Vec::new();
|
|
let vote_1 = test_state.expect_submit_vote(&mut handle).await;
|
|
statements.push(vote_1.stmt.clone());
|
|
vote_1.reply_ok();
|
|
let vote_2 = test_state.expect_submit_vote(&mut handle).await;
|
|
statements.push(vote_2.stmt.clone());
|
|
vote_2.reply_ok();
|
|
|
|
// Find and check the votes.
|
|
// Unfortunately, the order of revoting is not deterministic so we have to resort to
|
|
// a bit of trickery.
|
|
assert_eq!(statements.iter().find(|s| s.subject == pvf_1).unwrap().accept, true);
|
|
assert_eq!(statements.iter().find(|s| s.subject == pvf_2).unwrap().accept, false);
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn dont_resign_if_not_us() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
let pvf_1 = dummy_validation_code_hash(1);
|
|
let pvf_2 = dummy_validation_code_hash(2);
|
|
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 1, vec![pvf_1, pvf_2]),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
let pre_check_1 = test_state.expect_candidate_precheck(&mut handle).await;
|
|
assert_eq!(pre_check_1.validation_code_hash, pvf_1);
|
|
pre_check_1.reply(PreCheckOutcome::Valid);
|
|
let pre_check_2 = test_state.expect_candidate_precheck(&mut handle).await;
|
|
assert_eq!(pre_check_2.validation_code_hash, pvf_2);
|
|
pre_check_2.reply(PreCheckOutcome::Invalid);
|
|
|
|
test_state.expect_submit_vote(&mut handle).await.reply_ok();
|
|
test_state.expect_submit_vote(&mut handle).await.reply_ok();
|
|
|
|
// So far so good. Now we change the session.
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 2, vec![pvf_1, pvf_2]),
|
|
StartsNewSession {
|
|
session_index: 3,
|
|
// not us
|
|
validators: vec![Sr25519Keyring::Bob],
|
|
},
|
|
)
|
|
.await;
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
// We do not expect any votes to be re-signed.
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn api_not_supported() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 1, vec![dummy_validation_code_hash(1)]),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_not_supported();
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn not_supported_api_becomes_supported() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 1, vec![dummy_validation_code_hash(1)]),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_not_supported();
|
|
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
FakeLeaf::new(dummy_hash(), 1, vec![dummy_validation_code_hash(1)]),
|
|
StartsNewSession { session_index: 3, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
test_state
|
|
.expect_candidate_precheck(&mut handle)
|
|
.await
|
|
.reply(PreCheckOutcome::Valid);
|
|
test_state.expect_submit_vote(&mut handle).await.reply_ok();
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn unexpected_pvf_check_judgement() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
let block_1 = FakeLeaf::new(dummy_hash(), 1, vec![dummy_validation_code_hash(1)]);
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
block_1.clone(),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
// Catch the pre-check request, but don't reply just yet.
|
|
let pre_check = test_state.expect_candidate_precheck(&mut handle).await;
|
|
|
|
// Now deactivate the leaf and reply to the precheck request.
|
|
test_state.deactivate_leaves(&mut handle, &[block_1.block_hash]).await;
|
|
pre_check.reply(PreCheckOutcome::Invalid);
|
|
|
|
// the subsystem must remain silent.
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|
|
|
|
// Check that we do not abstain for a nondeterministic failure. Currently, this means the behavior
|
|
// is the same as if the pre-check returned `PreCheckOutcome::Invalid`.
|
|
#[test]
|
|
fn dont_abstain_for_nondeterministic_pvfcheck_failure() {
|
|
test_harness(|mut test_state, mut handle| {
|
|
async move {
|
|
let block_1 = FakeLeaf::new(dummy_hash(), 1, vec![dummy_validation_code_hash(1)]);
|
|
test_state
|
|
.activate_leaf_with_session(
|
|
&mut handle,
|
|
block_1.clone(),
|
|
StartsNewSession { session_index: 2, validators: vec![OUR_VALIDATOR] },
|
|
)
|
|
.await;
|
|
|
|
test_state.expect_pvfs_require_precheck(&mut handle).await.reply_mock();
|
|
test_state.expect_session_for_child(&mut handle).await;
|
|
test_state.expect_validators(&mut handle).await;
|
|
|
|
// Catch the pre-check request, but don't reply just yet.
|
|
let pre_check = test_state.expect_candidate_precheck(&mut handle).await;
|
|
|
|
// Now deactivate the leaf and reply to the precheck request.
|
|
test_state.deactivate_leaves(&mut handle, &[block_1.block_hash]).await;
|
|
pre_check.reply(PreCheckOutcome::Failed);
|
|
|
|
// the subsystem must remain silent.
|
|
|
|
test_state.send_conclude(&mut handle).await;
|
|
}
|
|
.boxed()
|
|
});
|
|
}
|