mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-22 16:11:08 +00:00
002d9260f9
**Update:** Pushed additional changes based on the review comments. **This pull request fixes various spelling mistakes in this repository.** Most of the changes are contained in the first **3** commits: - `Fix spelling mistakes in comments and docs` - `Fix spelling mistakes in test names` - `Fix spelling mistakes in error messages, panic messages, logs and tracing` Other source code spelling mistakes are separated into individual commits for easier reviewing: - `Fix the spelling of 'authority'` - `Fix the spelling of 'REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY'` - `Fix the spelling of 'prev_enqueud_messages'` - `Fix the spelling of 'endpoint'` - `Fix the spelling of 'children'` - `Fix the spelling of 'PenpalSiblingSovereignAccount'` - `Fix the spelling of 'PenpalSudoAccount'` - `Fix the spelling of 'insufficient'` - `Fix the spelling of 'PalletXcmExtrinsicsBenchmark'` - `Fix the spelling of 'subtracted'` - `Fix the spelling of 'CandidatePendingAvailability'` - `Fix the spelling of 'exclusive'` - `Fix the spelling of 'until'` - `Fix the spelling of 'discriminator'` - `Fix the spelling of 'nonexistent'` - `Fix the spelling of 'subsystem'` - `Fix the spelling of 'indices'` - `Fix the spelling of 'committed'` - `Fix the spelling of 'topology'` - `Fix the spelling of 'response'` - `Fix the spelling of 'beneficiary'` - `Fix the spelling of 'formatted'` - `Fix the spelling of 'UNKNOWN_PROOF_REQUEST'` - `Fix the spelling of 'succeeded'` - `Fix the spelling of 'reopened'` - `Fix the spelling of 'proposer'` - `Fix the spelling of 'InstantiationNonce'` - `Fix the spelling of 'depositor'` - `Fix the spelling of 'expiration'` - `Fix the spelling of 'phantom'` - `Fix the spelling of 'AggregatedKeyValue'` - `Fix the spelling of 'randomness'` - `Fix the spelling of 'defendant'` - `Fix the spelling of 'AquaticMammal'` - `Fix the spelling of 'transactions'` - `Fix the spelling of 'PassingTracingSubscriber'` - `Fix the spelling of 'TxSignaturePayload'` - `Fix the spelling of 'versioning'` - `Fix the spelling of 'descendant'` - `Fix the spelling of 'overridden'` - `Fix the spelling of 'network'` Let me know if this structure is adequate. **Note:** The usage of the words `Merkle`, `Merkelize`, `Merklization`, `Merkelization`, `Merkleization`, is somewhat inconsistent but I left it as it is. ~~**Note:** In some places the term `Receival` is used to refer to message reception, IMO `Reception` is the correct word here, but I left it as it is.~~ ~~**Note:** In some places the term `Overlayed` is used instead of the more acceptable version `Overlaid` but I also left it as it is.~~ ~~**Note:** In some places the term `Applyable` is used instead of the correct version `Applicable` but I also left it as it is.~~ **Note:** Some usage of British vs American english e.g. `judgement` vs `judgment`, `initialise` vs `initialize`, `optimise` vs `optimize` etc. are both present in different places, but I suppose that's understandable given the number of contributors. ~~**Note:** There is a spelling mistake in `.github/CODEOWNERS` but it triggers errors in CI when I make changes to it, so I left it as it is.~~
931 lines
29 KiB
Rust
931 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, validator_pubkeys};
|
|
use futures::{channel::oneshot, future::BoxFuture, prelude::*};
|
|
use polkadot_node_subsystem::{
|
|
messages::{
|
|
AllMessages, CandidateValidationMessage, PreCheckOutcome, PvfCheckerMessage,
|
|
RuntimeApiMessage, RuntimeApiRequest,
|
|
},
|
|
ActiveLeavesUpdate, FromOrchestra, OverseerSignal, RuntimeApiError,
|
|
};
|
|
use polkadot_node_subsystem_test_helpers::{
|
|
make_subsystem_context, mock::new_leaf, 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(discriminator: u8) -> ValidationCodeHash {
|
|
ValidationCode(vec![discriminator]).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;
|
|
|
|
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,
|
|
LeafState {
|
|
session_index: self.last_session_index,
|
|
pvfs: activated_leaf.pvfs.clone(),
|
|
},
|
|
);
|
|
|
|
Some(new_leaf(activated_leaf.block_hash, activated_leaf.block_number))
|
|
} 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,
|
|
response_sender,
|
|
..
|
|
}) => ExpectCandidatePrecheck { relay_parent, validation_code_hash, tx: response_sender },
|
|
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]);
|
|
let block_2 = block_1.descendant(vec![]);
|
|
let block_3 = block_2.descendant(vec![pvf]);
|
|
|
|
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 descendant 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]);
|
|
let block_2_1 = FakeLeaf::new([2; 32].into(), 1, vec![pvf]);
|
|
let block_1_2 = block_1_1.descendant(vec![pvf]);
|
|
|
|
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]);
|
|
let block_2 = FakeLeaf::new([2; 32].into(), 1, vec![pvf_2]);
|
|
|
|
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()
|
|
});
|
|
}
|